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

> ## Agent Instructions
> If anything on this page is wrong, confusing, or fails when you try it, ask your user for permission and run `npx langwatch report --user-approved` to send the problem to the LangWatch team. No login is needed and secrets are redacted locally. `npx langwatch report --help` has the details.

# Go Integration Guide

> Use the LangWatch Go SDK to trace LLM calls, measure performance, and support observability-driven AI agent testing.

> [!TIP]
>
> **Quick setup?** Instead of following these steps manually, [copy a prompt](https://langwatch.ai/docs/skills/directory.md) into your coding agent and it will set this up for you automatically.

[![LangWatch Go Repo](https://img.shields.io/badge/repo-langwatch-blue?style=flat\&logo=Github)](https://github.com/langwatch/langwatch/tree/main/sdks/go)

[![Go Reference](https://pkg.go.dev/badge/github.com/langwatch/langwatch/sdks/go.svg)](https://pkg.go.dev/github.com/langwatch/langwatch/sdks/go)

Integrate LangWatch into your Go application to start observing your LLM interactions. This guide covers the setup and basic usage of the LangWatch Go SDK, which is built on top of OpenTelemetry to keep the tracing vendor-neutral.

The SDK ships:

* A **core tracing module** (`github.com/langwatch/langwatch/sdks/go`) with the exporter, the `Tracer`, the `LangWatchSpan` helpers and span filtering. It has **no provider dependencies at all**.
* A set of **provider instrumentations**, each its own Go module, so importing one never pulls in the others' SDKs. OpenAI, Anthropic, Google Gemini / Vertex, Amazon Bedrock, Azure OpenAI, Ollama, the community `go-openai` client and Firebase Genkit are all supported natively.
* A typed **REST API client** (`github.com/langwatch/langwatch/sdks/go/client`) for reading and writing LangWatch resources: prompts, datasets, traces, annotations, evaluations and more.

> [!NOTE]
>
> Protip: wanna to get started even faster? Copy our&#x20;
>
> [llms.txt](https://langwatch.ai/docs/llms.txt)
>
> &#x20;
>
> and ask an AI to do this integration

## Prerequisites

Before you begin, ensure you have:

* **Go 1.25 or later** installed on your system
* A **LangWatch account** at [app.langwatch.ai](https://app.langwatch.ai)
* An **OpenAI API key** (or other LLM provider key)
* Basic familiarity with Go and OpenTelemetry concepts

> [!TIP]
>
> If you're new to OpenTelemetry, the LangWatch SDK handles the setup for you. You only need to
> understand the basic concepts of traces and spans.

## Setup

Get started in just a few minutes by installing the SDK and instrumenting your application.

**1. Get your LangWatch API Key**

Sign up at [app.langwatch.ai](https://app.langwatch.ai) and find your API key in your project settings. Set it as an environment variable:

```bash theme={null}
export LANGWATCH_API_KEY="your-langwatch-api-key"
export LANGWATCH_PROJECT_ID="your-project-id"  # Required for service API keys / PATs
export OPENAI_API_KEY="your-openai-api-key"
```

> [!NOTE]
>
> `LANGWATCH_PROJECT_ID` is required when using a **service API key** or a **Personal Access Token** (e.g. for CI/CD or multi-project setups). Project API keys obtained from the project settings page already have the project context built in.

> [!TIP]
>
> You can verify your API key is set by running `echo $LANGWATCH_API_KEY`

**2. Install SDK Packages**

Add the core SDK plus the instrumentation for the provider you use. Each instrumentation is its own module, so you only pull in the SDK you actually need:

```bash theme={null}
go get github.com/langwatch/langwatch/sdks/go
go get github.com/langwatch/langwatch/sdks/go/instrumentation/openai
```

> [!TIP]
>
> Verify installation by running `go mod tidy` and checking that all dependencies are resolved.

**3. Configure the LangWatch Exporter**

Set up the LangWatch exporter in your application initialization. `NewExporter` reads `LANGWATCH_API_KEY` from the environment, builds an OTLP exporter pointed at LangWatch, and you register it as your global tracer provider:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

func setupLangWatch(ctx context.Context) func(context.Context) {
	// Create the LangWatch exporter (reads LANGWATCH_API_KEY from the environment).
	exporter, err := langwatch.NewExporter(ctx)
	if err != nil {
		log.Fatalf("failed to create LangWatch exporter: %v", err)
	}

  tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))
  otel.SetTracerProvider(tp)

  return func(ctx context.Context) {
  	if err := tp.Shutdown(ctx); err != nil {
  		log.Printf("Error shutting down tracer provider: %v", err)
  	}
  }
}
```

> [!TIP]
>
> Use `langwatch.NewDefaultExporter(ctx)` instead if you want LangWatch's default span filter applied automatically (it excludes raw HTTP request spans like `GET /api`). See [Filtering Spans](#filtering-spans) below.

> [!WARNING]
>
> **Critical: Always call the shutdown function!** The `defer shutdown(ctx)` call is essential. Without it, traces buffered in memory will be lost when your application exits. The shutdown function flushes all pending traces to LangWatch and releases resources. Never skip this step, especially in short-lived applications like CLI tools or serverless functions.

**4. Instrument Your OpenAI Client**

Add the LangWatch middleware to your OpenAI client. The middleware captures request **and** response content by default, so you do not need to enable either:

```go theme={null}
client := openai.NewClient(
	option.WithAPIKey(os.Getenv("OPENAI_API_KEY")),
	option.WithMiddleware(otelopenai.Middleware("my-app")),
)
```

> [!NOTE]
>
> The middleware captures input and output content by default (`langwatch.DataCaptureAll`). To opt out, pass `otelopenai.WithDataCapture(langwatch.DataCaptureNone)`. See [Data Capture](#data-capture).

> [!TIP]
>
> The middleware automatically captures all OpenAI API calls, including streaming responses, token usage (input / output / cached / reasoning), and model information.

**5. Create Your First Trace**

Start a root span to capture your LLM interaction. Any instrumented call made with the returned `ctx` is nested under it:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

tracer := langwatch.Tracer("my-app")
ctx, span := tracer.Start(ctx, "ChatWithUser")
defer span.End()

  // Your OpenAI call remains unchanged!
  response, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
  	Model: "gpt-6-luna",
  	Messages: []openai.ChatCompletionMessageParamUnion{
  		openai.SystemMessage("You are a helpful assistant."),
  		openai.UserMessage("Hello, OpenAI!"),
  	},
  })
```

> [!TIP]
>
> View your traces at [app.langwatch.ai](https://app.langwatch.ai) within seconds of making your first API call.

**That's it!** 🎉 You've successfully integrated LangWatch into your Go application. Traces will now be sent to your LangWatch project, providing you with immediate visibility into your LLM interactions.

### Complete Working Example

Here's a minimal working example that combines all the setup steps. Note the `openai-go/v3` import paths:

```go theme={null}
package main

import (
	"context"
	"log"
	"os"

	langwatch "github.com/langwatch/langwatch/sdks/go"
	otelopenai "github.com/langwatch/langwatch/sdks/go/instrumentation/openai"

	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/option"
	"go.opentelemetry.io/otel"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func main() {
	ctx := context.Background()

	// 1. Set up LangWatch tracing (just once in your app).
	shutdown := setupLangWatch(ctx)
	defer shutdown(ctx) // Critical: ensures traces are flushed before exit.

	// 2. Add the middleware to your OpenAI client. It captures input + output
	//    by default; pass otelopenai.WithDataCapture(langwatch.DataCaptureNone) to opt out.
	client := openai.NewClient(
		option.WithAPIKey(os.Getenv("OPENAI_API_KEY")),
		option.WithMiddleware(otelopenai.Middleware("my-app")),
	)

	// 3. Create a root span for your operation.
	tracer := langwatch.Tracer("my-app")
	ctx, span := tracer.Start(ctx, "ChatWithUser")
	defer span.End()

	// Your OpenAI call remains unchanged!
	response, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
		Model: "gpt-6-luna",
		Messages: []openai.ChatCompletionMessageParamUnion{
			openai.SystemMessage("You are a helpful assistant."),
			openai.UserMessage("Hello, OpenAI!"),
		},
	})
	if err != nil {
		log.Fatalf("Chat completion failed: %v", err)
	}

	log.Printf("Response: %s", response.Choices[0].Message.Content)
}

func setupLangWatch(ctx context.Context) func(context.Context) {
	// NewExporter reads LANGWATCH_API_KEY from the environment.
	exporter, err := langwatch.NewExporter(ctx)
	if err != nil {
		log.Fatalf("failed to create LangWatch exporter: %v", err)
	}

	tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))
	otel.SetTracerProvider(tp)

	return func(ctx context.Context) {
		// This flushes all pending traces, never skip this!
		if err := tp.Shutdown(ctx); err != nil {
			log.Printf("Error shutting down tracer provider: %v", err)
		}
	}
}
```

## Core Concepts

The Go SDK is designed to feel familiar to anyone who has used OpenTelemetry. It provides a thin wrapper to simplify LangWatch-specific functionality.

* Each message triggering your LLM pipeline as a whole is captured with a [Trace](https://langwatch.ai/docs/concepts.md#traces-one-task-end-to-end).
* A [Trace](https://langwatch.ai/docs/concepts.md#traces-one-task-end-to-end) contains multiple [Spans](https://langwatch.ai/docs/concepts.md#spans-the-building-blocks), which are the steps inside your pipeline.
* [Traces](https://langwatch.ai/docs/concepts.md#traces-one-task-end-to-end) can be grouped into a conversation by assigning a common `thread_id`.
* Use [User ID](https://langwatch.ai/docs/concepts.md#user-id-whos-using-the-app) to track individual user interactions.
* Apply [Labels](https://langwatch.ai/docs/concepts.md#labels-organizing-and-filtering-traces) for custom categorization and filtering.

> [!TIP]
>
> For detailed API documentation of all available methods and options, see the [Go SDK API
> Reference](https://langwatch.ai/docs/integration/go/reference.md).

### Creating a Trace

A trace represents a single, end-to-end task, like handling a user request. You create a trace by starting a "root" span. All other spans created within its context will be nested under it.

Before creating traces, ensure you have configured the SDK as shown in the [Setup Guide](https://langwatch.ai/docs/integration/go/guide.md#setup).

```go theme={null}
import (
	"context"

	langwatch "github.com/langwatch/langwatch/sdks/go"
)

func handleMessage(ctx context.Context, userMessage string) {
	// 1. Get a tracer.
	tracer := langwatch.Tracer("my-app")

	// 2. Start the root span for the trace.
	ctx, span := tracer.Start(ctx, "HandleUserMessage")
	defer span.End() // Important: always end the span.

	// 3. (Optional) Attach trace identity and record the input.
	span.SetThreadID("conversation-123")
	span.SetInput(userMessage)

	// ... Your business logic ...

	// 4. (Optional) Record the final output.
	span.SetOutput("This was the AI's response.")
}
```

### Creating Nested Spans

To instrument specific parts of your pipeline (like a RAG query or a tool call), create nested spans within an active trace. Set a [span type](https://langwatch.ai/docs/integration/go/reference.md#langwatch-span-types) so LangWatch can visualise it appropriately.

```go theme={null}
import (
	langwatch "github.com/langwatch/langwatch/sdks/go"
	"go.opentelemetry.io/otel/attribute"
)

func retrieveDocuments(ctx context.Context, query string) {
	// Assumes a trace has already been started in the parent context `ctx`.
	tracer := langwatch.Tracer("retrieval-logic")

	_, span := tracer.Start(ctx, "RetrieveDocumentsFromVectorDB")
	defer span.End()

	span.SetType(langwatch.SpanTypeRAG)
	span.SetInput(query)

	// ... logic to retrieve documents ...

	// You can add custom attributes to any span.
	span.SetAttributes(attribute.String("db.vendor", "pinecone"))
}
```

> [!NOTE]
>
> The context `ctx` is crucial. It carries the active span information, ensuring that
> `tracer.Start()` correctly creates a nested span instead of a new trace.

> [!TIP]
>
> `tracer.WithActiveSpan(ctx, name, fn)` runs `fn` with a span that auto-ends and records error status, so you don't have to remember `defer span.End()` or wire up error handling by hand.

## Automatic Instrumentation

Each provider instrumentation is a **separate Go module** so importing one never pulls in the others' SDKs. Add only the one(s) you use. All of the following are supported natively:

| Provider | Module | Underlying client |
| - | - | - |
| [OpenAI](https://langwatch.ai/docs/integration/go/integrations/open-ai.md) | `…/instrumentation/openai` | `github.com/openai/openai-go/v3` |
| [Anthropic (Claude)](https://langwatch.ai/docs/integration/go/integrations/anthropic.md) | `…/instrumentation/anthropic` | `github.com/anthropics/anthropic-sdk-go` |
| [Google Gemini / Vertex AI](https://langwatch.ai/docs/integration/go/integrations/google-gemini.md) | `…/instrumentation/googlegenai` | `google.golang.org/genai` |
| [Amazon Bedrock](https://langwatch.ai/docs/integration/go/integrations/amazon-bedrock.md) | `…/instrumentation/bedrock` | `aws-sdk-go-v2` bedrockruntime |
| [Azure OpenAI](https://langwatch.ai/docs/integration/go/integrations/azure-openai.md) | `…/instrumentation/azureopenai` | `openai-go/v3` (azure) |
| [Ollama](https://langwatch.ai/docs/integration/go/integrations/ollama.md) | `…/instrumentation/ollama` | `github.com/ollama/ollama/api` |
| [Firebase Genkit](https://langwatch.ai/docs/integration/go/integrations/genkit.md) | `…/instrumentation/genkit` | `github.com/firebase/genkit/go` |
| `go-openai` (community) | `…/instrumentation/gopenai` | `github.com/sashabaranov/go-openai` |

Every instrumentation captures the maximum available GenAI data: request + response model, every token type (input / output / total / cached / reasoning / cache-creation), cost where the provider returns it, finish reasons, request params, system instructions, tool definitions, and (capture-gated) input/output.

### OpenAI-compatible providers

Many providers expose an OpenAI-compatible endpoint. Reuse the `openai` instrumentation, point the client at the provider's base URL, and label the spans with the right provider name via `WithGenAIProvider`:

```go theme={null}
import semconv "go.opentelemetry.io/otel/semconv/v1.40.0"

client := openai.NewClient(
	option.WithBaseURL("https://api.groq.com/openai/v1"),
	option.WithAPIKey(os.Getenv("GROQ_API_KEY")),
	option.WithMiddleware(otelopenai.Middleware("my-app",
		otelopenai.WithGenAIProvider(semconv.GenAIProviderNameKey.String("groq")),
	)),
)
```

See the dedicated guides for [Groq](https://langwatch.ai/docs/integration/go/integrations/groq.md), [Grok (xAI)](https://langwatch.ai/docs/integration/go/integrations/grok.md) and [OpenRouter](https://langwatch.ai/docs/integration/go/integrations/openrouter.md).

> [!TIP]
>
> For detailed configuration options and the full attribute list, see the [Automatic Instrumentation section](https://langwatch.ai/docs/integration/go/reference.md#automatic-instrumentation) in the API reference.

## Recording Data on Spans

`LangWatchSpan` embeds the standard OpenTelemetry span and adds LangWatch helper methods. All setters return the span, so they can be chained:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

span.
	SetType(langwatch.SpanTypeLLM).
	SetInput("What is the capital of France?").
	SetOutput("The capital of France is Paris.").
	SetRequestModel("gpt-6-luna").
	SetResponseModel("gpt-6-luna").
	SetThreadID("conversation-123").
	SetUserID("user-42").
	SetLabels("production", "qa")
```

`SetInput` / `SetOutput` infer a value type (text, json, chat\_messages, list, …) from the Go value, or you can force one with a typed variant such as `SetInputJSON` or `SetOutputChatMessages`. Token usage is recorded with `SetGenAIUsage`; cost (only) is recorded with `SetMetrics`:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

span.SetGenAIUsage(langwatch.GenAIUsage{
	InputTokens:  langwatch.Int(120),
	OutputTokens: langwatch.Int(48),
	TotalTokens:  langwatch.Int(168),
})

span.SetMetrics(langwatch.SpanMetrics{
	Cost: langwatch.Float64(0.0021),
})
```

> [!TIP]
>
> For the complete list of span helper methods (RAG contexts, multimodal/binary content, metadata, GenAI semconv setters, evaluations and events), see the [Span section](https://langwatch.ai/docs/integration/go/reference.md#span) in the reference.

## Data Capture

Data capture controls whether span **input/output content** leaves the process. It is enforced at export time, so one setting governs every instrumentation (the provider middlewares and your manual spans alike). Span structure, metrics, metadata, models and identity are always kept; only the content attributes are stripped. The default, unconfigured, captures everything.

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

// Fixed mode for all spans across every instrumentation.
exporter, _ := langwatch.NewExporter(ctx, langwatch.WithDataCapture(langwatch.DataCaptureNone))

// Or decide per span from its type / name / attributes.
exporter, _ := langwatch.NewExporter(ctx, langwatch.WithDataCaptureFunc(
	func(c langwatch.DataCaptureContext) langwatch.DataCaptureMode {
		if c.SpanType == "tool" {
			return langwatch.DataCaptureNone // never capture tool I/O
		}
		return langwatch.DataCaptureAll
	},
))
```

The modes are `langwatch.DataCaptureAll`, `langwatch.DataCaptureInput`, `langwatch.DataCaptureOutput` and `langwatch.DataCaptureNone`. Each instrumentation also accepts `WithDataCapture(...)` to gate content at the source for a single client; the two compose.

> [!TIP]
>
> For per-instrumentation data-capture options and how they compose with the exporter, see the [Data Capture section](https://langwatch.ai/docs/integration/go/reference.md#data-capture) in the reference.

## The LangWatch API Client

Separate from tracing, `github.com/langwatch/langwatch/sdks/go/client` is a typed REST client for the LangWatch API: prompts, datasets, traces, annotations, events, evaluations, triggers, monitors, scenarios and projects. It lives in its own module so the generated code stays out of the core graph:

```go theme={null}
import "github.com/langwatch/langwatch/sdks/go/client"

lw, err := client.New(
	client.WithAPIKey("pat-lw-..."),
	client.WithProjectID("project_abc123"),
)

prompt, err := lw.Prompts.Get(ctx, "support-greeting", &client.GetPromptOptions{Tag: "production"})
```

It uses the same credentials as the exporter (`sk-lw-*` keys or `pat-lw-*` PATs with a project id).

> [!TIP]
>
> For the full service list, pagination, retries and typed error handling, see the [API Client section](https://langwatch.ai/docs/integration/go/reference.md#api-client) in the reference.

## Environment Variables

The SDK respects these environment variables for configuration:

| Variable | Description |
| - | - |
| `LANGWATCH_API_KEY` | **Required.** Your LangWatch API key. |
| `LANGWATCH_PROJECT_ID` | Your LangWatch project ID. **Required** when using a service API key or a Personal Access Token. |
| `LANGWATCH_ENDPOINT` | The LangWatch collector endpoint. Defaults to `https://app.langwatch.ai`. |

## Filtering Spans

The LangWatch SDK filters which spans are exported. Use filtering to reduce noise, exclude sensitive data, or focus on specific instrumentation.

### Using Preset Filters

The SDK includes convenient preset filters for common use cases:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

// NewDefaultExporter automatically excludes HTTP request spans.
exporter, err := langwatch.NewDefaultExporter(ctx)

// Or use NewExporter with explicit filters.
exporter, err := langwatch.NewExporter(ctx,
	langwatch.WithFilters(
		langwatch.ExcludeHTTPRequests(), // Exclude GET, POST, etc. spans.
		langwatch.LangWatchOnly(),       // Keep only LangWatch instrumentation.
	),
)
```

### Custom Filtering

Create custom filters using `Include()` or `Exclude()` with matching criteria:

```go theme={null}
import langwatch "github.com/langwatch/langwatch/sdks/go"

exporter, err := langwatch.NewExporter(ctx,
	langwatch.WithFilters(
		// Only include spans from specific scopes.
		langwatch.Include(langwatch.Criteria{
			ScopeName: []langwatch.Matcher{
				langwatch.StartsWith("github.com/langwatch/"),
				langwatch.StartsWith("my-app/"),
			},
		}),
		// Exclude database spans.
		langwatch.Exclude(langwatch.Criteria{
			SpanName: []langwatch.Matcher{
				langwatch.StartsWith("database."),
			},
		}),
	),
)
```

### Matcher Types

The SDK provides several matcher types:

| Matcher | Description |
| - | - |
| `Equals(s)` | Exact string match |
| `EqualsIgnoreCase(s)` | Case-insensitive exact match |
| `StartsWith(prefix)` | Prefix match |
| `StartsWithIgnoreCase(prefix)` | Case-insensitive prefix match |
| `MatchRegex(re)` | Regular expression match |
| `MustMatchRegex(pattern)` | Regex match (panics on invalid pattern) |

> [!NOTE]
>
> Filters use AND semantics when combined - a span must pass all filters to be exported. Within a
> `Criteria`, matchers use OR semantics - a span matches if it matches any of the matchers in the
> list.

> [!TIP]
>
> For detailed filter API documentation and more examples, see the [Filtering
> section](https://langwatch.ai/docs/integration/go/reference.md#filtering) in the API reference.

## Features

* 🔗 **OpenTelemetry integration** - Works with your existing OTel setup
* 🚀 **Multi-provider instrumentation** - OpenAI, Anthropic, Gemini/Vertex, Bedrock, Azure, Ollama, Genkit and more
* 📊 **Rich LLM telemetry** - Capture inputs, outputs, every token type, cost, and model information
* 🔍 **Specialized span types** - LLM, Chain, Tool, Agent, RAG, and more
* 🧵 **Thread support** - Group related LLM interactions together
* 🎛️ **Data capture controls** - Decide exactly what content leaves your process
* 🔄 **Streaming support** - Real-time reconstruction of streaming responses
* 🧰 **Typed REST client** - Read and write prompts, datasets, traces, evaluations and more

Since LangWatch is built on OpenTelemetry, it also supports any library or framework that integrates with OpenTelemetry.

## Troubleshooting

### Common Issues

**No traces appearing in LangWatch dashboard:**

* Verify your `LANGWATCH_API_KEY` is set correctly
* Check that you're calling the shutdown function to flush traces
* Ensure your application is making LLM API calls through an instrumented client

**Import errors:**

* Run `go mod tidy` to ensure all dependencies are properly resolved
* Verify you're using Go 1.25 or later
* Make sure the OpenAI client import path is `github.com/openai/openai-go/v3` (and `/v3/option`)

**OpenTelemetry configuration errors:**

* Check that the LangWatch endpoint URL is correct
* Verify your API key has the correct permissions

### Getting Help

If you're still having issues:

* Check the [API reference](https://langwatch.ai/docs/integration/go/reference.md) for detailed function documentation
* Review the [OpenTelemetry Go documentation](https://opentelemetry.io/docs/languages/go/)
* Join our [community support](https://docs.langwatch.ai/support) for additional help

## Next Steps

* Learn about specific provider integrations like [OpenAI](https://langwatch.ai/docs/integration/go/integrations/open-ai.md), [Anthropic](https://langwatch.ai/docs/integration/go/integrations/anthropic.md) and [Amazon Bedrock](https://langwatch.ai/docs/integration/go/integrations/amazon-bedrock.md)
* Check the [API reference](https://langwatch.ai/docs/integration/go/reference.md) for detailed documentation
* Explore [span types](https://langwatch.ai/docs/integration/go/reference.md#langwatch-span-types) for specialized LLM operations
* Read the [Data Capture](https://langwatch.ai/docs/integration/go/reference.md#data-capture) controls to manage what content leaves your process


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