> ## Documentation Index
> Fetch the complete documentation index at: https://docs.braintrust.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CloudWeGo Eino

> Trace CloudWeGo Eino chat model, tool, and embedding calls in Braintrust to debug and evaluate your LLM apps

[CloudWeGo Eino](https://github.com/cloudwego/eino) is a Go framework for building LLM applications. Braintrust traces Eino's chat model, tool, and embedding components, including streaming responses and tool calls.

**Go**

## Setup

Install the Braintrust Go SDK alongside the Eino packages, then configure your API keys.

**1. Install packages**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
go get github.com/braintrustdata/braintrust-sdk-go
go get github.com/braintrustdata/braintrust-sdk-go/trace/contrib/cloudwego/eino
go get github.com/cloudwego/eino-ext/components/model/openai
```

**2. Set environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=your-braintrust-api-key
OPENAI_API_KEY=your-openai-api-key
```

## Auto-instrumentation

To trace Eino without wiring Braintrust into your application code, build your app with [Orchestrion](https://github.com/DataDog/orchestrion), a compile-time tool that appends Braintrust's handler to your `callbacks.AppendGlobalHandlers` call. Once the handler is registered, all `ChatModel`, tool, and embedding calls are traced.

**1. Install Orchestrion**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
go install github.com/DataDog/orchestrion@latest
```

**2. Create orchestrion.tool.go in your project root**

```go title="orchestrion.tool.go" #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
//go:build tools

package main

import (
_ "github.com/DataDog/orchestrion"
_ "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/cloudwego/eino"
)
```

**3. Write your app**

Register Eino's global handlers with `callbacks.AppendGlobalHandlers()`. Orchestrion appends `traceeino.DefaultHandler()` to that call at build time, so you don't import or construct the Braintrust handler yourself.

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
package main

import (
"context"
"fmt"
"log"
"os"

einoopenai "github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/callbacks"
"github.com/cloudwego/eino/schema"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"

"github.com/braintrustdata/braintrust-sdk-go"
)

func main() {
tp := trace.NewTracerProvider()
defer tp.Shutdown(context.Background())
otel.SetTracerProvider(tp)

bt, err := braintrust.New(tp,
	braintrust.WithProject("my-eino-project"),
	braintrust.WithAPIKey(os.Getenv("BRAINTRUST_API_KEY")),
)
if err != nil {
	log.Fatal(err)
}

// Orchestrion appends traceeino.DefaultHandler() to this call at build time.
callbacks.AppendGlobalHandlers()

ctx := context.Background()
tracer := otel.Tracer("eino-example")
ctx, span := tracer.Start(ctx, "eino-chat")
defer span.End()

chatModel, err := einoopenai.NewChatModel(ctx, &einoopenai.ChatModelConfig{
	Model:  "gpt-4o-mini",
	APIKey: os.Getenv("OPENAI_API_KEY"),
})
if err != nil {
	log.Fatal(err)
}

messages := []*schema.Message{{
	Role:    schema.User,
	Content: "What is the capital of France?",
}}
resp, err := chatModel.Generate(ctx, messages)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Response: %s\n", resp.Content)
fmt.Printf("View trace: %s\n", bt.Permalink(span))
}
```

**4. Build with Orchestrion and run**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
go mod tidy
orchestrion go build -o myapp
./myapp
```

> [accordion] Enable Orchestrion via GOFLAGS
>
> Instead of running `orchestrion go build`, you can set a `GOFLAGS` environment variable to enable Orchestrion for normal `go build` commands:
>
> ```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> export GOFLAGS="-toolexec='orchestrion toolexec'"
> go build ./...
> ```

## Manual instrumentation

To trace Eino manually, register Braintrust's handler yourself by passing `traceeino.NewHandler()` to `callbacks.AppendGlobalHandlers`. All subsequent `ChatModel` calls are traced.

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	einoopenai "github.com/cloudwego/eino-ext/components/model/openai"
	"github.com/cloudwego/eino/callbacks"
	"github.com/cloudwego/eino/schema"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/sdk/trace"

	"github.com/braintrustdata/braintrust-sdk-go"
	traceeino "github.com/braintrustdata/braintrust-sdk-go/trace/contrib/cloudwego/eino"
)

func main() {
	tp := trace.NewTracerProvider()
	defer tp.Shutdown(context.Background())
	otel.SetTracerProvider(tp)

	bt, err := braintrust.New(tp,
		braintrust.WithProject("my-eino-project"),
		braintrust.WithAPIKey(os.Getenv("BRAINTRUST_API_KEY")),
	)
	if err != nil {
		log.Fatal(err)
	}

	handler := traceeino.NewHandler()
	callbacks.AppendGlobalHandlers(handler)

	ctx := context.Background()
	tracer := otel.Tracer("eino-example")
	ctx, span := tracer.Start(ctx, "eino-chat")
	defer span.End()

	chatModel, err := einoopenai.NewChatModel(ctx, &einoopenai.ChatModelConfig{
		Model:  "gpt-4o-mini",
		APIKey: os.Getenv("OPENAI_API_KEY"),
	})
	if err != nil {
		log.Fatal(err)
	}

	messages := []*schema.Message{{
		Role:    schema.User,
		Content: "What is the capital of France?",
	}}
	resp, err := chatModel.Generate(ctx, messages)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Response: %s\n", resp.Content)
	fmt.Printf("View trace: %s\n", bt.Permalink(span))
}
```

## Streaming

For streaming, call `Wait()` on the handler after closing the reader so asynchronously finalized span attributes are flushed before exit. Get the handler from `traceeino.NewHandler()` in the manual path, or from `traceeino.DefaultHandler()` when auto-instrumenting.

```go #skip-compile theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
reader, err := chatModel.Stream(ctx, messages)
if err != nil {
	log.Fatal(err)
}
// ... consume the stream ...
reader.Close()
handler.Wait()
```

## Tracing embeddings

Eino's callback handler captures `EmbedStrings` calls with no setup beyond registering the handler. Embedding calls appear as embedding spans in Braintrust, with input texts serialized as `{"inputs": [{"content": "..."}]}`, output as `{"count": N}`, and model metadata captured.

## What Braintrust traces

Braintrust captures:

* Graph spans (`eino.<Graph>`, `eino.<Chain>`, `eino.<Workflow>`), emitted as `task` spans that wrap all child model, tool, and embedding calls for the run.
* Chat model spans (`eino.<model>`, for example `eino.OpenAI`), with input messages, model parameters (model, max\_tokens, temperature, top\_p, stop, tools, tool\_choice) in metadata, output in OpenAI-compatible choices format (`[{"index": 0, "finish_reason": "...", "message": {...}}]`), and token usage metrics (prompt, completion, total, cached prompt, and reasoning tokens).
* Streaming chat model output reconstructed from streamed chunks, with a `time_to_first_token` metric.
* Multimodal message content (images, audio, video, files) captured as content-part arrays.
* Tool spans (`eino.<tool>`), with the tool's JSON arguments as input, its name in `braintrust.span_attributes`, and its response as output.
* Embedding spans (`eino.<embedder>`), with input as `{"inputs": [{"content": "..."}]}`, output as `{"count": N}`, model in metadata, and prompt and total token metrics.

## Resources

* [CloudWeGo Eino documentation](https://github.com/cloudwego/eino)
* [Go SDK example](https://github.com/braintrustdata/braintrust-sdk-go/tree/main/examples/cloudwego/eino)
* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) for general Go auto-instrumentation setup


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.