Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/openai" // OpenAI (openai-go)
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/anthropic" // Anthropic
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/genai" // Google GenAI
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/langchaingo" // LangChainGo OpenAI
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/github.com/sashabaranov/go-openai" // sashabaranov/go-openai
)
```
Expand Down
7 changes: 5 additions & 2 deletions examples/internal/autoinstrumentation/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/braintrustdata/braintrust-sdk-go/examples/internal/autoinstrumentation

go 1.24.0
go 1.24.4

toolchain go1.24.11

Expand Down Expand Up @@ -51,6 +51,7 @@ require (
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/dave/dst v0.27.3 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
Expand All @@ -71,7 +72,7 @@ require (
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
Expand All @@ -90,6 +91,7 @@ require (
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/polyfloyd/go-errorlint v1.8.1-0.20250906200200-9b25878c4dea // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
Expand All @@ -108,6 +110,7 @@ require (
github.com/tinylib/msgp v1.4.0 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/tmc/langchaingo v0.1.14 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
7 changes: 7 additions & 0 deletions examples/internal/autoinstrumentation/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
Expand Down Expand Up @@ -142,6 +144,7 @@ github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3J
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
Expand Down Expand Up @@ -202,6 +205,8 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -269,6 +274,8 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmc/langchaingo v0.1.14 h1:o1qWBPigAIuFvrG6cjTFo0cZPFEZ47ZqpOYMjM15yZc=
github.com/tmc/langchaingo v0.1.14/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/vmihailenco/msgpack/v4 v4.3.13 h1:A2wsiTbvp63ilDaWmsk2wjx6xZdxQOvpiNlKBGKKXKI=
Expand Down
27 changes: 26 additions & 1 deletion examples/internal/autoinstrumentation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
// - OpenAI (openai-go official SDK)
// - Anthropic
// - sashabaranov/go-openai
// - LangChainGo (OpenAI provider)
//
// Note: NO manual middleware is added to any client.
// Note: NO manual middleware or callbacks are added to any client.
// When built with `orchestrion go build`, tracing middleware is injected at compile time.
//
// To run:
Expand All @@ -25,6 +26,8 @@ import (
"github.com/anthropics/anthropic-sdk-go"
"github.com/openai/openai-go"
sashabaranov "github.com/sashabaranov/go-openai"
"github.com/tmc/langchaingo/llms"
langchainopenai "github.com/tmc/langchaingo/llms/openai"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"

Expand Down Expand Up @@ -71,6 +74,10 @@ func main() {
fmt.Println("3. sashabaranov/go-openai...")
runSashabaranov(ctx)

// 4. LangChainGo - NO callback added
fmt.Println("4. LangChainGo (OpenAI)...")
runLangChainGo(ctx)

fmt.Println("\n=== All providers tested ===")
fmt.Println("If tracing worked, you should see LLM spans for each provider in Braintrust.")
fmt.Printf("View trace: %s\n", bt.Permalink(rootSpan))
Expand Down Expand Up @@ -128,3 +135,21 @@ func runSashabaranov(ctx context.Context) {
}
fmt.Printf(" Response: %s\n", resp.Choices[0].Message.Content)
}

func runLangChainGo(ctx context.Context) {
// NO callback - Orchestrion injects it
llm, err := langchainopenai.New()
if err != nil {
log.Printf(" LangChainGo error: %v", err)
return
}

resp, err := llm.GenerateContent(ctx, []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeHuman, "Say 'Hello from LangChainGo' in exactly those words."),
})
if err != nil {
log.Printf(" LangChainGo error: %v", err)
return
}
fmt.Printf(" Response: %s\n", resp.Choices[0].Content)
}
7 changes: 4 additions & 3 deletions internal/genorchestrion/genorchestrion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestGenerate(t *testing.T) {
"anthropic-newclient-middleware",
"sashabaranov-newclientwithconfig-wrap",
"genai-newclient-wrap",
"langchaingo-openai-newllm-callback",
}

for _, expected := range expectedAspects {
Expand Down Expand Up @@ -74,10 +75,10 @@ func TestGenerateExcludesAllDirectory(t *testing.T) {
t.Fatalf("Failed to parse generated YAML: %v", err)
}

// Count aspects - should be exactly 5 (one per provider, plus openai-v2)
// Count aspects - should be exactly 6 (one per provider, plus openai-v2)
// If it's more, we might be including duplicates from all/
if len(result.Aspects) != 5 {
t.Errorf("expected 5 aspects, got %d", len(result.Aspects))
if len(result.Aspects) != 6 {
t.Errorf("expected 6 aspects, got %d", len(result.Aspects))
}
}

Expand Down
132 changes: 89 additions & 43 deletions trace/contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Set up OpenTelemetry and initialize Braintrust:

```go
import (
"context"
"log"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"github.com/braintrustdata/braintrust-sdk-go"
Expand All @@ -30,86 +33,129 @@ func main() {

```go
import (
"context"

"github.com/openai/openai-go"
"github.com/openai/openai-go/option"
traceopenai "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/openai"
)

client := openai.NewClient(
option.WithMiddleware(traceopenai.NewMiddleware()),
)

_, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Hello!"),
},
Model: openai.ChatModelGPT4oMini,
})
func main() {
ctx := context.Background()
client := openai.NewClient(
option.WithMiddleware(traceopenai.NewMiddleware()),
)

_, _ = client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Hello!"),
},
Model: openai.ChatModelGPT4oMini,
})
}
```

## Anthropic

```go
import (
"context"

"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
traceanthropic "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/anthropic"
)

client := anthropic.NewClient(
option.WithMiddleware(traceanthropic.NewMiddleware()),
)

_, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaude3_7SonnetLatest,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
MaxTokens: 1024,
})
func main() {
ctx := context.Background()
client := anthropic.NewClient(
option.WithMiddleware(traceanthropic.NewMiddleware()),
)

_, _ = client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaude3_7SonnetLatest,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
MaxTokens: 1024,
})
}
```

## Google Gemini

```go
import (
"context"
"os"

"google.golang.org/genai"
tracegenai "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/genai"
)

client, err := genai.NewClient(ctx, &genai.ClientConfig{
HTTPClient: tracegenai.Client(),
APIKey: os.Getenv("GOOGLE_API_KEY"),
Backend: genai.BackendGeminiAPI,
})

_, err = client.Models.GenerateContent(ctx,
"gemini-1.5-flash",
genai.Text("Hello!"),
nil,
)
func main() {
ctx := context.Background()
client, _ := genai.NewClient(ctx, &genai.ClientConfig{
HTTPClient: tracegenai.Client(),
APIKey: os.Getenv("GOOGLE_API_KEY"),
Backend: genai.BackendGeminiAPI,
})

_, _ = client.Models.GenerateContent(ctx,
"gemini-1.5-flash",
genai.Text("Hello!"),
nil,
)
}
```

## sashabaranov/go-openai

```go
import (
"context"
"os"

"github.com/sashabaranov/go-openai"
traceopenai "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/github.com/sashabaranov/go-openai"
)

config := openai.DefaultConfig(os.Getenv("OPENAI_API_KEY"))
config.HTTPClient = traceopenai.Client()
client := openai.NewClientWithConfig(config)

_, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleUser, Content: "Hello!"},
},
})
func main() {
ctx := context.Background()
config := openai.DefaultConfig(os.Getenv("OPENAI_API_KEY"))
config.HTTPClient = traceopenai.Client()
client := openai.NewClientWithConfig(config)

_, _ = client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleUser, Content: "Hello!"},
},
})
}
```

## LangChainGo

See [`examples/langchaingo`](../../examples/langchaingo/main.go) for LangChainGo integration with callback-based tracing.
```go
import (
"context"

"github.com/tmc/langchaingo/llms"
"github.com/tmc/langchaingo/llms/openai"
tracelangchaingo "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/langchaingo"
)

func main() {
ctx := context.Background()
handler := tracelangchaingo.NewHandler()
llm, _ := openai.New(openai.WithCallback(handler))

_, _ = llm.GenerateContent(ctx, []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeHuman, "Hello!"),
})
}
```

For richer traces, use `NewHandlerWithOptions` with `TracerProvider`, `Model`, and `Provider` options.
See [`examples/langchaingo`](../../examples/langchaingo/main.go) for complete examples.
11 changes: 11 additions & 0 deletions trace/contrib/all/orchestrion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ aspects:
__config.HTTPClient = traceopenai.WrapHTTPDoer(__config.HTTPClient)
return openai.NewClientWithConfig(__config)
}()
- id: langchaingo-openai-newllm-callback
join-point:
function-call: github.com/tmc/langchaingo/llms/openai.New
advice:
- append-args:
type: github.com/tmc/langchaingo/llms/openai.Option
values:
- imports:
tracelangchaingo: github.com/braintrustdata/braintrust-sdk-go/trace/contrib/langchaingo
openai: github.com/tmc/langchaingo/llms/openai
template: openai.WithCallback(tracelangchaingo.NewOpenAIHandler())
- id: openai-newclient-middleware
join-point:
function-call: github.com/openai/openai-go.NewClient
Expand Down
17 changes: 17 additions & 0 deletions trace/contrib/langchaingo/orchestrion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# yaml-language-server: $schema=https://datadoghq.dev/orchestrion/schema.json
meta:
name: github.com/braintrustdata/braintrust-sdk-go/trace/contrib/langchaingo
description: Auto-instrumentation for LangChainGo OpenAI provider with Braintrust tracing

aspects:
- id: langchaingo-openai-newllm-callback
join-point:
function-call: github.com/tmc/langchaingo/llms/openai.New
advice:
- append-args:
type: github.com/tmc/langchaingo/llms/openai.Option
values:
- imports:
tracelangchaingo: github.com/braintrustdata/braintrust-sdk-go/trace/contrib/langchaingo
openai: github.com/tmc/langchaingo/llms/openai
template: openai.WithCallback(tracelangchaingo.NewOpenAIHandler())
12 changes: 12 additions & 0 deletions trace/contrib/langchaingo/tracelangchaingo.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ func NewHandler() *Handler {
}
}

// NewOpenAIHandler creates a new Handler pre-configured for the LangChainGo OpenAI provider.
// This is used by auto-instrumentation to provide correct provider metadata.
func NewOpenAIHandler() *Handler {
return &Handler{
spans: make(map[context.Context][]spanEntry),
streamBuffers: make(map[string]*strings.Builder),
opts: HandlerOptions{
Provider: "openai",
},
}
}

// NewHandlerWithOptions creates a new Handler with custom options.
func NewHandlerWithOptions(opts HandlerOptions) *Handler {
return &Handler{
Expand Down
Loading