Skip to main content
Google’s Gemini models include Gemini 2.0 Flash, Gemini 2.5 Pro, and other advanced multimodal language models. Braintrust integrates seamlessly with Gemini through direct API access, wrapper functions for automatic tracing, and proxy support.
This guide covers manual instrumentation. For quicker setup, use auto-instrumentation.

Setup

To use Gemini models, configure your Gemini API key in Braintrust.
  1. Get a Gemini API key from Google AI Studio
  2. Add the Gemini API key as an organization or project AI provider.
  3. Set the Gemini API key and your Braintrust API key as environment variables
.env
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>
API keys are stored as one-way cryptographic hashes, never in plaintext.

Trace with Gemini

Trace your Gemini LLM calls for observability and monitoring using either the native Google GenAI SDK or the Braintrust gateway.

Trace automatically with native Google GenAI SDK

Braintrust provides wrapper functions that automatically log Google GenAI API calls. All subsequent API calls will be automatically traced. Braintrust traces these native Google GenAI SDK operations:
OperationNotes
generate_content / generateContent (and streaming variants)Text and multimodal generation, including streaming metrics. Available in Python and TypeScript.
embed_content / embedContentText embedding requests. Python supports async embedding helpers as well.
Grounding metadataWhen grounding is enabled (e.g. Google Search grounding), citations, search queries, and web attribution are captured on the span output and metadata. Available in Python and TypeScript.
generate_images and async generate_imagesImage generation calls, with generated image output captured in the span output. Python only.
interactions and Live API methodsinteractions.create (foreground calls), including streaming, in Python and TypeScript. interactions.get/delete and client.aio.live.connect() sessions, including async streaming and tool calls, are Python only.
These wrapper functions are convenience functions that integrate the Braintrust logger with the Google GenAI client. For more control, see the manual wrapping section below.
Install the required packages:
# pnpm
pnpm add braintrust @google/genai
# npm
npm install braintrust @google/genai
pip install braintrust google-genai
# add to build.gradle dependencies{} block
implementation 'dev.braintrust:braintrust-sdk-java:<version-goes-here>'
implementation 'com.google.genai:google-genai:1.20.0'
Then wrap the Google GenAI client:
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-2.5-flash",
  contents: "What is machine learning?",
  config: {
    maxOutputTokens: 100,
  },
});
console.log(response.text);
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-2.5-flash",
    contents="What is machine learning?",
    config=types.GenerateContentConfig(
        max_output_tokens=100,
    ),
)
print(response.text)
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-2.5-flash",
            "What is machine learning?",
            config
        );
        System.out.println(response.text());
    }
}

Stream responses with native Google GenAI SDK

The native Google GenAI client supports streaming with automatic tracing of token metrics.
const stream = await client.models.generateContentStream({
  model: "gemini-2.5-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);
  }
}
stream = client.models.generate_content_stream(
    model="gemini-2.5-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="")
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-2.5-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);
            }
        }
    }
}

Trace Live API and interactions

Braintrust traces the Google GenAI interactions API in both Python and TypeScript. In Python, with google-genai v1.30.0 or later, Braintrust also traces the Live API exposed through client.aio.live.connect() and automatically captures:
  • Async Live API round-trips
  • Async streaming responses inside Live API sessions
  • Tool calls made during Live API sessions
  • interactions.create(...), interactions.get(...), and interactions.delete(...)
In TypeScript, with @google/genai v1.33.0 or later, Braintrust traces foreground client.interactions.create() calls, including streaming responses. Background (fire-and-forget) calls are not traced. No additional setup is required beyond the normal Google GenAI wrapper or auto-instrumentation.

Manual wrapping for more control

If you need more control over when tracing is enabled, you can manually wrap the client.
import * as googleGenAI from "@google/genai";
import { wrapGoogleGenAI, initLogger } from "braintrust";

initLogger({ projectName: "My Project" });

// Wrap only when needed
const { GoogleGenAI } = wrapGoogleGenAI(googleGenAI);

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

const response = await client.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "Hello, world!",
});

console.log(response.text);
import os

from braintrust import init_logger
from braintrust.wrappers.google_genai import wrap_async_models, wrap_client, wrap_models
from google.genai import Client as BaseClient

init_logger(project="My Project")


@wrap_client
class Client(BaseClient):
    @property
    def models(self):
        return wrap_models(super().models)

    @property
    def aio(self):
        @wrap_client
        class AsyncClient:
            def __init__(self, parent):
                self._parent = parent._aio

            @property
            def models(self):
                return wrap_async_models(self._parent.models)

        return AsyncClient(super())


client = Client(api_key=os.environ["GEMINI_API_KEY"])
response = client.models.generate_content(
    model="gemini-1.5-flash",
    contents="Hello, world!",
)

# Async operations are also traced
import asyncio


async def generate_async():
    response = await client.aio.models.generate_content(
        model="gemini-1.5-flash", contents="Write a haiku about coding"
    )
    return response


# Run async generation
result = asyncio.run(generate_async())
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 GeminiManualWrappingExample {
    public static void main(String[] args) {
        Braintrust braintrust = Braintrust.get();
        OpenTelemetry openTelemetry = braintrust.openTelemetryCreate();

        // Wrap the client when you need tracing enabled
        Client client = BraintrustGenAI.wrap(openTelemetry, new Client.Builder());

        GenerateContentConfig config = GenerateContentConfig.builder()
            .temperature(0.0f)
            .build();

        var response = client.models.generateContent(
            "gemini-1.5-flash",
            "Hello, world!",
            config
        );

        System.out.println(response.text());
    }
}

Use Gemini with Braintrust gateway

The Braintrust gateway allows you to access Gemini models through a unified interface. Use any supported provider’s SDK to call Gemini models. Install the braintrust and openai packages.
# pnpm
pnpm add braintrust openai
# npm
npm install braintrust openai
pip install braintrust openai
Then, initialize the client and make a request to a Gemini model via the Braintrust gateway.
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-2.5-flash",
  messages: [{ role: "user", content: "Hello, world!" }],
});
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-2.5-flash",
    messages=[{"role": "user", "content": "Hello, world!"}],
)

Trace gateway calls

When using the Braintrust gateway, API calls are automatically logged to the specified project.
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-2.5-flash",
  messages: [{ role: "user", content: "What is machine learning?" }],
});
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-2.5-flash",
    messages=[{"role": "user", "content": "What is machine learning?"}],
)

Stream with proxy

Gemini models support streaming through the proxy.
const stream = await client.chat.completions.create({
  model: "gemini-2.5-flash",
  messages: [{ role: "user", content: "Count to 10" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
stream = client.chat.completions.create(
    model="gemini-2.5-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="")

Generate embeddings

The gateway also supports Gemini’s native embedContent and batchEmbedContents endpoints, including multimodal text and image content. See Generate embeddings for an example.

Evaluate with Gemini

Evaluations distill the non-deterministic outputs of Gemini models into an effective feedback loop that enables you to ship more reliable, higher quality products. Braintrust Eval is a simple function composed of a dataset of user inputs, a task, and a set of scorers. To learn more about evaluations, see the Experiments guide.

Evaluate with native SDK

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-2.5-flash",
      contents: input,
      config: {
        maxOutputTokens: 100,
      },
    });
    return response.text;
  },
  scores: [
    {
      name: "accuracy",
      scorer: (args) => (args.output === args.expected ? 1 : 0),
    },
  ],
});
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-2.5-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],
)

Evaluate with proxy

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-2.5-flash",
      messages: [{ role: "user", content: input }],
    });
    return response.choices[0].message.content;
  },
  scores: [
    {
      name: "accuracy",
      scorer: (args) => (args.output === args.expected ? 1 : 0),
    },
  ],
});
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-2.5-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],
)

Additional features

Reasoning models

Gemini 2.5 models (gemini-2.5-flash, gemini-2.5-pro) have built-in reasoning capabilities enabled by default. You can configure reasoning behavior using thinkingConfig.

Native SDK

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-2.5-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
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-2.5-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

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-2.5-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
    }
}

Structured outputs

Gemini supports structured JSON outputs using response schemas.
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-1.5-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}`);
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-1.5-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 and tools

Gemini supports function calling for building AI agents with tools.
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-1.5-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
      }
    }
  }
}
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-1.5-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.
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-1.5-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-1.5-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
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-1.5-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-1.5-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

Streaming with token metrics

Stream responses with automatic token tracking.
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-1.5-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);
  }
}
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-1.5-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-1.5-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())
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-1.5-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());
            }
        }
    }
}

Context caching

Gemini supports context caching for efficient reuse of large contexts.
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-1.5-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
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-1.5-flash",
    contents=[document_content],
    ttl=timedelta(hours=1),
)

# Use the cache in subsequent requests
response = client.models.generate_content(
    model="gemini-1.5-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
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-1.5-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, you can integrate Braintrust by wrapping the underlying Google GenAI client and passing it to Spring AI’s GoogleGenAiChatModel.
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-2.0-flash-lite")
        //             .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.

Error handling, attachments, and masking sensitive data

To learn more about these topics, check out the customize traces guide.
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-2.5-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
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-2.5-flash",
    reasoning_enabled=True,
    reasoning_budget=1024,
    messages=[{"role": "user", "content": "How many rs in 'ferrocarril'?"}],
)

print(response.choices[0].reasoning)  # Access reasoning steps
To learn more about multimodal support, attachments, error handling, and masking sensitive data with Gemini, visit the customize traces guide.