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.

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.

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.

import os
from browserstack_ai_sdk import Observe

Observe.init(
    public_key=os.environ["AISDK_PUBLIC_KEY"],
    secret_key=os.environ["AISDK_SECRET_KEY"],
)

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.TestOps;

TestOps sdk = TestOps.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 browserstack-ai-sdk 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.TestOps;

TestOps sdk = TestOps.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.

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()
TestOps sdk = TestOps.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. This requires an active span — it works with Observe.init(), withTrace, or any auto-instrumented HTTP request.

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")