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
| Method | Captured as |
|---|
chat.completions.create | One LLM event |
chat.completions.stream | One LLM event, recorded after the stream completes |
chat.completions.runTools | LLM events plus one tool event per tool invocation |
responses.create | One LLM event |
responses.stream | One LLM event, recorded after the stream completes |
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.
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.