> ## Documentation Index
> Fetch the complete documentation index at: https://braintrust.dev/docs/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

If you are a coding agent, prefer the Braintrust [`bt` CLI](/docs/reference/cli/quickstart) for repeatable, scriptable work: running evals, instrumenting code, querying logs, syncing data, managing functions, and configuring coding agents. Use the MCP server for reasoning over Braintrust data in conversation, and for capabilities the CLI doesn't cover, such as monitor views, alerts, and authoring evaluators, preprocessors, and facets.

[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.

<View title="Go" icon="https://img.logo.dev/go.dev?token=pk_BdcHD9e5SCW3j1rnJkNyMQ">
  <h2 id="setup-go">
    Setup
  </h2>

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

  <Steps>
    <Step title="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
      ```
    </Step>

    <Step title="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
      ```
    </Step>
  </Steps>

  <h2 id="auto-instrumentation-go">
    Auto-instrumentation
  </h2>

  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.

  <Steps>
    <Step title="Install Orchestrion">
      ```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
      go install github.com/DataDog/orchestrion@latest
      ```
    </Step>

    <Step title="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"
      )
      ```
    </Step>

    <Step title="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.

      <CodeGroup>
        ```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))
        }
        ```
      </CodeGroup>
    </Step>

    <Step title="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 title="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 ./...
        ```
      </Accordion>
    </Step>
  </Steps>

  <h2 id="manual-instrumentation-go">
    Manual instrumentation
  </h2>

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

  <CodeGroup>
    ```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))
    }
    ```
  </CodeGroup>

  <h2 id="streaming-go">
    Streaming
  </h2>

  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()
  ```

  <h2 id="tracing-embeddings-go">
    Tracing embeddings
  </h2>

  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.

  <h2 id="what-traced-go">
    What Braintrust traces
  </h2>

  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.

  <h2 id="resources-go">
    Resources
  </h2>

  * [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](/docs/instrument/trace-llm-calls) for general Go auto-instrumentation setup
</View>
