> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oncarbon.site/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI

> Capture OpenAI SDK calls — Chat Completions, Responses, streaming, and runTools — as Carbon events.

`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

```typescript theme={null}
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?" }],
});
```

<Note>
  `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.
</Note>

Deploying to a serverless platform — Next.js, Vercel, AWS Lambda? Flush buffered events before the function exits so none are lost. See [Serverless](/documentation/sdk/serverless).

## 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 |

## 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.

```typescript theme={null}
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 [Context](/documentation/sdk/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:

```typescript theme={null}
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.

<Warning>A stream that is abandoned before it completes produces no event.</Warning>

## Tool calls

A tool execution is captured as a tool event on the same trace as the LLM call that requested it. Whether that happens automatically depends on which method runs the tool.

### Automatically instrumented

`chat.completions.runTools` executes your tool functions in a loop and captures every step — each model turn becomes an LLM event and each tool execution becomes a tool event, grouped on one trace automatically. No `wrapTool` or trace ID needed.

```typescript theme={null}
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 */
  ],
});

const result = await runner.finalChatCompletion();
```

### Manual instrumentation

`chat.completions.create`, `chat.completions.stream`, and the `responses.*` methods don't execute tools — the model returns the tool calls and your code runs them.

Wrap each tool with `wrapTool` so its execution is captured. These methods don't open a trace of their own, so create a trace ID with `carbon.createTraceId()` and pass it to both the model call and every tool call — that shared ID links the LLM event and its tool events into one trace:

```typescript theme={null}
const searchDocs = carbon.wrapTool({
  name: "search_docs",
  run: async ({ query }: { query: string }) => search(query),
});

const traceId = carbon.createTraceId();

const completion = await openai.chat.completions.create({
  model: "gpt-5.4-nano",
  messages,
  tools: [/* search_docs definition */],
  carbon: { traceId },
});

// Run each tool the model requested — each wrapped call is captured on the same trace.
for (const call of completion.choices[0].message.tool_calls ?? []) {
  if (call.type === "function" && call.function.name === "search_docs") {
    await searchDocs(JSON.parse(call.function.arguments), { traceId });
  }
}
```

See [Tool calls](/documentation/sdk/tool-calls) for the full `wrapTool` API.
