Skip to main content
wrapOpenAISdk instruments an existing OpenAI client. Your call sites stay the same; each request is captured as an LLM event with model, prompts, output, token usage, duration, and status.

Quick setup

import OpenAI from "openai";
import { Carbon } from "@carbon-js/sdk";
import { wrapOpenAISdk } from "@carbon-js/sdk/ai";

const carbon = new Carbon();
const openai = wrapOpenAISdk(new OpenAI(), carbon);

const completion = await openai.chat.completions.create({
  model: "gpt-5.4-nano",
  messages: [{ role: "user", content: "What is the capital of France?" }],
});

// Flush : Only needed in serverless and other short-lived environments
await carbon.flushPendingEvents();
wrapOpenAISdk mutates the client you pass in and returns the same instance, typed to accept the optional carbon field on requests. Wrap the client once at startup and share the wrapped instance.

What gets captured

MethodCaptured as
chat.completions.createOne LLM event
chat.completions.streamOne LLM event, recorded after the stream completes
chat.completions.runToolsLLM events plus one tool event per tool invocation
responses.createOne LLM event
responses.streamOne LLM event, recorded after the stream completes

Attach metadata

Add a carbon field to any request to set a trace ID, context identifiers, or custom properties. The SDK strips the field before the request reaches OpenAI.
const traceId = carbon.createTraceId();

const completion = await openai.chat.completions.create({
  model: "gpt-5.4-nano",
  messages: [{ role: "user", content: "Summarize this ticket." }],
  carbon: {
    traceId,
    context: { agentId: "support-agent", userId: "user-481" },
    additionalProperties: { release: "2026-06" },
  },
});
See Traces and context for what each field powers in the dashboard.

Streaming

Streamed responses are captured after the stream finishes, so the stream must be fully consumed — iterate it to completion or await the final result:
const stream = await openai.chat.completions.create({
  model: "gpt-5.4-nano",
  messages: [{ role: "user", content: "Write a haiku about ledgers." }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
For streamed chat completions, the wrapper sets stream_options.include_usage automatically so token usage is recorded.
A stream that is abandoned before it completes produces no event.

Tool loops

runTools captures the full loop: each model turn becomes an LLM event and each tool execution becomes a tool event, all sharing the same trace.
const runner = openai.chat.completions.runTools({
  model: "gpt-5.4-nano",
  messages: [{ role: "user", content: "What is the weather in Tokyo?" }],
  tools: [
    /* your tool definitions */
  ],
  carbon: { traceId: carbon.createTraceId() },
});

const result = await runner.finalChatCompletion();

Flushing

In serverless and other short-lived environments, flush buffered events before exiting:
await carbon.flushPendingEvents();
Buffering and flush behavior are configurable — see Configuration.