@brizz/sdk

中文文档
0.2.1发布于 2026/11/12Apache-2.0TypeScriptESM + CJS近 30 天下载 7万

OpenTelemetry-based observability SDK for AI applications

原始使用文档
文档目录44 节
  1. Table of Contents
  2. Features
  3. Installation
  4. Quick Start
  5. Module System Support
  6. Supported Libraries
  7. Optional Instrumentations
  8. Vercel AI SDK Integration
  9. Vercel eve
  10. Google GenAI (Gemini)
  11. PII Masking
  12. Session Tracking
  13. Session Context Manager (Recommended)
  14. Session Title Generation
  15. Accessing the Active Session
  16. Lightweight Session Helpers
  17. Function Wrapper Pattern
  18. Immediate Execution Pattern
  19. Identifying Users, Organizations & Messages
  20. Recording Feedback
  21. Recording Metrics
  22. Recording Classification Calls
  23. Custom Properties
  24. Handling Method Context
  25. Subagents
  26. Mute Messages
  27. Deployment Environment
  28. Multiple Services in One Process
  29. Custom Events & Logging
  30. Environment Variables
  31. Disable Span Export
  32. Dropping Spans
  33. Running Alongside Your Own OpenTelemetry Setup
  34. Serverless (Cloud Run, Lambda)
  35. Advanced Configuration
  36. Testing & Development
  37. Package.json Examples
  38. Examples
  39. Troubleshooting
  40. Common Issues
  41. Debug Mode
  42. Contributing
  43. Links
  44. License

Brizz SDK

npm version License: Apache-2.0 TypeScript

OpenTelemetry-based observability SDK for AI applications, built by Brizz. Traces popular AI libraries including OpenAI, Anthropic, Vercel AI SDK, and more.

Table of Contents

Features

  • 🔍 One-Call Setup - Pass your AI libraries to Brizz.initialize() and they're traced
  • 📊 OpenTelemetry Native - Standards-compliant tracing, metrics, and logs
  • 🛡️ PII Masking - Optional masking for sensitive data
  • 🔄 Session Tracking - Group related operations and traces
  • 📦 Runs Anywhere - ESM, CommonJS, bundlers, and tsx

Installation

npm install @brizz/sdk
# or
yarn add @brizz/sdk
# or
pnpm add @brizz/sdk

Quick Start

First, set up your environment variables:

BRIZZ_API_KEY=your-api-key
BRIZZ_BASE_URL=https://telemetry.brizz.dev  # Optional
BRIZZ_APP_NAME=my-app                       # Optional
BRIZZ_LOG_LEVEL=info                        # Optional: debug, info, warn, error, none

Then initialize the SDK in your application, passing the libraries you want to trace:

import { Brizz } from '@brizz/sdk';
import OpenAI from 'openai';

Brizz.initialize({
  apiKey: process.env.BRIZZ_API_KEY,
  appName: 'my-ai-app',
  instrumentModules: { openAI: OpenAI },
});

Run your app the way you already do — node app.js, tsx app.ts, or your own start script.

Important: Call Brizz.initialize() before making any AI calls. If using dotenv, use import "dotenv/config" before importing @brizz/sdk.

Module System Support

The same setup works in ESM, CommonJS, bundlers, and tsx — import @brizz/sdk and call Brizz.initialize().

Bundlers that inline modules (Next.js, Webpack) may need a dynamic import so you can hand the resolved module to instrumentModules:

// For problematic bundlers
const { default: OpenAI } = await import('openai');

Brizz.initialize({
  apiKey: 'your-api-key',
  instrumentModules: {
    openAI: OpenAI,
  },
});

Supported Libraries

Pass the libraries you use to instrumentModules and the SDK traces them:

  • OpenAI - openai package
  • Anthropic - @anthropic-ai/sdk package
  • Vercel AI SDK - ai package (generateText, streamText, etc.)
    • Captured through the AI SDK's own telemetry — pass instrumentModules: { ai, aiOtel } to trace from the first call on ai@7 (with @ai-sdk/otel installed); on ai@6 and earlier, pass experimental_telemetry: { isEnabled: true } on each call
  • LangChain - langchain and @langchain/* packages
  • LlamaIndex - llamaindex package
  • AWS Bedrock - @aws-sdk/client-bedrock-runtime
  • Vector Databases - Pinecone, Qdrant, ChromaDB

Optional Instrumentations

A few instrumentations ship as optional peer dependencies so they're only pulled in when you need them. Install the matching package(s) to enable them:

To traceInstall
Vercel AI SDK v7+ (ai@7) — required for span capturenpm install @ai-sdk/otel
Google GenAI / Gemini (@google/genai) — see Gemininpm install @traceloop/instrumentation-google-generativeai
Google Vertex AI (@google-cloud/vertexai)npm install @traceloop/instrumentation-vertexai
MCP (Model Context Protocol)npm install @arizeai/openinference-instrumentation-mcp @modelcontextprotocol/sdk

If a package isn't installed, the SDK keeps working and simply skips that instrumentation. On the automatic path the skip is a quiet debug log; when you opt in explicitly via instrumentModules, a missing package is logged at error so the misconfiguration is visible.

Vercel AI SDK Integration

On ai@7 and newer, also install @ai-sdk/otel — v7 moved AI SDK span collection into that package, so without it your generateText / streamText calls produce no spans. It isn't needed on ai@6.

On ai@6 and earlier, telemetry is off by default, so enable it on each call. On ai@7 it is on by default and the flag is optional:

import { generateText, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

// For generateText
const result = await generateText({
  model: openai('gpt-4'),
  prompt: 'Hello, world!',
  experimental_telemetry: { isEnabled: true }, // Required on ai@6 and earlier
});

// For streamText
const stream = streamText({
  model: openai('gpt-4'),
  messages: [{ role: 'user', content: 'Hello!' }],
  experimental_telemetry: { isEnabled: true }, // Required on ai@6 and earlier
});

This enables automatic tracing of:

  • Model calls and responses
  • Token usage and costs
  • Tool calls and executions
  • Streaming data

For scripts, serverless functions and bundled ESM, pass your imported modules to register telemetry before Brizz.initialize() returns. Your first AI SDK call is then traced:

import { Brizz } from '@brizz/sdk';
import * as ai from 'ai';
import * as aiOtel from '@ai-sdk/otel';

Brizz.initialize({ appName: 'my-app', instrumentModules: { ai, aiOtel } });
// Calls to ai.generateText() and ai.streamText() are traced immediately.

Without these modules, Brizz loads the installed packages in the background. A call that starts before registration finishes is not traced. On ai@6, pass only ai and enable experimental_telemetry on each call.

If your app registers its own @ai-sdk/otel collector and its spans already reach Brizz, turn Brizz's collector off so each call is traced once:

Brizz.initialize({
  appName: 'my-app',
  instrumentModules: { ai },
  disableAiSdkCollector: true,
});
ai.registerTelemetry(new aiOtel.OpenTelemetry()); // after Brizz.initialize()

disableAiSdkCollector: true also works without instrumentModules, using background registration. Always create your own collector after setting up your telemetry provider.

Vercel eve

Vercel eve agents emit standard OpenTelemetry GenAI spans, so Brizz ingests their traces, message content, and cost as soon as you point eve's exporter at your Brizz endpoint. @brizz/sdk/eve gives you a one-line registerOTel config that reads BRIZZ_DSN — it wires the exporter and the session grouping that makes each agent run render as a single conversation:

// agent/instrumentation.ts
import { defineInstrumentation } from 'eve/instrumentation';
import { registerOTel } from '@vercel/otel';
import { createEveOtelConfig } from '@brizz/sdk/eve';

export default defineInstrumentation({
  setup: ({ agentName }) => registerOTel(createEveOtelConfig({ serviceName: agentName })),
});

Then set your DSN in the environment — a single pasted credential:

BRIZZ_DSN=https://<bearer>@<gateway-host>/<service-name>

createEveOtelConfig derives the bearer and endpoint from the DSN and ignores its service segment — eve's agentName stays the service name. Prefer to compose it yourself? The pieces are exported too:

import { createEveTraceExporter, createEveSessionProcessor } from '@brizz/sdk/eve';

registerOTel({
  serviceName: agentName,
  traceExporter: createEveTraceExporter(),
  // 'auto' keeps the default exporter processor.
  spanProcessors: [createEveSessionProcessor(), 'auto'],
});

@brizz/sdk/eve is a standalone, side-effect-free export: it does not initialize the full SDK, so it won't duplicate the spans eve already emits.

Google GenAI (Gemini)

@google/genai is Google's unified SDK for Gemini (both the Gemini Developer API and Vertex AI). Install the peer dependency, then build your client from the class instrumentGoogleGenAI returns:

import { Brizz } from '@brizz/sdk';
import { instrumentGoogleGenAI } from '@brizz/sdk/google-genai';
import * as genai from '@google/genai';

Brizz.initialize({ appName: 'my-app' });

// Call after Brizz.initialize(). The returned class is instrumented — build the client from it.
const GoogleGenAI = instrumentGoogleGenAI(genai);
const ai = new GoogleGenAI({ vertexai: true, project, location });

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

This traces generateContent / generateContentStream — messages, tool calls, token usage, and cost — with no per-call instrumentation.

Unlike the other integrations, @google/genai is not wired through instrumentModules. It's an ESM-only package, and a Node ES module namespace is read-only, so it can't be patched in place — instrumentGoogleGenAI returns an instrumented GoogleGenAI class instead. Construct your client from that class and you're done.

PII Masking

Optional masking for span attributes.

// Enable default masking
Brizz.initialize({
  apiKey: 'your-api-key',
  masking: true,
});

// Custom masking configuration
Brizz.initialize({
  apiKey: 'your-api-key',
  masking: {
    spanMasking: {
      rules: [
        {
          attributePattern: 'gen_ai\\.(prompt|completion)',
          mode: 'partial', // 'partial' or 'full'
          patterns: ['sk-[a-zA-Z0-9]{48}'],
        },
      ],
    },
  },
});

When enabled, defaults cover a curated set of common secret patterns. Add custom rules for anything else you need masked.

Session Tracking

Group related operations under a session context:

The startSession function creates a session span and provides a Session object:

import { startSession } from '@brizz/sdk';

// Basic usage - all LLM calls within the callback are automatically linked
const result = await startSession(
  'session-123',
  async (session) => {
    // Add custom properties (optional)
    session.updateProperties({ userId: 'user-456', model: 'gpt-4' });

    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: userQuery }],
    });

    return response;
  },
  { feature: 'chat' }, // optional: extraProperties propagated to all spans
);

Session Methods:

  • session.updateProperties({ key: value }) - Add custom properties to the session span
  • session.setInput(text, context?) - (Optional) Manually track input text; optional context bag attaches per-turn metadata rendered in the dashboard's Context panel
  • session.setOutput(text, context?) - (Optional) Manually track output text; optional context bag attaches per-turn metadata rendered in the dashboard's Context panel
  • session.setTitle(text) - Set a session title (typically used with mode: 'title')
  • session.addExternalLink(url, options?) - (Optional) Attach an external link (e.g. a Datadog trace or dashboard) to the session; it appears on the session detail panel. Also available as the top-level addExternalLink(url, options?).

Per-turn context example:

await startSession('session-123', async (session) => {
  session.setInput('Why is my bill high?', { selected_invoice: 'INV-9182' });
  const response = await openai.chat.completions.create({ ... });
  session.setOutput(response.choices[0].message.content, {
    message_id: 'msg-42',
    sources: ['doc-abc'],
  });
});

When to use manual input/output tracking:

In most cases, Brizz automatically captures inputs and outputs from your LLM calls. Use setInput/setOutput for special scenarios:

  • Multi-agent flows: Track only user-facing input/output, not intermediate agent communications
  • Structured data extraction: Track a specific field from complex JSON requests
  • Post-processing: Track transformed responses before returning to the user
await startSession('session-456', async (session) => {
  // Extract just the query from a structured request
  const requestData = { query: "What's the weather?", context: {...} };
  session.setInput(requestData.query);

  const response = await openai.chat.completions.create({...});

  // Extract answer from structured response
  const responseJson = JSON.parse(response.choices[0].message.content);
  session.setOutput(responseJson.answer);

  return responseJson;
});

External link example:

import { addExternalLink, startSession } from '@brizz/sdk';

startSession('session-123', (session) => {
  // Top-level function — resolves the active session from context.
  addExternalLink('https://app.datadoghq.com/trace/abc', { title: 'Datadog trace' });
});

// Outside a session — pass the id explicitly.
addExternalLink('https://sentry.io/issues/456', { sessionId: 'session-123', linkType: 'sentry' });

Session Title Generation

If you use an LLM call to generate session titles, wrap it so its spans show as one "Title generated" row instead of turns of the conversation:

import { startSession, startSessionTitle } from '@brizz/sdk';

await startSession('session-123', async (session) => {
  const response = await openai.chat.completions.create({...});

  // Title generation — shown as one row, not as turns
  await startSessionTitle(async (title) => {
    const t = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: 'Summarize this chat in 3 words' }],
    });
    title.setTitle(t.choices[0].message.content);
  });
});

// Or use mode: 'title' on startSession directly
await startSession('session-123', async (session) => {
  const t = await openai.chat.completions.create({...});
  session.setTitle(t.choices[0].message.content);
}, undefined, { mode: 'title' });

// Or use startSessionTitle outside a session (pass sessionId explicitly)
await startSessionTitle(async (title) => {
  title.setTitle('My Title');
}, { sessionId: 'session-123' });

Accessing the Active Session

Use getActiveSession() to retrieve the current session from anywhere within a startSession scope — no need to pass the session object through your call stack:

import { startSession, getActiveSession } from '@brizz/sdk';

function deepHelper() {
  const session = getActiveSession();
  session?.updateProperties({ step: 'helper' });
}

await startSession('session-123', async () => {
  deepHelper(); // accesses session without it being passed as a parameter
});

// Outside a session, returns undefined
getActiveSession(); // undefined

Lightweight Session Helpers

@brizz/sdk/session exports the session and context helpers without loading the rest of the SDK, which keeps module-heavy setups such as Jest test suites fast and small:

import { callWithSessionId, getActiveSession } from '@brizz/sdk/session';

Keep Brizz.initialize() on the main @brizz/sdk entry at startup. Nothing is sent until it runs.

Function Wrapper Pattern

For simpler cases where you just need to tag traces with a session ID:

import { withSessionId, emitEvent } from '@brizz/sdk';

async function processUserWorkflow(userId: string) {
  // All traces within this function will include the session ID
  const result = await generateText({
    model: openai('gpt-4'),
    messages: [{ role: 'user', content: 'Hello' }],
    experimental_telemetry: { isEnabled: true },
  });

  return result;
}

// Create a wrapped function that always executes with session context
// withSessionId(sessionId, fn, thisArg?, extraProperties?)
const sessionedWorkflow = withSessionId('session-123', processUserWorkflow, undefined, {
  feature: 'workflow',
});

// Call multiple times, each with the same session context
await sessionedWorkflow('user-456');
await sessionedWorkflow('user-789');

Immediate Execution Pattern

import { callWithSessionId } from '@brizz/sdk';

// Execute function immediately with session context
await callWithSessionId('session-123', processUserWorkflow, null, 'user-456');

Identifying Users, Organizations & Messages

Attach the end-user, their organization, and a per-message id with typed setters. Call them inside a session — they apply to the turn's spans:

import { startSession, setUser, setOrganization, setMessageId } from '@brizz/sdk';

await startSession(sessionId, async () => {
  setUser({ id: user.id, email: user.email, role: user.role, plan: user.plan });
  setOrganization({ id: org.id, name: org.name, plan: org.plan, domain: org.domain });
  setMessageId(message.id); // your own id, to reference this message later

  return agent.run(prompt);
});

Only id is required. Each field maps to its own attribute (brizz.user.id, brizz.organization.plan, brizz.message.id, …). For anything beyond the named fields, pass a traits record — each entry becomes brizz.user.<key> / brizz.organization.<key>:

setUser({ id: user.id, traits: { department: 'sales', signup_source: 'referral' } });
setOrganization({ id: org.id, traits: { industry: 'fintech' } });

The same setters are available on the session object (session.setUser(...)) and as scoped wrappers (callWithUser / withUser).

Recording Feedback

Capture an end-user's reaction to a specific reply — a 👍/👎, a rating, a reason. Pair it with the message id you set on the turn:

import { startSession, setMessageId, recordFeedback } from '@brizz/sdk';

await startSession(sessionId, async () => {
  setMessageId(message.id); // the id you'll reference this reply by
  const reply = await agent.run(prompt);
  recordFeedback('thumbs_up'); // defaults to the current message
});

Only type is required; score, reason, comment, and source are optional, and attributes adds free-form brizz.feedback.<key> entries. Feedback is anchored by messageId and/or sessionId, so you can send it later — even minutes or days after the reply — by passing the id(s) explicitly:

recordFeedback('thumbs_down', { messageId: message.id, sessionId, reason: 'inaccurate' });

Recording Metrics

Report a number your own system already computes about an interaction — an eval score, a customer rating, a latency, a cost. It becomes a real Brizz metric you can filter and chart by, rather than an untyped bag of event attributes.

import { startSession, recordMetric } from '@brizz/sdk';

await startSession(sessionId, async () => {
  const reply = await agent.run(prompt);

  const score = await myEvaluator.score(prompt, reply);
  recordMetric({
    name: 'quality_score',
    value: score,
    unit: 'score',
    minValue: 0,
    maxValue: 1,
    polarity: 'positive',
  });
});

polarity tells Brizz which direction is good — a rising quality_score is an improvement, a rising hallucination_rate is a regression. minValue / maxValue describe the scale, so a 4 out of 5 isn't read as a 4 out of 1.

Scoring often happens after the fact. Pass sessionId to attach a metric from anywhere, and timestamp to say when the measured thing happened, so a nightly job lands the score on the turn it describes rather than on the evaluation run:

recordMetric({
  name: 'quality_score',
  value: await judge.score(session),
  sessionId: session.id,
  timestamp: session.endedAt,
  comment: 'graded by the nightly LLM judge',
  attributes: { evaluator: 'gpt-4o', rubric: 'v2' },
});

attributes are flat labels you can slice the metric by. Re-reporting the same metric supersedes the previous value, so a re-score wins over the original.

An offline evaluation job often scores conversations from several services in one process. Pass serviceName to report each metric under the service it belongs to, and traceId when you have a trace of the conversation rather than its session id:

for (const row of evalResults) {
  recordMetric({
    name: 'resolved',
    value: row.resolved,
    serviceName: row.service, // e.g. 'support-agent'
    traceId: row.traceId, // 32 hex characters
  });
}

A metric needs a session id or a trace id to land on a conversation; one with neither is dropped with a warning.

A metric is one value per conversation by default. For a metric you score once per turn, pass scope: 'trace': each trace keeps its own value, shown on that turn in the conversation, and the session reads as the average of its turns. The trace is traceId when given, else the span you call it from — a trace-scoped call with neither is dropped with a warning:

await startSession(sessionId, async () => {
  const reply = await agent.run(prompt);
  recordMetric({ name: 'frustration_rate', value: frustration(prompt), scope: 'trace', polarity: 'negative' });
});

Recording Classification Calls

When you call a classification model through your own HTTP code, wrap the call in decisionCall so it shows up in the conversation with its questions, answers, tokens and cost. The callback's return value is passed through, and an error it throws is recorded and rethrown:

import { decisionCall } from '@brizz/sdk';

const response = await decisionCall(
  { provider: 'microsoft', model: 'microsoft-decision-1', state, questions },
  async (call) => {
    const r = await client.decide(state, questions);
    call.setAnswers(r.answers);
    call.setUsage({ inputTokens: r.usage.input, outputTokens: r.usage.output });
    call.setCost(0.0001); // optional, USD
    return r;
  },
);

state is a string or any JSON value; questions and answers are sent as you pass them. The optional requestModel is the model name you requested, when it differs from model. The call joins the current session and carries the current custom properties; to set them for one call, wrap it in callWithSessionId or callWithProperties.

To record a call that already happened, pass everything at once, with optional startTime / endTime (Dates):

import { recordDecisionCall } from '@brizz/sdk';

recordDecisionCall({
  provider: 'microsoft',
  model: 'microsoft-decision-1',
  state,
  questions,
  answers: r.answers,
  inputTokens: 555,
  outputTokens: 6,
  startTime: startedAt,
});

Custom Properties

Add custom properties to telemetry context:

import { withProperties, callWithProperties } from '@brizz/sdk';

// Wrapper pattern - properties applied to all calls
const taggedFn = withProperties({ env: 'prod', feature: 'chat' }, myFunction);
await taggedFn(args);

// Immediate execution - properties applied once
await callWithProperties({ userId: 'user-123' }, myFunction, null, args);

Handling Method Context

When wrapping methods that use this, you have several options:

class ChatService {
  async processMessage(userId: string, message: string) {
    // This method uses 'this' context
    return `Processed by ${this.serviceName}: ${message}`;
  }
}

const service = new ChatService();
// Wrap with arrow function to preserve 'this' context
const sessionedProcess = withSessionId('session-123', (userId: string, message: string) =>
  service.processMessage(userId, message),
);

Option 2: Using bind()

// Pre-bind the method to preserve 'this' context
const sessionedProcess = withSessionId('session-123', service.processMessage.bind(service));

Option 3: Explicit thisArg Parameter

// Pass 'this' context explicitly as third parameter
const sessionedProcess = withSessionId(
  'session-123',
  service.processMessage,
  service, // explicit 'this' context
);

Note: The arrow function approach (Option 1) is recommended as it's more explicit, avoids lint warnings, and is less prone to this binding issues.

Subagents

Mark work as a subagent so it shows as its own lane in the conversation instead of mixing into the session's main transcript. Nesting composes, and two runs of the same agent stay two lanes.

Three forms, same first argument — a name, { name, id, parentId }, or { token }:

import { setAgent, startAgent, withAgent } from '@brizz/sdk';

// startAgent — scoped: runs the callback now, returns its return value
await startAgent('research', () => researchAgent.generate({ prompt: task }));

// withAgent — wraps a function; every call is its own run, so its own lane
const research = withAgent('research', runResearch);
await research(topicA);
await research(topicB);

// setAgent — imperative, no scope: marks everything created after it, and
// returns the handle. The natural form at the top of a job or route handler.
export async function handleJob(job) {
  setAgent({ token: job.brizzAgent });
  await boardCreator.generate({ prompt: job.goal });
}

const { token } = setAgent('board-creator'); // grab the token to send onward

setAgent mirrors setUser / setSessionId — there is no auto-reset, so use startAgent when you want the scope to close.

Handing a subagent to code running elsewhere — a queue message, an RPC argument, a database row — is the token: send it with the work and re-enter the agent from it on the other side. It carries the session and trace context, so the detached run joins the same conversation.

await startAgent('board-creator', ({ token }) => sendToWorker({ goal, brizzAgent: token }));

Already tracking your own agent ids? Pass them instead of a bare name:

await startAgent({ name: 'research', id: runId, parentId: callerRunId }, () => run());

id is your id for this run (unique per run, or two runs share a lane) and parentId overrides the enclosing agent. Anything omitted is filled in — a fresh id per run, the enclosing agent as the parent. Don't give withAgent an id: it would put every call to the wrapped function in one lane.

Mute Messages

Keep internal or unrelated LLM calls — summarization, title generation, classification, guardrail checks — out of the captured conversation. The call still runs and its telemetry (latency, tokens, cost) is recorded; only the content is left out — the prompt, the reply, and the tool calls.

import { callWithMute, withMute } from '@brizz/sdk';

// Hide both sides of an internal call
await callWithMute({}, () => agent.run('Summarize this conversation for internal logging.'));

// Keep the assistant reply, drop the prompt
await callWithMute({ output: false }, () => agent.run('…a long internal prompt…'));

// Keep the tool calls, drop the prompt and the reply
await callWithMute({ tools: false }, () => agent.run('…a long internal prompt…'));

// Pre-wrap a function to reuse the same muting
const muteTitle = withMute({}, agent.run, agent);

tools covers tool calls and their results. Leave it out and it follows output.

Deployment Environment

Optionally specify the deployment environment for better filtering and organization:

Brizz.initialize({
  apiKey: 'your-api-key',
  appName: 'my-app',
  environment: 'production', // Optional: 'dev', 'staging', 'production', etc.
});

Multiple Services in One Process

appName names the whole process. When one process runs several agents that should show up as separate services in Brizz, scope the service name instead:

import { callWithServiceName, withServiceName, setServiceName } from '@brizz/sdk';

// Everything traced inside the callback reports as 'checkout-agent',
// including auto-instrumented LLM calls
await callWithServiceName('checkout-agent', async () => {
  return openai.chat.completions.create({ ... });
});

// Pre-wrap a handler
const handleSupport = withServiceName('support-agent', supportHandler);

// Imperative form, for when the enclosing span already exists
// (an MCP or HTTP instrumentation opens its span before your handler runs)
setServiceName('billing-agent');

Spans and events created outside any of these keep the configured appName.

Custom Events & Logging

Emit custom events and structured logs:

import { emitEvent, emitEventWithSessionId, logger } from '@brizz/sdk';

// Emit custom events
emitEvent('user.signup', {
  userId: '123',
  plan: 'pro',
  source: 'website',
});

emitEvent('ai.request.completed', {
  model: 'gpt-4',
  tokens: 150,
  latency: 1200,
});

// Emit with a known session ID, outside any session scope
emitEventWithSessionId('session-123', 'user.feedback.submitted', {
  rating: 5,
});

// Structured logging
logger.info('Processing user request', { userId: '123', requestId: 'req-456' });
logger.error('AI request failed', { error: err.message, model: 'gpt-4' });

Environment Variables

# Required
BRIZZ_API_KEY=your-api-key

# Optional Configuration
BRIZZ_BASE_URL=https://telemetry.brizz.dev    # Default telemetry endpoint
BRIZZ_APP_NAME=my-app                         # Application name
BRIZZ_APP_VERSION=1.0.0                       # Application version
BRIZZ_ENVIRONMENT=production                  # Environment (dev, staging, prod)
BRIZZ_LOG_LEVEL=info                          # SDK log level
BRIZZ_DISABLE_SPAN_EXPORTER=true              # Skip span export (see "Disable Span Export")
BRIZZ_SET_GLOBAL_TRACER_PROVIDER=false        # Keep Brizz spans on their own provider (see below)

# OpenTelemetry Standard Variables
OTEL_SERVICE_NAME=my-service                               # Service name
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true    # Capture AI content

Disable Span Export

Keep Brizz.initialize() in your code without sending any spans — useful for dev/test environments. When enabled, the SDK skips span exporter and processor setup, so no spans are exported. Metrics and logs continue to work.

Brizz.initialize({ apiKey: 'your-api-key', disableSpanExporter: true });

Or via env var: BRIZZ_DISABLE_SPAN_EXPORTER=true.

Dropping Spans

Filter spans before export with beforeSendSpan. Return false to drop a span, true to keep it. Useful for stripping noisy paths (health checks, internal tooling) or excluding telemetry for specific end-users.

Brizz.initialize({
  apiKey: 'your-api-key',
  beforeSendSpan: (span) => {
    // OpenInference (LangChain JS, LangGraph JS) packs per-call
    // `config.metadata` into a JSON-stringified `metadata` attribute.
    const raw = span.attributes['metadata'];
    if (typeof raw !== 'string') return true;
    try {
      return JSON.parse(raw).customer_id !== 'cust_42';
    } catch {
      return true;
    }
  },
});

Tag the call so the filter has something to match on:

await llm.invoke([new HumanMessage('Hello')], {
  metadata: { customer_id: 'cust_42' },
});

Both sync and async filters are supported. Exceptions are caught and the span passes through.

Running Alongside Your Own OpenTelemetry Setup

If your app registers its own OpenTelemetry TracerProvider (for example, for an APM), pass setGlobalTracerProvider: false:

Brizz.initialize({ apiKey: 'your-api-key', setGlobalTracerProvider: false });

Brizz keeps its spans on its own provider and sends them to Brizz only. Your provider, propagator and sampling stay as they are, and Brizz records every conversation even when your setup does not sample the request.

Give an instrumentation you construct yourself Brizz's tracer provider:

import { getTracerProvider } from '@brizz/sdk';

new LangChainInstrumentation({ tracerProvider: getTracerProvider() });

Or via env var: BRIZZ_SET_GLOBAL_TRACER_PROVIDER=false.

Serverless (Cloud Run, Lambda)

Where CPU is throttled once the response is sent, queued telemetry may never leave the instance. Await Brizz.flush() before responding:

await Brizz.flush();
res.json(result);

On Cloud Run, enabling "CPU always allocated" works too.

Advanced Configuration

Brizz.initialize({
  apiKey: 'your-api-key',
  appName: 'my-app',
  baseUrl: 'https://telemetry.brizz.dev',

  // Custom headers for authentication
  headers: {
    'X-API-Version': '2024-01',
    'X-Environment': 'production',
  },

  // Disable batching for immediate export (testing)
  disableBatch: false,

  // Custom exporters for testing
  customSpanExporter: new InMemorySpanExporter(),
  customLogExporter: new InMemoryLogExporter(),

  // Disable internal NodeSDK (advanced usage)
  disableNodeSdk: false,

  // Skip Brizz's AI SDK collector when the app registers its own
  disableAiSdkCollector: false,

  // Log level for SDK diagnostics
  logLevel: 'info', // debug, info, warn, error, none
});

Testing & Development

For testing and development, you can use in-memory exporters:

import { InMemorySpanExporter } from '@opentelemetry/sdk-trace';

const spanExporter = new InMemorySpanExporter();

Brizz.initialize({
  apiKey: 'test-key',
  customSpanExporter: spanExporter,
  logLevel: 'debug',
});

// Later in tests
const spans = spanExporter.getFinishedSpans();
expect(spans).toHaveLength(1);
expect(spans[0].name).toBe('openai.chat');

Package.json Examples

The SDK is initialized from your application code, so your scripts stay as they are:

{
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "debug": "node --inspect src/index.js"
  }
}

Examples

Check out the examples directory for complete working examples:

  • Basic Usage - Simple AI application setup
  • Vercel AI SDK - Integration with Vercel's AI SDK
  • Session Tracking - Grouping related operations
  • Custom Events - Emitting business metrics
  • PII Masking - Data protection configuration

Troubleshooting

Common Issues

"Could not find declaration file"

  • Make sure to build the SDK: pnpm build
  • Check that dist/ contains .d.ts files

Instrumentation not working

  • Ensure Brizz.initialize() runs before any AI calls
  • Pass the libraries you want to trace via instrumentModules
  • Check that BRIZZ_API_KEY is set
  • For Vercel AI SDK: on ai@7, install @ai-sdk/otel; on ai@6 and earlier, add experimental_telemetry: { isEnabled: true } to function calls

CJS/ESM compatibility issues

  • Use dynamic imports in CommonJS: const ai = await import('ai')
  • For bundlers, use manual instrumentation with instrumentModules

Debug Mode

Enable debug logging to troubleshoot issues:

BRIZZ_LOG_LEVEL=debug node your-app.js

Contributing

See our Contributing Guide for development setup and guidelines.

License

Apache-2.0 - see LICENSE file.

版本与发布记录

@brizz/sdk 共发布 69 个版本,首次发布于 2025/08/05;过去 12 个月发布了 51 个版本。

版本发布日期距今
0.2.1latest2026/11/12今天
0.1.60latest-0.12026/11/083 天前
0.2.02026/11/083 天前
0.1.592026/11/056 天前
0.1.582026/11/0210 天前
0.1.572026/10/2814 天前

其他发布通道:rc → 0.1.3-rc.1,latest-0.1 → 0.1.60。

关于 @brizz/sdk 的常见问题

@brizz/sdk 是什么?

OpenTelemetry-based observability SDK for AI applications

如何安装 @brizz/sdk?

在项目目录运行 npm install @brizz/sdk,或使用 pnpm add / yarn add / bun add。

@brizz/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@brizz/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@brizz/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.15.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@brizz/sdk 可以商用吗?

@brizz/sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@brizz/sdk 的下载量有多少?

根据 npm 官方下载统计,@brizz/sdk 在 2026/10/09 – 2026/11/07 这 30 天内被下载 69,722 次,日均约 2,324 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@brizz/sdk 还在维护吗?

最新版本 0.2.1 发布于 2026/11/12(今天);至今共发布 69 个版本,其中过去 12 个月发布了 51 个;首次发布于 2025/08/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。