BrowserStack AI Evals
Tracing

Auto-Instrumentation

Zero-config automatic tracing for LLM providers and frameworks.

Auto-Instrumentation

Auto-instrumentation patches supported LLM libraries at startup so every call is automatically captured as a trace — no manual trace() or generation() calls required.

Running inside an HTTP server? The incoming request is the scope of the trace — every auto-traced LLM call made while handling a request nests under that one request's trace. See Trace scope in HTTP apps.

Setup

Call Observe.init() before importing or creating any LLM provider clients. This patches supported libraries at startup so every call is automatically captured as a trace.

import { Observe } from '@browserstack/ai-sdk';

// MUST be called before importing any LLM provider SDKs
await Observe.init({
  publicKey: process.env.AISDK_PUBLIC_KEY,
  secretKey: process.env.AISDK_SECRET_KEY,
});

import OpenAI from 'openai';

const openai = new OpenAI();

// This call is automatically traced
const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello!' }],
});

Observe.init() must be called before importing any LLM provider SDKs (OpenAI, Anthropic, etc.). If you import the provider first, auto-tracing won't work — the libraries are patched at init time.

The in-file import '@browserstack/ai-sdk/instrument' and in-file Observe.init() are CommonJS-only — in pure ESM, preload with node --import @browserstack/ai-sdk/instrument <entry> instead. See ESM vs CommonJS.

Using Next.js? With auto-instrumentation, Next.js inlines the SDK and the provider packages it patches into the server bundle by default, so the module hooks never fire and traces silently go missing. You must externalize them via serverExternalPackages — see Next.js setup.

Credentials can also be read from environment variables automatically:

AISDK_PUBLIC_KEY=pk-...
AISDK_SECRET_KEY=sk-...
import { Observe } from '@browserstack/ai-sdk';

await Observe.init(); // reads keys from env

Call Observe.init() before importing or creating any LLM provider clients. This starts the global OpenTelemetry tracer and auto-patches all supported libraries so every call is automatically captured as a trace.

import os
from browserstack_ai_sdk import Observe

# MUST be called before importing any LLM provider SDKs
Observe.init(
    public_key=os.environ["AISDK_PUBLIC_KEY"],
    secret_key=os.environ["AISDK_SECRET_KEY"],
)

import openai

client = openai.OpenAI()

# This call is automatically traced
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
)

Observe.init() must be called before importing any LLM provider SDKs (OpenAI, Anthropic, etc.). If you import the provider first, auto-tracing won't work — the libraries are patched at init time.

Credentials can also be read from environment variables automatically:

AISDK_PUBLIC_KEY=pk-...
AISDK_SECRET_KEY=sk-...
from browserstack_ai_sdk import Observe

Observe.init()  # reads keys from env

Call sdk.observe().init() once at application startup, before any LLM clients are created. This installs ByteBuddy bytecode instrumentation at the JVM level.

import com.browserstack.aisdk.AISDK;

AISDK sdk = AISDK.fromEnv();
sdk.observe().init();  // Install ByteBuddy instrumentation — call once

init() is idempotent — calling it multiple times is safe.

Auto-tracing requires a JDK (not JRE). ByteBuddy needs access to tools.jar or the Java instrumentation API. Running on a JRE will log a warning and disable auto-tracing gracefully.

Model Providers

Select your language and provider:

Installation

Install the SDK along with your provider's package:

Model Provider
npm install @browserstack/ai-sdk openai

Usage

Import the instrumentation as the very first import in your entry point, then use your provider as normal. All calls are automatically traced.

Model Provider
// MUST be first — before any other imports
import '@browserstack/ai-sdk/instrument';

import OpenAI from 'openai';

const openai = new OpenAI(); // uses OPENAI_API_KEY env var

const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'What is the boiling point of water?' },
  ],
});

console.log(response.choices[0].message.content);
// A trace appears in the dashboard with model, messages, tokens, and response

Environment Variables

Model Provider
OPENAI_API_KEY=sk-...

Installation

Install the SDK along with your provider's package and OpenTelemetry instrumentation:

Model Provider
pip install https://static.testops.ai/sdk/python/browserstack_ai_sdk-latest.tar.gz openai opentelemetry-instrumentation-openai

Usage

Call Observe.init() before creating any LLM clients, then use your provider as normal. All calls are automatically captured as traces.

Model Provider
import os
from browserstack_ai_sdk import Observe

# MUST be called before importing any LLM provider SDKs
Observe.init(
    public_key=os.environ["AISDK_PUBLIC_KEY"],
    secret_key=os.environ["AISDK_SECRET_KEY"],
)

import openai
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the speed of light?"},
    ],
)

print(response.choices[0].message.content)

What is captured: model, messages, response content, token usage, finish reason, latency.

Environment Variables

Model Provider
OPENAI_API_KEY=...

AWS Bedrock auto-instrumentation for Python is coming soon.

Model Provider
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;
import com.browserstack.aisdk.AISDK;

AISDK sdk = AISDK.fromEnv();
sdk.observe().init();

OpenAIClient openai = OpenAIOkHttpClient.fromEnv();

ChatCompletion response = openai.chat().completions().create(
    ChatCompletionCreateParams.builder()
        .model("gpt-4o")
        .addMessage(ChatCompletionMessageParam.ofUser("What causes Northern Lights?"))
        .build()
);

System.out.println(response.choices().get(0).message().content());

sdk.flush();

Instruments ChatCompletionServiceImpl.create(). All chat completion calls are automatically traced as generations.

Vertex AI, Bedrock, Azure, and Google AI Studio auto-instrumentation for Java is coming soon.

Frameworks

Select your language to see available framework integrations:

Installation

Install the SDK along with your framework package:

Framework

No additional packages required — the model provider instrumentation handles tracing automatically.

Usage

Framework

No additional setup needed — the model provider instrumentation above handles tracing automatically.

Installation

Install the SDK along with your framework package and OpenTelemetry instrumentation:

Framework

No additional packages required — the model provider instrumentation handles tracing automatically.

Usage

Framework

No additional setup needed — the model provider instrumentation above handles tracing automatically.

Framework

No additional setup needed — the model provider instrumentation above handles tracing automatically.

Vercel AI SDK

TypeScript / Node.js only.

The Vercel AI SDK is traced by wrapping the ai module with wrapVercelAI() — pass the imported module through it once, then use the returned functions in place of the originals.

npm install @browserstack/ai-sdk ai @ai-sdk/openai
import * as aiModule from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { wrapVercelAI } from '@browserstack/ai-sdk';

const ai = wrapVercelAI(aiModule);
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

const { text } = await ai.generateText({
  model: openai('gpt-4o-mini'),
  prompt: 'In one sentence, what is the capital of France?',
});
console.log(text);
// A trace appears with the model, prompt, response, and token usage.

wrapVercelAI traces generateText / generateObject, the streaming streamText / streamObject, embeddings, and multi-step agent loops including tool calls. For a complete runnable example (streaming + flushing on exit) and the full function list, see the dedicated guide: Use with Vercel AI SDK.

Mixing Auto and Manual Tracing

You can combine auto-instrumentation with manual tracing. Create a trace and then make LLM calls — they are automatically attached to your active trace context.

import { Observe, AISDK } from '@browserstack/ai-sdk';

// MUST be called before importing any LLM provider SDKs
await Observe.init({
  publicKey: process.env.AISDK_PUBLIC_KEY,
  secretKey: process.env.AISDK_SECRET_KEY,
});

import OpenAI from 'openai';

const client = new AISDK();
const openai = new OpenAI();

const trace = client.trace({
  name: 'my-pipeline',
  input: { user: 'Alice' },
  tags: ['production'],
});

// This OpenAI call is auto-traced AND linked to the trace above
const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello, Alice!' }],
});

trace.update({ output: response.choices[0].message.content });
await client.shutdown();
import os
from browserstack_ai_sdk import Observe, AISDK

# MUST be called before importing any LLM provider SDKs
Observe.init(
    public_key=os.environ["AISDK_PUBLIC_KEY"],
    secret_key=os.environ["AISDK_SECRET_KEY"],
)

import openai

client = AISDK(
    public_key=os.environ["AISDK_PUBLIC_KEY"],
    secret_key=os.environ["AISDK_SECRET_KEY"],
)

openai_client = openai.OpenAI()

# Manual trace provides context and metadata
trace = client.trace(
    name="my-pipeline",
    user_id="alice",
    tags=["production"],
)

# This call is auto-traced and linked to the trace above
response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello, Alice!"}],
)

trace.update(output=response.choices[0].message.content)
client.flush()
AISDK sdk = AISDK.fromEnv();
sdk.observe().init();

TraceManager tm = sdk.traceManager();

// Create a parent trace with context
var trace = tm.trace(TraceBody.builder()
    .name("rag-pipeline")
    .input(question)
    .userId("user-42")
    .build());

// This span records retrieval — manual
var span = trace.span(SpanBody.builder().name("retrieval").input(question).build());
List<String> docs = retrieveDocuments(question);
span.end(SpanBody.builder().output(docs).build());

// This OpenAI call is auto-traced as a generation nested under the span
String answer = openai.chat().completions().create(params)
    .choices().get(0).message().content();

trace.update(TraceBody.builder().output(answer).build());
tm.flush();

Setting Trace Attributes

Use Observe.setAttribute (TypeScript) / Observe.set_attribute (Python) inside an auto-traced context to attach session ID, user ID, or custom attributes to the current trace. It requires an active span — so it works inside a withTrace block or while handling an auto-instrumented HTTP request. A bare Observe.init() on its own does not open a span, so setAttribute calls made outside any trace context are silently ignored.

Available Attributes

AttributeValueAcceptsDescription
TraceAttribute.SESSION_IDsession.idstringGroup related traces into a session.
TraceAttribute.USER_IDuser.idstringIdentify the user that triggered the trace.
TraceAttribute.TRACE_INPUTAIOPS_INTERNAL.trace.inputanySet trace input.
TraceAttribute.TRACE_OUTPUTAIOPS_INTERNAL.trace.outputanySet trace output.
import { Observe } from '@browserstack/ai-sdk';

Observe.setAttribute(Observe.TraceAttribute.SESSION_ID, 'session-abc');
Observe.setAttribute(Observe.TraceAttribute.USER_ID, 'user-42');
AttributeValueAcceptsDescription
TraceAttribute.SESSION_IDsession.idstrGroup related traces into a session.
TraceAttribute.USER_IDuser.idstrIdentify the user that triggered the trace.
TraceAttribute.INPUTAIOPS_INTERNAL.trace.inputanySet trace input.
TraceAttribute.OUTPUTAIOPS_INTERNAL.trace.outputanySet trace output.
from browserstack_ai_sdk import Observe

Observe.set_attribute(Observe.TraceAttribute.SESSION_ID, "session-abc")
Observe.set_attribute(Observe.TraceAttribute.USER_ID, "user-xyz")

Custom Attributes

You can also pass any custom key — it will appear in the trace metadata on the dashboard:

Observe.setAttribute('environment', 'production');
Observe.setAttribute('version', '2.1');
Observe.setAttribute('custom-data', { foo: 'bar' });
Observe.set_attribute("environment", "production")
Observe.set_attribute("version", "2.1")