Setup & Installation
Install the BrowserStack AI Evals SDK and initialize the client for TypeScript, Python, or Java.
Setup & Installation
Installation
npm install @browserstack/ai-sdkpip install browserstack-ai-sdkGradle:
implementation 'com.browserstack:aisdk:LATEST_VERSION'Maven:
<dependency>
<groupId>com.browserstack</groupId>
<artifactId>aisdk</artifactId>
<version>LATEST_VERSION</version>
</dependency>Client Initialization
The SDK provides three initialization paths. Pick the one that matches your use case:
1. Auto-instrumentation (simplest)
Import the instrument bundle as the first import in your entry file. This patches supported LLM libraries (OpenAI, Anthropic, etc.) at startup and reads credentials from env vars.
// Must be the very first import — before any LLM provider imports
import '@browserstack/ai-sdk/instrument';
import OpenAI from 'openai';
const openai = new OpenAI();
// All OpenAI calls are now auto-traced2. Observe.init() — global init with control
Use when you want explicit control over when tracing starts, or need to call Observe.setAttribute() / Observe.getClient() from anywhere in your code.
import { Observe } from '@browserstack/ai-sdk';
await Observe.init({
publicKey: process.env.AISDK_PUBLIC_KEY,
secretKey: process.env.AISDK_SECRET_KEY,
});3. new AISDK() — explicit client
Use when you need a client instance to call CRUD methods (datasets, prompts, experiments) or manual tracing (client.trace(...), client.score(...)).
import { AISDK } from '@browserstack/ai-sdk';
const client = new AISDK({
publicKey: process.env.AISDK_PUBLIC_KEY,
secretKey: process.env.AISDK_SECRET_KEY,
});These paths are not mutually exclusive. A common setup is auto-instrument at the entry file, then new AISDK() wherever you need CRUD or manual tracing.
The SDK provides two initialization paths that work together:
Observe.init()— sets up global auto-instrumentation. Patches OpenAI, Anthropic, etc. at import time.AISDK()— client instance for CRUD APIs (datasets, prompts, experiments) and manual tracing.
Most applications use both: Observe.init() at startup for auto-tracing, plus AISDK() when you need to call CRUD methods.
import os
from browserstack_ai_sdk import Observe
Observe.init(
public_key=os.environ["AISDK_PUBLIC_KEY"],
secret_key=os.environ["AISDK_SECRET_KEY"],
)Create a client for CRUD operations and manual tracing:
import os
from browserstack_ai_sdk import AISDK
client = AISDK(
public_key=os.environ["AISDK_PUBLIC_KEY"],
secret_key=os.environ["AISDK_SECRET_KEY"],
)TestOps is the main entry point. All clients are lazily initialized on first access.
import com.browserstack.aisdk.TestOps;
// From environment variables (recommended)
TestOps sdk = TestOps.fromEnv();
// Or using the builder pattern
TestOps sdk = TestOps.builder()
.publicKey(System.getenv("AISDK_PUBLIC_KEY"))
.secretKey(System.getenv("AISDK_SECRET_KEY"))
.build();The Observe Namespace
Beyond init(), the Observe namespace exposes helpers for interacting with the current trace.
| Method | Description |
|---|---|
Observe.init(config?) | Initialize global tracing. Returns Promise<void>. |
Observe.getClient(config?) | Get the singleton AISDK instance created by init(). |
Observe.setAttribute(key, value) | Attach an attribute to the current trace. |
Observe.TraceAttribute | Enum of standard attribute keys: SESSION_ID, USER_ID, TRACE_INPUT, TRACE_OUTPUT. |
Observe.withTrace(name, baggage, fn) | Run fn inside a named trace, optionally propagating baggage. |
Observe.getBaggage() / Observe.setBaggage(str) | Read/write OpenTelemetry baggage for cross-service context propagation. |
Observe.trace / Observe.context / Observe.propagation | Raw OpenTelemetry APIs for advanced use cases. |
Example — attach a session ID to the current trace:
import { Observe } from '@browserstack/ai-sdk';
Observe.setAttribute(Observe.TraceAttribute.SESSION_ID, 'session-abc-123');
Observe.setAttribute(Observe.TraceAttribute.USER_ID, 'user-42');| Method | Description |
|---|---|
Observe.init(**kwargs) | Initialize global tracing. Returns Optional[TestOpsTracing]. |
Observe.shutdown() | Shut down tracing and flush pending spans. |
Observe.is_initialized() | Returns True if init() has been called. |
Observe.set_attribute(key, value) | Attach an attribute to the current trace. |
Observe.TraceAttribute | Enum of standard attribute keys: SESSION_ID, USER_ID, etc. |
Example — attach a session ID to the current trace:
from browserstack_ai_sdk import Observe
Observe.set_attribute(Observe.TraceAttribute.SESSION_ID, "session-abc-123")
Observe.set_attribute(Observe.TraceAttribute.USER_ID, "user-42")Environment Variables
| Variable | Description |
|---|---|
AISDK_PUBLIC_KEY | API public key (preferred) |
AISDK_SECRET_KEY | API secret key (preferred) |
AISDK_WRITE_ONLY_KEY | Write-only key for tracing-only access |
AISDK_REST_API_URL | REST API endpoint override |
AISDK_INGESTION_URL | Trace ingestion endpoint override |
Set them in your .env file and initialize with no explicit credentials:
AISDK_PUBLIC_KEY=pk-...
AISDK_SECRET_KEY=sk-...import { AISDK } from '@browserstack/ai-sdk';
const client = new AISDK(); // reads from env vars| Variable | Description |
|---|---|
AISDK_PUBLIC_KEY | API public key |
AISDK_SECRET_KEY | API secret key |
AISDK_WRITE_ONLY_KEY | Write-only key for tracing-only access |
AISDK_REST_API_URL | REST API endpoint override |
AISDK_INGESTION_URL | Trace ingestion endpoint override |
Call Observe.init() with no arguments to read from environment:
AISDK_PUBLIC_KEY=pk-...
AISDK_SECRET_KEY=sk-...from browserstack_ai_sdk import Observe
Observe.init() # reads from environmentThe SDK reads credentials in this resolution order:
- Values set explicitly via
Builder AISDK_*environment variablesTESTOPS_*environment variables (legacy fallback)- Hardcoded URL defaults
| Variable | Purpose | Required |
|---|---|---|
AISDK_PUBLIC_KEY | API public key | Yes |
AISDK_SECRET_KEY | API secret key | Yes |
AISDK_REST_API_URL | REST API endpoint | No |
AISDK_INGESTION_URL | Trace ingestion endpoint | No |
AISDK_WEB_URL | Web app URL | No |
The SDK also auto-loads a .env file from the current or parent directories. System environment variables always take precedence.
Constructor Options
new AISDK(config?: TestOpsConfig)| Option | Type | Default | Description |
|---|---|---|---|
publicKey | string | AISDK_PUBLIC_KEY env | Your API public key. |
secretKey | string | AISDK_SECRET_KEY env | Your API secret key. |
writeOnlyKey | string | AISDK_WRITE_ONLY_KEY env | Write-only key for tracing-only access (no read APIs). |
enabled | boolean | true | Enable or disable the SDK. |
environment | string | — | Environment name attached to all traces (e.g. "production"). |
fileLog | boolean | false | Enable file-based logging for debugging. |
family | 4 | 6 | — | Force IPv4 or IPv6 connections. |
agent | TestOpsAgent | — | Custom http.Agent / https.Agent for connection pooling. |
flushInterval | number | SDK default | Interval in ms between auto-flushes. |
maxBatchSize | number | SDK default | Maximum number of events per flush batch. |
baseUrl is not a constructor option — to override the API endpoint, use the AISDK_REST_API_URL environment variable.
Observe.init() parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
public_key | str | AISDK_PUBLIC_KEY env | Your public API key. |
secret_key | str | AISDK_SECRET_KEY env | Your secret API key. |
write_only_key | str | AISDK_WRITE_ONLY_KEY env | Write-only key for tracing-only access. |
host | str | — | Ingestion host override. |
environment | str | — | Environment name attached to all traces. |
service_name | str | "default" | Service name attached to spans. |
service_version | str | — | Service version attached to spans. |
tracing_enabled | bool | True | Enable or disable tracing. |
flush_at | int | SDK default | Number of events to buffer before flushing. |
flush_interval | float | SDK default | Interval in seconds between auto-flushes. |
sample_rate | float | 1.0 | Trace sampling rate between 0.0 and 1.0. |
AISDK() accept the same parameters.
Optional builder methods:
TestOps sdk = TestOps.builder()
.publicKey("...")
.secretKey("...")
.restApiUrl("https://your-proxy.example.com") // override REST endpoint
.ingestionUrl("https://your-proxy.example.com") // override ingestion endpoint
.webAppUrl("https://your-proxy.example.com") // override web app URL
.build();Write-Only Key
For tracing-only deployments where read access to datasets, prompts, or other APIs should be restricted:
import { AISDK } from '@browserstack/ai-sdk';
const client = new AISDK({
writeOnlyKey: process.env.AISDK_WRITE_ONLY_KEY,
});from browserstack_ai_sdk import Observe
Observe.init(write_only_key=os.environ["AISDK_WRITE_ONLY_KEY"])The Java SDK does not currently support a separate write-only key. Use the standard public/secret key pair for all access.
Limitations
With a write-only key, only tracing/ingestion endpoints are accessible. The following will fail with an authentication error:
client.prompt.get()/client.prompt.create()/client.prompt.list()client.datasets.*(list, create, items, runs)client.experiments.*/client.experimentRuns.*client.evalsList.*/client.evals.evaluate()client.tools.*
Use the full public/secret key pair for any of these.
Flush and Shutdown
The SDK batches trace data and sends it asynchronously. Always flush before your process exits to ensure all pending data is sent.
import { AISDK } from '@browserstack/ai-sdk';
const client = new AISDK({
publicKey: process.env.AISDK_PUBLIC_KEY,
secretKey: process.env.AISDK_SECRET_KEY,
});
try {
// ... application logic
} finally {
await client.shutdown(); // flushes and closes connections
}| Method | Description |
|---|---|
flush(): Promise<void> | Flush all pending trace events. |
flushAsync(): Promise<void> | Same as flush() but resolves immediately and flushes in the background. |
shutdown(): Promise<void> | Flush synchronously and shut down the client. |
shutdownAsync(): Promise<void> | Non-blocking shutdown variant. |
In short-lived scripts or serverless functions, always call await client.shutdown() before the process exits. Without it, buffered traces may be lost.
from browserstack_ai_sdk import AISDK
client = AISDK(
public_key=os.environ["AISDK_PUBLIC_KEY"],
secret_key=os.environ["AISDK_SECRET_KEY"],
)
try:
# ... your application logic
pass
finally:
client.flush()For scripts or short-lived processes, use the client as a context manager:
from browserstack_ai_sdk import AISDK
with AISDK(
public_key=os.environ["AISDK_PUBLIC_KEY"],
secret_key=os.environ["AISDK_SECRET_KEY"],
) as client:
# LLM calls here are automatically flushed on exit
passIf you use the @observe decorator to trace functions, flushing is still required at process exit. See the Manual Tracing page for decorator usage.
// Flush without shutting down (e.g., between tests)
sdk.flush();
// Flush and shut down background threads (call at application exit)
sdk.shutdown();Use a JVM shutdown hook for long-running processes:
TestOps sdk = TestOps.fromEnv();
Runtime.getRuntime().addShutdownHook(new Thread(sdk::shutdown));Traces are batched and flushed automatically every 5 seconds or when the buffer reaches 5 items. Always call sdk.flush() before exit to ensure all buffered traces are sent.