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

# Gemini

> Trace Gemini SDK calls, run evals, and use Gemini models in Braintrust

[Google Gemini](https://ai.google.dev/gemini-api/docs) is Google's family of multimodal models, including Gemini 3.1 Pro and Gemini 3.6 Flash. Braintrust traces Gemini requests across text, streaming, structured outputs, function calling, and multimodal generation, lets you run experiments against Gemini models, and routes to them from the playground.

**TypeScript**

Trace Gemini calls from your TypeScript application, then run experiments against Gemini models.

## Tracing

Trace Gemini calls to Braintrust with the native Google GenAI SDK, or route them through the Braintrust gateway.

### Setup

Install the Braintrust and Google GenAI SDKs, then set your API keys.

**1. Install packages**

```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pnpm add braintrust @google/genai
```

```bash npm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
npm install braintrust @google/genai
```

**2. Set environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
GEMINI_API_KEY=<your-gemini-api-key>
BRAINTRUST_API_KEY=<your-braintrust-api-key>

# For organizations on the EU data plane, use https://api-eu.braintrust.dev
# For self-hosted deployments, use your data plane URL
# BRAINTRUST_API_URL=<your-braintrust-api-url-here>
```

### Auto-instrumentation

To trace Gemini calls without modifying your application code, run your app with Braintrust's import hook. The hook patches the Google GenAI SDK at startup, so calls from an unwrapped client are traced automatically.

```javascript title="trace-gemini-auto.js" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { GoogleGenAI } from "@google/genai";
import { initLogger } from "braintrust";

initLogger({
  projectName: "My Project", // Replace with your project name
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const client = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: "What is the capital of France?",
});
console.log(response.text);
```

Run your app with the import hook:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
node --import braintrust/hook.mjs trace-gemini-auto.js
```

The auto-instrumentation example uses plain JavaScript so `node --import` can run the file directly. The Braintrust APIs work the same in TypeScript projects. Compile your TypeScript to JavaScript and run the compiled file with the import hook, or [run TypeScript directly](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) with a TypeScript loader.

> [!NOTE]
>
> If you're using a bundler, see [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) for plugin and loader setup.

### Manual instrumentation

To trace Gemini calls manually, wrap the Google GenAI module yourself with `wrapGoogleGenAI`. Every call from the wrapped client is then logged to Braintrust.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Initialize Braintrust tracing
initLogger({ projectName: "My Project" });

// Use wrapGoogleGenAI to wrap the Google GenAI module for automatic tracing
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

// Create a native Google GenAI client
const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// All API calls are automatically logged
const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: "What is machine learning?",
  config: {
    maxOutputTokens: 100,
  },
});
console.log(response.text);
```

### Gateway

To route Gemini calls through the [Braintrust gateway](https://docs.braintrust.dev/deploy/gateway.md), point the OpenAI SDK at the gateway base URL with your Braintrust API key. Routing and logging happen server-side, so you can reach Gemini through any [supported provider's SDK](https://docs.braintrust.dev/integrations/ai-providers.md).

Install the `braintrust` and `openai` packages.

```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pnpm add braintrust openai
```

```bash npm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
npm install braintrust openai
```

Initialize the client and make a request to a Gemini model through the gateway.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { OpenAI } from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.braintrust.dev/v1",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const response = await client.chat.completions.create({
  model: "gemini-3.6-flash",
  messages: [{ role: "user", content: "Hello, world!" }],
});
```

#### Log to a project

To log gateway calls to a specific project, initialize a logger with `initLogger`.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { OpenAI } from "openai";
import { initLogger } from "braintrust";

initLogger({
  projectName: "My Project",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const client = new OpenAI({
  baseURL: "https://gateway.braintrust.dev/v1",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

// All API calls are automatically logged
const result = await client.chat.completions.create({
  model: "gemini-3.6-flash",
  messages: [{ role: "user", content: "What is machine learning?" }],
});
```

#### Streaming

Stream Gemini responses through the gateway.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
const stream = await client.chat.completions.create({
  model: "gemini-3.6-flash",
  messages: [{ role: "user", content: "Count to 10" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
```

#### Embeddings

The gateway also supports Gemini's native `embedContent` and `batchEmbedContents` endpoints, including multimodal text and image content. See [Generate embeddings](https://docs.braintrust.dev/deploy/gateway.md#generate-embeddings) for an example.

### Streaming

Stream responses from the native Google GenAI client with automatic tracing.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
const stream = await client.models.generateContentStream({
  model: "gemini-3.6-flash",
  contents: "Count from 1 to 10 slowly.",
  config: {
    maxOutputTokens: 200,
  },
});

// All streaming chunks are automatically logged
for await (const chunk of stream) {
  if (chunk.text) {
    process.stdout.write(chunk.text);
  }
}
```

The wrapper aggregates streamed chunks and records streaming token metrics.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Stream responses - automatically tracked
const stream = await client.models.generateContentStream({
  model: "gemini-3.6-flash",
  contents: "Write a story about a robot learning to paint.",
  config: {
    maxOutputTokens: 500,
  },
});

// Streaming automatically tracks:
// - time_to_first_token
// - prompt_tokens, completion_tokens, total_tokens
// - prompt_cached_tokens (if using caching)
for await (const chunk of stream) {
  if (chunk.text) {
    process.stdout.write(chunk.text);
  }
}
```

### Structured outputs

Gemini supports structured JSON outputs using response schemas.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Define a schema for the response
interface Person {
  name: string;
  age: number;
  occupation: string;
}

const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents:
    "Extract information about: John Smith is a 30-year-old software engineer.",
  config: {
    responseMimeType: "application/json",
    responseSchema: {
      type: "object",
      properties: {
        name: { type: "string" },
        age: { type: "number" },
        occupation: { type: "string" },
      },
      required: ["name", "age", "occupation"],
    },
    maxOutputTokens: 200,
  },
});

// Parse the JSON response
const personData: Person = JSON.parse(response.text);
console.log(`Name: ${personData.name}, Age: ${personData.age}`);
```

### Function calling

Gemini supports function calling for building AI agents with tools.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Define functions for the model to call
function getWeather(location: string, unit: string = "celsius"): string {
  // In a real app, this would call a weather API
  return `22 degrees ${unit} and sunny in ${location}`;
}

function searchWeb(query: string): string {
  return `Search results for: ${query}`;
}

// Define function declarations
const tools = [
  {
    functionDeclarations: [
      {
        name: "get_weather",
        description: "Get the current weather for a location",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "The city and state, e.g. San Francisco, CA",
            },
            unit: {
              type: "string",
              enum: ["celsius", "fahrenheit"],
              description: "The unit of temperature",
            },
          },
          required: ["location"],
        },
      },
      {
        name: "search_web",
        description: "Search the web for information",
        parameters: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description: "The search query",
            },
          },
          required: ["query"],
        },
      },
    ],
  },
];

// Generate with tools
const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents:
    "What's the weather in Paris and what tourist sites should I visit?",
  config: {
    tools: tools,
    maxOutputTokens: 500,
  },
});

// Handle function calls
if (response.candidates[0].content.parts) {
  for (const part of response.candidates[0].content.parts) {
    if (part.functionCall) {
      const fc = part.functionCall;
      console.log(`Function: ${fc.name}`);
      console.log(`Arguments: ${JSON.stringify(fc.args)}`);

      // Execute the function
      if (fc.name === "get_weather") {
        const result = getWeather(fc.args.location, fc.args.unit);
        // Send result back to model for final response
      }
    }
  }
}
```

### Multimodal content

Gemini models support multimodal inputs including images, audio, and video.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";
import * as fs from "fs";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Image analysis
const imageData = fs.readFileSync("image.jpg");

const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: [
    { text: "What's in this image?" },
    {
      inlineData: {
        mimeType: "image/jpeg",
        data: imageData.toString("base64"),
      },
    },
  ],
});

// Audio transcription
const audioData = fs.readFileSync("audio.mp3");

const audioResponse = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: [
    { text: "Transcribe this audio:" },
    {
      inlineData: {
        mimeType: "audio/mp3",
        data: audioData.toString("base64"),
      },
    },
  ],
});

// The wrapper automatically handles binary data serialization
// Binary attachments are converted to Braintrust Attachment objects
```

### Reasoning models

Gemini 3 models like `gemini-3.6-flash` and `gemini-3.1-pro` have built-in reasoning capabilities enabled by default. Configure reasoning behavior with `thinkingConfig`.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Use reasoning model - reasoning tokens are automatically tracked
const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: "What is the derivative of x^2 + 3x + 5? Think step by step.",
  config: {
    maxOutputTokens: 1000,
  },
});

// The response includes both the reasoning and final answer
console.log(response.text);

// Metrics automatically include reasoning tokens
// The wrapper captures completion_reasoning_tokens in the metrics
```

### Context caching

Gemini supports context caching for efficient reuse of large contexts.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

// Setup automatic tracing
initLogger({ projectName: "My Project" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

// Create a cache for a large document
const documentContent = "... very long document content ...";

// Note: Caching API requires the full Vertex AI SDK
// This example shows the structure - refer to Google's documentation
// for complete caching implementation

const response = await client.models.generateContent({
  model: "gemini-3.6-flash",
  contents: "Summarize the key points from the document",
  config: {
    // cachedContent would be configured here
    maxOutputTokens: 500,
  },
});

// The wrapper tracks cached tokens in metrics
// Look for prompt_cached_tokens in the logged metrics
```

### Error handling, attachments, and masking sensitive data

To learn more about these topics, check out the [customize traces](https://docs.braintrust.dev/instrument/advanced-tracing.md) guide.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { OpenAI } from "openai";
import "@braintrust/proxy/types"; // for type safety

const client = new OpenAI({
  baseURL: "https://gateway.braintrust.dev/v1",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const response = await client.chat.completions.create({
  model: "gemini-3.6-flash",
  reasoning_enabled: true,
  reasoning_budget: 1024,
  messages: [{ role: "user", content: "How many rs in 'ferrocarril'?" }],
});

console.log(response.choices[0].reasoning); // Access reasoning steps
```

> [!TIP]
>
> To learn more about multimodal support, attachments, error handling, and masking sensitive data with Gemini, visit the [customize traces](https://docs.braintrust.dev/instrument/advanced-tracing.md) guide.

### What Braintrust traces

Braintrust captures:

* Content generation spans (`generate_content` and `generate_content_stream`), with the request as input and the full response as output. Streaming adds time to first token.
* Embedding spans (`embed_content`), with embedding count and length and token usage. For `gemini-embedding-2` models, image inputs in multimodal embedding requests are uploaded as Braintrust [attachments](https://docs.braintrust.dev/instrument/attachments.md).
* Image generation spans (`generate_images`), with the prompt and generation parameters as input and the generated images as attachments.
* Image editing spans (`edit_image`), with the source image, mask, and prompt as input and the edited image as an attachment.
* Video generation spans (`generate_videos`), with the prompt, any source or reference images and video, and generation parameters as input, and any generated videos in the returned operation as output.
* Interaction spans (`create_interaction`) for foreground `interactions.create` calls, including streaming, with the interaction input, output, and usage.
* Token metrics: prompt, completion, total (`tokens`), cached (`prompt_cached_tokens`), reasoning (`completion_reasoning_tokens`), and audio and image modality tokens.
* Google Search grounding metadata on grounded responses.
* Binary inputs (images, audio, and documents) uploaded as Braintrust [attachments](https://docs.braintrust.dev/instrument/attachments.md).

### Tracing resources

* [Google GenAI SDK reference](https://googleapis.github.io/js-genai/) for the API surface being traced.
* [`braintrust` on npm](https://www.npmjs.com/package/braintrust) for the tracing package.
* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) for tracing patterns across providers.

## Evals

Evaluate the output of Gemini models by running experiments with Braintrust `Eval`. Each eval combines a dataset, a task, and one or more scorers. To learn more, see the [Experiments](https://docs.braintrust.dev/evaluate/run-evaluations.md) guide.

### Native SDK

Run experiments that call Gemini through the native Google GenAI SDK.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import * as googleGenAI from "@google/genai";
import { Eval, wrapGoogleGenAI, initLogger } from "braintrust";

// Setup tracing
initLogger({ projectName: "Gemini Evaluation" });
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

const client = new GoogleGenAI({
  apiKey: process.env.GEMINI_API_KEY || "",
});

Eval("Gemini Native Evaluation", {
  data: () => [
    { input: "What is 2+2?", expected: "4" },
    { input: "What is the capital of France?", expected: "Paris" },
  ],
  task: async (input) => {
    const response = await client.models.generateContent({
      model: "gemini-3.6-flash",
      contents: input,
      config: {
        maxOutputTokens: 100,
      },
    });
    return response.text;
  },
  scores: [
    {
      name: "accuracy",
      scorer: (args) => (args.output === args.expected ? 1 : 0),
    },
  ],
});
```

### Gateway

Run experiments that call Gemini through the Braintrust gateway.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { Eval } from "braintrust";
import { OpenAI } from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.braintrust.dev/v1",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

Eval("Gemini Evaluation", {
  data: () => [
    { input: "What is 2+2?", expected: "4" },
    { input: "What is the capital of France?", expected: "Paris" },
  ],
  task: async (input) => {
    const response = await client.chat.completions.create({
      model: "gemini-3.6-flash",
      messages: [{ role: "user", content: input }],
    });
    return response.choices[0].message.content;
  },
  scores: [
    {
      name: "accuracy",
      scorer: (args) => (args.output === args.expected ? 1 : 0),
    },
  ],
});
```

## Legacy SDK (`@google/generative-ai`)

The [`@google/generative-ai`](https://www.npmjs.com/package/@google/generative-ai) package (v0.24.x) is the older Google Generative AI SDK for TypeScript, separate from the newer `@google/genai` SDK documented above. Braintrust supports auto-instrumentation and manual instrumentation for this legacy SDK.

### Auto-instrumentation

Install the Braintrust and `@google/generative-ai` packages, then run your app with the import hook. Requires `@google/generative-ai` v0.24.0 or later.

**1. Install packages**

```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pnpm add braintrust @google/generative-ai
```

```bash npm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
npm install braintrust @google/generative-ai
```

**2. Initialize Braintrust and call the legacy SDK**

```javascript title="trace-legacy-gemini-auto.js" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { GoogleGenerativeAI } from "@google/generative-ai";
import { initLogger } from "braintrust";

initLogger({
  projectName: "gemini-example",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });

const result = await model.generateContent("What is the capital of France?");
console.log(result.response.text());
```

**3. Run with the import hook**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
node --import braintrust/hook.mjs trace-legacy-gemini-auto.js
```

The auto-instrumentation example uses plain JavaScript so `node --import` can run the file directly. The Braintrust APIs work the same in TypeScript projects. Compile your TypeScript to JavaScript and run the compiled file with the import hook, or [run TypeScript directly](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) with a TypeScript loader.

> [!NOTE]
>
> If you're using a bundler, see [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) for plugin and loader setup.

### Manual instrumentation

To trace legacy SDK clients manually, wrap the client with `wrapGoogleGenerativeAI()`.

```javascript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { GoogleGenerativeAI } from "@google/generative-ai";
import { initLogger, wrapGoogleGenerativeAI } from "braintrust";

initLogger({
  projectName: "gemini-example",
  apiKey: process.env.BRAINTRUST_API_KEY,
});

const client = wrapGoogleGenerativeAI(
  new GoogleGenerativeAI(process.env.GEMINI_API_KEY),
);

const model = client.getGenerativeModel({ model: "gemini-1.5-flash" });
const result = await model.generateContent("What is the capital of France?");
console.log(result.response.text());
```

### What Braintrust traces

Braintrust patches the `@google/generative-ai` SDK and creates a span per call:

* Content generation spans (`generate_content`, `generate_content_stream`): input contents, model, generation config, safety settings, and tools as metadata. Output includes response text, token usage (`prompt_tokens`, `completion_tokens`, `tokens`), and `time_to_first_token` on streaming calls.
* Embedding spans (`embed_content`, `batch_embed_contents`): input text and model as metadata.
* Chat spans (`send_message`, `send_message_stream`): message input and model as metadata. Streaming calls also capture `time_to_first_token`.
* Images handled as Braintrust attachments.
* Errors captured on every call.

**Python**

Trace Gemini calls from your Python application, then run experiments against Gemini models.

## Tracing

Trace Gemini calls to Braintrust with the native Google GenAI SDK, or route them through the Braintrust gateway.

### Setup

Install the Braintrust and Google GenAI SDKs, then set your API keys.

**1. Install packages**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pip install braintrust google-genai
```

**2. Set environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
GEMINI_API_KEY=<your-gemini-api-key>
BRAINTRUST_API_KEY=<your-braintrust-api-key>

# For organizations on the EU data plane, use https://api-eu.braintrust.dev
# For self-hosted deployments, use your data plane URL
# BRAINTRUST_API_URL=<your-braintrust-api-url-here>
```

### Auto-instrumentation

To trace Gemini calls without modifying your application code, call `braintrust.auto_instrument()` at startup. It patches the Google GenAI SDK so calls from an unwrapped client are traced automatically.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

import braintrust
from google.genai import Client

# Call once at startup — all Gemini calls are traced automatically
braintrust.auto_instrument()
braintrust.init_logger(
    api_key=os.environ["BRAINTRUST_API_KEY"],
    project="My Project",
)

client = Client(api_key=os.environ["GEMINI_API_KEY"])
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="What is the capital of France?",
)
print(response.text)
```

> [!NOTE]
>
> `braintrust.auto_instrument()` enables Google GenAI by default alongside every other supported Python integration. See [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) to disable specific integrations.

### Manual instrumentation

To trace Gemini calls manually, call `setup_genai` yourself to patch the Google GenAI client. Every subsequent call is then logged to Braintrust.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust.wrappers.google_genai import setup_genai
from google.genai import types
from google.genai.client import Client

# Use setup_genai to automatically trace all Google GenAI API calls
setup_genai(project_name="My Project")

# Create a native Google GenAI client
client = Client(api_key=os.environ["GEMINI_API_KEY"])

# All API calls are automatically logged
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="What is machine learning?",
    config=types.GenerateContentConfig(
        max_output_tokens=100,
    ),
)
print(response.text)
```

### Gateway

To route Gemini calls through the [Braintrust gateway](https://docs.braintrust.dev/deploy/gateway.md), point the OpenAI SDK at the gateway base URL with your Braintrust API key. Routing and logging happen server-side, so you can reach Gemini through any [supported provider's SDK](https://docs.braintrust.dev/integrations/ai-providers.md).

Install the `braintrust` and `openai` packages.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pip install braintrust openai
```

Initialize the client and make a request to a Gemini model through the gateway.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.braintrust.dev/v1",
    api_key=os.environ["BRAINTRUST_API_KEY"],
)

response = client.chat.completions.create(
    model="gemini-3.6-flash",
    messages=[{"role": "user", "content": "Hello, world!"}],
)
```

#### Log to a project

To log gateway calls to a specific project, initialize a logger with `init_logger`.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust import init_logger
from openai import OpenAI

init_logger(project="My Project")

client = OpenAI(
    base_url="https://gateway.braintrust.dev/v1",
    api_key=os.environ["BRAINTRUST_API_KEY"],
)

# All API calls are automatically logged
result = client.chat.completions.create(
    model="gemini-3.6-flash",
    messages=[{"role": "user", "content": "What is machine learning?"}],
)
```

#### Streaming

Stream Gemini responses through the gateway.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
stream = client.chat.completions.create(
    model="gemini-3.6-flash",
    messages=[{"role": "user", "content": "Count to 10"}],
    stream=True,
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
```

#### Embeddings

The gateway also supports Gemini's native `embedContent` and `batchEmbedContents` endpoints, including multimodal text and image content. See [Generate embeddings](https://docs.braintrust.dev/deploy/gateway.md#generate-embeddings) for an example.

### Streaming

Stream responses from the native Google GenAI client with automatic tracing.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
stream = client.models.generate_content_stream(
    model="gemini-3.6-flash",
    contents="Count from 1 to 10 slowly.",
    config=types.GenerateContentConfig(
        max_output_tokens=200,
    ),
)

# All streaming chunks are automatically logged
for chunk in stream:
    if chunk.text:
        print(chunk.text, end="")
```

The wrapper aggregates streamed chunks and records streaming token metrics, including time to first token. Async streaming is traced the same way.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Stream responses - automatically tracked
stream = client.models.generate_content_stream(
    model="gemini-3.6-flash",
    contents="Write a story about a robot learning to paint.",
    config=types.GenerateContentConfig(
        max_output_tokens=500,
    ),
)

# Streaming automatically tracks:
# - time_to_first_token
# - prompt_tokens, completion_tokens, total_tokens
# - prompt_cached_tokens (if using caching)
for chunk in stream:
    if chunk.text:
        print(chunk.text, end="")

# Async streaming is also supported
import asyncio

async def stream_async():
    stream = await client.aio.models.generate_content_stream(
        model="gemini-3.6-flash",
        contents="Count from 1 to 10 slowly.",
        config=types.GenerateContentConfig(
            max_output_tokens=200,
        ),
    )

    async for chunk in stream:
        if chunk.text:
            print(chunk.text, end="")

asyncio.run(stream_async())
```

### Structured outputs

Gemini supports structured JSON outputs using response schemas.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os
from typing import TypedDict

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Define a schema for the response
class Person(TypedDict):
    name: str
    age: int
    occupation: str

response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="Extract information about: John Smith is a 30-year-old software engineer.",
    config=types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema=Person,
        max_output_tokens=200,
    ),
)

# Parse the JSON response
import json

person_data = json.loads(response.text)
print(f"Name: {person_data['name']}, Age: {person_data['age']}")
```

### Function calling

Gemini supports function calling for building AI agents with tools.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Define functions for the model to call
def get_weather(location: str, unit: str = "celsius") -> str:
    """Get the current weather for a location.

    Args:
        location: The city and state, e.g. San Francisco, CA
        unit: The unit of temperature (celsius or fahrenheit)
    """
    # In a real app, this would call a weather API
    return f"22 degrees {unit} and sunny in {location}"

def search_web(query: str) -> str:
    """Search the web for information.

    Args:
        query: The search query
    """
    return f"Search results for: {query}"

# Generate with tools
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="What's the weather in Paris and what tourist sites should I visit?",
    config=types.GenerateContentConfig(
        tools=[get_weather, search_web],  # Pass functions as tools
        max_output_tokens=500,
    ),
)

# Handle function calls
if response.candidates[0].content.parts:
    for part in response.candidates[0].content.parts:
        if hasattr(part, "function_call"):
            fc = part.function_call
            print(f"Function: {fc.name}")
            print(f"Arguments: {fc.args}")

            # Execute the function
            if fc.name == "get_weather":
                result = get_weather(**fc.args)
                # Send result back to model for final response
```

### Multimodal content

Gemini models support multimodal inputs including images, audio, and video.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Image analysis
with open("image.jpg", "rb") as f:
    image_data = f.read()

response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents=["What's in this image?", types.Part.from_bytes(data=image_data, mime_type="image/jpeg")],
)

# Audio transcription
with open("audio.mp3", "rb") as f:
    audio_data = f.read()

response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents=["Transcribe this audio:", types.Part.from_bytes(data=audio_data, mime_type="audio/mp3")],
)

# The wrapper automatically handles binary data serialization
# Binary attachments are converted to Braintrust Attachment objects
```

### Reasoning models

Gemini 3 models like `gemini-3.6-flash` and `gemini-3.1-pro` have built-in reasoning capabilities enabled by default. Configure reasoning behavior with `thinkingConfig`.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Use reasoning model - reasoning tokens are automatically tracked
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="What is the derivative of x^2 + 3x + 5? Think step by step.",
    config=types.GenerateContentConfig(
        max_output_tokens=1000,
    ),
)

# The response includes both the reasoning and final answer
print(response.text)

# Metrics automatically include reasoning tokens
# The wrapper captures completion_reasoning_tokens in the metrics
```

### Context caching

Gemini supports context caching for efficient reuse of large contexts.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os
from datetime import timedelta

from braintrust.wrappers.google_genai import setup_genai
from google import genai
from google.genai import caching, types

# Setup automatic tracing
setup_genai(project_name="My Project")
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

# Create a cache for a large document
document_content = "... very long document content ..."

cache = caching.CachedContent.create(
    model="gemini-3.6-flash",
    contents=[document_content],
    ttl=timedelta(hours=1),
)

# Use the cache in subsequent requests
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="Summarize the key points from the document",
    config=types.GenerateContentConfig(
        cached_content=cache,
        max_output_tokens=500,
    ),
)

# The wrapper tracks cached tokens in metrics
# Look for prompt_cached_tokens in the logged metrics
```

### Error handling, attachments, and masking sensitive data

To learn more about these topics, check out the [customize traces](https://docs.braintrust.dev/instrument/advanced-tracing.md) guide.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.braintrust.dev/v1",
    api_key=os.environ["BRAINTRUST_API_KEY"],
)

response = client.chat.completions.create(
    model="gemini-3.6-flash",
    reasoning_enabled=True,
    reasoning_budget=1024,
    messages=[{"role": "user", "content": "How many rs in 'ferrocarril'?"}],
)

print(response.choices[0].reasoning)  # Access reasoning steps
```

> [!TIP]
>
> To learn more about multimodal support, attachments, error handling, and masking sensitive data with Gemini, visit the [customize traces](https://docs.braintrust.dev/instrument/advanced-tracing.md) guide.

### What Braintrust traces

Braintrust captures:

* Content generation spans (`generate_content`, sync and async), with the model, contents, and config as input and the full response as output.
* Streaming content spans (`generate_content_stream`), with the aggregated response and time to first token alongside the token metrics. The aggregated response keeps candidate metadata, such as the finish reason, safety ratings, and grounding and URL context metadata, from every chunk, and keeps streamed code execution parts (`executable_code` and `code_execution_result`) in the order they arrive.
* Embedding spans (`embed_content`), with embedding count and length, token usage, and billable character count.
* Image generation spans (`generate_images`), with generated-image metadata and attachments.
* Interaction spans (`interactions.create`, `interactions.get`, `interactions.cancel`, and `interactions.delete`, sync and async, including streaming), with interaction inputs, outputs, status, and usage. Metadata records the interaction's `status` and `continuation_token`.
* Tool spans created around interactions, with tool arguments, results, and tool metadata.
* Token metrics for content and interaction calls: prompt, completion, total (`tokens`), cached (`prompt_cached_tokens`), reasoning (`completion_reasoning_tokens`), and audio and image modality tokens.
* Google Search grounding metadata (web search queries, grounding chunks, and grounding supports) on grounded responses.
* Binary inputs (images, audio, and documents) uploaded as Braintrust [attachments](https://docs.braintrust.dev/instrument/attachments.md).

### Tracing resources

* [Google GenAI SDK reference](https://googleapis.github.io/python-genai/) for the API surface being traced.
* [`braintrust` on PyPI](https://pypi.org/project/braintrust/) for the tracing package.
* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) for tracing patterns across providers.

## Evals

Evaluate the output of Gemini models by running experiments with Braintrust `Eval`. Each eval combines a dataset, a task, and one or more scorers. To learn more, see the [Experiments](https://docs.braintrust.dev/evaluate/run-evaluations.md) guide.

### Native SDK

Run experiments that call Gemini through the native Google GenAI SDK.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust import Eval
from braintrust.wrappers.google_genai import setup_genai
from google.genai import types
from google.genai.client import Client

# Setup tracing
setup_genai(project_name="Gemini Evaluation")

client = Client(api_key=os.environ["GEMINI_API_KEY"])

def task(input):
    response = client.models.generate_content(
        model="gemini-3.6-flash",
        contents=input,
        config=types.GenerateContentConfig(
            max_output_tokens=100,
        ),
    )
    return response.text

def accuracy_scorer(output, expected, **kwargs):
    return 1 if output == expected else 0

Eval(
    "Gemini Native Evaluation",
    data=[
        {"input": "What is 2+2?", "expected": "4"},
        {"input": "What is the capital of France?", "expected": "Paris"},
    ],
    task=task,
    scores=[accuracy_scorer],
)
```

### Gateway

Run experiments that call Gemini through the Braintrust gateway.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

from braintrust import Eval
from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.braintrust.dev/v1",
    api_key=os.environ["BRAINTRUST_API_KEY"],
)

def task(input):
    response = client.chat.completions.create(
        model="gemini-3.6-flash",
        messages=[{"role": "user", "content": input}],
    )
    return response.choices[0].message.content

def accuracy_scorer(output, expected, **kwargs):
    return 1 if output == expected else 0

Eval(
    "Gemini Evaluation",
    data=[
        {"input": "What is 2+2?", "expected": "4"},
        {"input": "What is the capital of France?", "expected": "Paris"},
    ],
    task=task,
    scores=[accuracy_scorer],
)
```

**Go**

Trace Gemini calls from your Go application.

## Tracing

Trace Gemini calls made through the [Google GenAI Go SDK](https://pkg.go.dev/google.golang.org/genai), either by attaching a traced HTTP client manually or by applying it automatically at compile time with Orchestrion.

### Setup

Install the Braintrust Go SDK and the genai contrib alongside the Google GenAI SDK, then set 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/genai
go get google.golang.org/genai
```

**2. Set environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=<your-braintrust-api-key>
GEMINI_API_KEY=<your-gemini-api-key>
```

### Auto-instrumentation

To trace Gemini calls without modifying your application code, build your app with [Orchestrion](https://github.com/DataDog/orchestrion), a compile-time tool that rewrites every `genai.NewClient` call to attach Braintrust's traced HTTP client automatically.

**1. Install Orchestrion**

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

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

```go title="orchestrion.tool.go" 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/genai"
)
```

**3. Write your app**

Orchestrion instruments every `genai.NewClient` call at build time, so no HTTP client wiring is needed.

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

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

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"google.golang.org/genai"

"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("gemini-example"),
	braintrust.WithBlockingLogin(true),
)
if err != nil {
	log.Fatal(err)
}

ctx, span := otel.Tracer("gemini-example").Start(context.Background(), "trace-gemini")
defer span.End()

// Orchestrion rewrites genai.NewClient to attach Braintrust tracing at build time
client, err := genai.NewClient(ctx, &genai.ClientConfig{
	APIKey:  os.Getenv("GEMINI_API_KEY"),
	Backend: genai.BackendGeminiAPI,
})
if err != nil {
	log.Fatal(err)
}

resp, err := client.Models.GenerateContent(ctx, "gemini-3.6-flash",
	genai.Text("What is the capital of France?"), nil)
if err != nil {
	log.Fatal(err)
}

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

**4. Build with Orchestrion**

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

### Manual instrumentation

To trace Gemini calls manually, attach Braintrust's traced HTTP client yourself by passing `tracegenai.Client()` as the `HTTPClient` in your `genai.ClientConfig`. Every call from that client is then logged to Braintrust.

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

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

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/sdk/trace"
	"google.golang.org/genai"

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

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

	bt, err := braintrust.New(tp,
		braintrust.WithProject("gemini-example"),
		braintrust.WithBlockingLogin(true),
	)
	if err != nil {
		log.Fatal(err)
	}

	ctx, span := otel.Tracer("gemini-example").Start(context.Background(), "trace-gemini")
	defer span.End()

	// Attach Braintrust tracing through the genai HTTP client
	client, err := genai.NewClient(ctx, &genai.ClientConfig{
		HTTPClient: tracegenai.Client(),
		APIKey:     os.Getenv("GEMINI_API_KEY"),
		Backend:    genai.BackendGeminiAPI,
	})
	if err != nil {
		log.Fatal(err)
	}

	resp, err := client.Models.GenerateContent(ctx, "gemini-3.6-flash",
		genai.Text("What is the capital of France?"), nil)
	if err != nil {
		log.Fatal(err)
	}

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

### What Braintrust traces

Braintrust captures:

* Content generation spans (`generate_content`), for `GenerateContent` and `GenerateContentStream`, with the model, contents, and config as input and the response as output. Time to first token is recorded on both.
* Embedding spans (`embed_content`), for `EmbedContent` and `batchEmbedContents`, with a summary of embedding count and length.
* Request metadata: provider, model, system instruction, tools and tool config, safety settings, cached content, and generation parameters (temperature, top-p, top-k, candidate count, max output tokens, stop sequences, response MIME type and schema, and thinking config).
* Token metrics: prompt (`prompt_tokens`), completion (`completion_tokens`), total (`tokens`), cached (`prompt_cached_tokens`), and reasoning (`completion_reasoning_tokens`).

### Tracing resources

* [Google GenAI Go SDK](https://pkg.go.dev/google.golang.org/genai) for the API surface being traced.
* [`braintrust-sdk-go`](https://github.com/braintrustdata/braintrust-sdk-go) for the tracing package.
* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) for tracing patterns across providers.

**Java**

Trace Gemini calls from your Java application.

## Tracing

Trace Gemini calls made through the native Google GenAI SDK, either by wrapping the client manually or by attaching the Braintrust Java agent at startup.

### Setup

Add the Braintrust and Google GenAI dependencies, then set your API keys.

**1. Add dependencies**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# add to build.gradle dependencies{} block
implementation 'dev.braintrust:braintrust-sdk-java:<version-goes-here>'
implementation 'com.google.genai:google-genai:1.20.0'
```

**2. Set environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
GEMINI_API_KEY=<your-gemini-api-key>
BRAINTRUST_API_KEY=<your-braintrust-api-key>

# For organizations on the EU data plane, use https://api-eu.braintrust.dev
# For self-hosted deployments, use your data plane URL
# BRAINTRUST_API_URL=<your-braintrust-api-url-here>
```

### Auto-instrumentation

To trace Gemini calls without modifying your application code, attach the [Braintrust Java agent](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) at JVM startup. The agent intercepts every Google GenAI client build and wraps it automatically, so no call to `BraintrustGenAI.wrap` is required.

**1. Add the agent dependency**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# build.gradle
configurations {
    braintrustAgent
}

dependencies {
    braintrustAgent 'dev.braintrust:braintrust-java-agent:+'
}

tasks.withType(JavaExec).configureEach {
    jvmArgs "-javaagent:${configurations.braintrustAgent.asPath}"
}
```

**2. Run your app**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
./gradlew run
```

Google GenAI clients built with `Client.builder()` in your application code are now instrumented automatically. The agent configures OpenTelemetry and the Braintrust exporter itself, so you only need to set `BRAINTRUST_API_KEY`.

> [!NOTE]
>
> Auto-instrumentation requires `com.google.genai:google-genai` version 1.18.0 or later.

### Manual instrumentation

To trace Gemini calls manually, wrap the Google GenAI client yourself with `BraintrustGenAI.wrap`. Every call from the wrapped client is then logged to Braintrust.

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import dev.braintrust.Braintrust;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;

class GeminiExample {
    public static void main(String[] args) {
        // Initialize Braintrust and create OpenTelemetry instance
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();

        // Wrap the Google GenAI client for automatic tracing
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        // All API calls are automatically logged
        GenerateContentConfig config = GenerateContentConfig.builder()
            .maxOutputTokens(100)
            .build();

        var response = client.models.generateContent(
            "gemini-3.6-flash",
            "What is machine learning?",
            config
        );
        System.out.println(response.text());
    }
}
```

### Streaming

Stream responses from the native Google GenAI client.

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import dev.braintrust.Braintrust;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;

class GeminiStreamingExample {
    public static void main(String[] args) {
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        GenerateContentConfig config = GenerateContentConfig.builder()
            .maxOutputTokens(200)
            .build();

        var stream = client.models.generateContentStream(
            "gemini-3.6-flash",
            "Count from 1 to 10 slowly.",
            config
        );

        // All streaming chunks are automatically logged
        for (var chunk : stream) {
            String text = chunk.text();
            if (text != null && !text.isEmpty()) {
                System.out.print(text);
            }
        }
    }
}
```

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import dev.braintrust.Braintrust;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;

class GeminiStreamingMetricsExample {
    public static void main(String[] args) {
        // Setup automatic tracing
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        // Stream responses - automatically tracked
        GenerateContentConfig config = GenerateContentConfig.builder()
            .maxOutputTokens(500)
            .build();

        var stream = client.models.generateContentStream(
            "gemini-3.6-flash",
            "Write a story about a robot learning to paint.",
            config
        );

        // Streaming automatically tracks:
        // - time_to_first_token
        // - prompt_tokens, completion_tokens, total_tokens
        // - prompt_cached_tokens (if using caching)
        for (var chunk : stream) {
            if (chunk.text() != null) {
                System.out.print(chunk.text());
            }
        }
    }
}
```

### Reasoning models

Gemini 3 models like `gemini-3.6-flash` and `gemini-3.1-pro` have built-in reasoning capabilities enabled by default.

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import dev.braintrust.Braintrust;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;

class GeminiReasoningExample {
    public static void main(String[] args) {
        // Setup automatic tracing
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        // Use reasoning model - reasoning tokens are automatically tracked
        GenerateContentConfig config = GenerateContentConfig.builder()
            .maxOutputTokens(1000)
            .build();

        var response = client.models.generateContent(
            "gemini-3.6-flash",
            "What is the derivative of x^2 + 3x + 5? Think step by step.",
            config
        );

        // The response includes both the reasoning and final answer
        System.out.println(response.text());

        // Metrics automatically include reasoning tokens
        // The wrapper captures completion_reasoning_tokens in the metrics
    }
}
```

### Context caching

Gemini supports context caching for efficient reuse of large contexts.

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import com.google.genai.types.GenerateContentConfig;
import dev.braintrust.Braintrust;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;

class GeminiCachingExample {
    public static void main(String[] args) {
        // Setup automatic tracing
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        // Create a cache for a large document
        String documentContent = "... very long document content ...";

        // Note: Caching API requires the full Vertex AI SDK
        // This example shows the structure - refer to Google's documentation
        // for complete caching implementation

        GenerateContentConfig config = GenerateContentConfig.builder()
            // cachedContent would be configured here
            .maxOutputTokens(500)
            .build();

        var response = client.models.generateContent(
            "gemini-3.6-flash",
            "Summarize the key points from the document",
            config
        );

        // The wrapper tracks cached tokens in metrics
        // Look for prompt_cached_tokens in the logged metrics
    }
}
```

### Use with Spring AI

For Java applications using [Spring AI](https://spring.io/projects/spring-ai), integrate Braintrust by wrapping the underlying Google GenAI client and passing it to Spring AI's `GoogleGenAiChatModel`.

```java theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import com.google.genai.Client;
import dev.braintrust.Braintrust;
import dev.braintrust.config.BraintrustConfig;
import dev.braintrust.instrumentation.genai.BraintrustGenAI;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;

// Spring AI imports (requires spring-ai-google-genai dependency)
// import org.springframework.ai.chat.model.ChatModel;
// import org.springframework.ai.chat.prompt.Prompt;
// import org.springframework.ai.google.genai.GoogleGenAiChatModel;
// import org.springframework.ai.google.genai.GoogleGenAiChatOptions;
// import org.springframework.boot.CommandLineRunner;
// import org.springframework.boot.SpringApplication;
// import org.springframework.boot.autoconfigure.SpringBootApplication;
// import org.springframework.context.annotation.Bean;

// @SpringBootApplication
class SpringAIExample {

    public static void main(String[] args) {
        // SpringApplication.run(SpringAIExample.class, args);

        // Key pattern for Spring AI integration:
        // 1. Initialize Braintrust
        Braintrust braintrust = Braintrust.get(BraintrustConfig.fromEnvironment());
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();

        // 2. Wrap the Google GenAI client with Braintrust
        Client genAIClient = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        // 3. Pass the wrapped client to Spring AI's GoogleGenAiChatModel
        // ChatModel chatModel = GoogleGenAiChatModel.builder()
        //     .genAIClient(genAIClient)
        //     .defaultOptions(
        //         GoogleGenAiChatOptions.builder()
        //             .model("gemini-3.6-flash")
        //             .temperature(0.0)
        //             .maxOutputTokens(50)
        //             .build())
        //     .build();

        // 4. Use the ChatModel in your Spring application
        // All calls through ChatModel are automatically traced to Braintrust
    }
}
```

This pattern works with all Spring AI features including streaming, function calling, and structured outputs. All calls through the `ChatModel` are automatically traced to Braintrust.

### What Braintrust traces

Braintrust captures:

* Content generation spans (`generate_content`, sync and async), with the model, contents, and generation config as input and the full response as output.
* Request metadata: provider (`"google"`), model, system instruction, tools and tool config, safety settings, cached content, and generation parameters (temperature, top-p, top-k, candidate count, max output tokens, stop sequences, response MIME type, and response schema).
* Token metrics: prompt (`prompt_tokens`), completion (`completion_tokens`), total (`tokens`), and cached (`prompt_cached_tokens`).

### Tracing resources

* [google-genai for Java](https://github.com/googleapis/java-genai) for the API surface being traced.
* [`braintrust-sdk-java`](https://github.com/braintrustdata/braintrust-sdk-java) for the tracing package.
* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) for tracing patterns across providers.

## Add Gemini as an AI provider

Add your Gemini API key to Braintrust to route requests through the gateway and use Gemini models from the playground.

**1. Get a Gemini API key**

Get a Gemini API key from [Google AI Studio](https://aistudio.google.com/app/apikey).

**2. Add the key to Braintrust**

Go to \*\* Settings\*\* > [\*\* Models\*\*](https://www.braintrust.dev/app/~/configuration/models/organization) and add the Gemini API key as an organization or project [AI provider](https://docs.braintrust.dev/admin/ai-providers.md).

> [!NOTE]
>
> API keys are stored as one-way cryptographic hashes, never in plaintext.

### Gemini provider resources

* [AI providers](https://docs.braintrust.dev/admin/ai-providers.md) for configuring provider keys and scopes.
* [Playgrounds](https://docs.braintrust.dev/evaluate/playgrounds.md) for comparing Gemini models against other providers.
* [Google AI Studio](https://aistudio.google.com/) for managing Gemini API keys.


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