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

# OpenAI

> Trace OpenAI chat completions automatically in TypeScript and Python — streaming, tool calls, guardrails, and config-driven retries and fallbacks, with two lines of code.

<Note>
  Available for: **Python** and **TypeScript**.
</Note>

Wrap your OpenAI client with `wrapOpenAI()` (TypeScript) or patch the `openai` module with `patch_openai()` (Python). Every `chat.completions.create` call your existing code makes is traced automatically — no changes to call sites required.

<Note>
  Only `chat.completions.create` is instrumented today — this covers standard chat and streaming chat calls, including function/tool calling. `embeddings.create`, the Responses API, and other OpenAI endpoints (images, audio, moderations) are not currently traced by either SDK.
</Note>

## Installation

<CodeGroup>
  ```bash TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  npm install @zespan/sdk openai
  ```

  ```bash Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  pip install zespan openai
  ```
</CodeGroup>

## Setup

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import OpenAI from "openai";
  import { zespan } from "@zespan/sdk";

  zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

  const openai = zespan.wrapOpenAI(new OpenAI());
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import os
  import zespan

  zespan.init(api_key=os.environ["ZESPAN_API_KEY"])
  zespan.patch_openai()

  import openai  # import after patching
  client = openai.OpenAI()
  ```
</CodeGroup>

In TypeScript, `wrapOpenAI()` returns a wrapped client instance — pass it to any function that makes OpenAI calls, and use it exactly like the original client. In Python, `patch_openai()` monkey-patches `openai.resources.chat.completions.Completions.create` in place, so `openai.OpenAI()` (constructed after patching) is traced automatically with no wrapping step.

## Example

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: "Summarize this document." }],
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  response = client.chat.completions.create(
      model="gpt-4o",
      messages=[{"role": "user", "content": "Summarize this document."}],
  )
  ```
</CodeGroup>

Zespan captures this call as a span with model, prompt tokens, completion tokens, latency, and cost.

## What gets captured

| Field            | Details                                                                                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Model            | `gpt-4o`, `gpt-4o-mini`, `o1`, etc. — the model actually sent to OpenAI, after any config override                                                          |
| Input tokens     | From `usage.prompt_tokens`                                                                                                                                  |
| Output tokens    | From `usage.completion_tokens`                                                                                                                              |
| Reasoning tokens | From `usage.completion_tokens_details.reasoning_tokens`, for reasoning models (e.g. `o1`)                                                                   |
| Cached tokens    | From `usage.prompt_tokens_details.cached_tokens`. TypeScript only — the Python wrapper does not currently extract this field                                |
| Cost             | Calculated from token counts and OpenAI pricing                                                                                                             |
| Latency          | Total time from request to response (`ttft_ms` also recorded for streaming calls)                                                                           |
| Status           | `success` or `error`. TypeScript further distinguishes `rate_limited` (HTTP 429) and `timeout` (HTTP 408); Python currently reports all failures as `error` |
| Finish reason    | `stop`, `length`, `tool_calls`, `content_filter`                                                                                                            |
| Tool calls       | Tool definitions from the request, plus the tool names and arguments the model actually called — see [Tool and function calls](#tool-and-function-calls)    |

<Note>
  Prompt and completion text is stored by default, with [PII redaction](/sdk/pii-redaction) applied before transmission. Set `storePrompts: false` (`store_prompts=False` in Python) in `zespan.init()` to disable prompt/completion storage entirely.
</Note>

## Streaming

Both wrappers support streaming. Token counts and tool calls are accumulated from the stream chunks, and time-to-first-token (`ttft_ms`) is recorded on the first chunk.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const stream = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: "Write a poem." }],
    stream: true,
  });

  for await (const chunk of stream) {
    process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  stream = client.chat.completions.create(
      model="gpt-4o",
      messages=[{"role": "user", "content": "Write a poem."}],
      stream=True,
  )

  for chunk in stream:
      delta = chunk.choices[0].delta.content
      if delta:
          print(delta, end="")
  ```
</CodeGroup>

## Tool and function calls

Pass `tools` as usual. Zespan records the tool definitions from the request and, once the model responds, the names and arguments of any tools it decided to call — for both streaming and non-streaming calls, in both languages.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: "What's the weather in Paris?" }],
    tools: [
      {
        type: "function",
        function: {
          name: "get_weather",
          description: "Get current weather",
          parameters: {
            type: "object",
            properties: { location: { type: "string" } },
            required: ["location"],
          },
        },
      },
    ],
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  response = client.chat.completions.create(
      model="gpt-4o",
      messages=[{"role": "user", "content": "What's the weather in Paris?"}],
      tools=[
          {
              "type": "function",
              "function": {
                  "name": "get_weather",
                  "description": "Get current weather",
                  "parameters": {
                      "type": "object",
                      "properties": {"location": {"type": "string"}},
                      "required": ["location"],
                  },
              },
          }
      ],
  )
  ```
</CodeGroup>

## Guardrails

Pass `guardrails: true` (`guardrails=True` in Python) to run pre- and post-call content checks against your configured guardrail policies.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const openai = zespan.wrapOpenAI(new OpenAI(), {
    guardrails: true,
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  zespan.patch_openai(guardrails=True)

  import openai  # import after patching
  client = openai.OpenAI()
  ```
</CodeGroup>

A guardrail that blocks content throws (Python: raises) `GuardrailBlockedError` — catch it to return a safe fallback instead of letting the exception propagate. See [Guardrails](/sdk/guardrails) for the full configuration options (`pre`/`post`/`failClosed`), the `GuardrailBlockedError` shape, and how to call guardrail checks directly outside of a wrapped call.

## Error handling

On failure, both wrappers re-throw (Python: re-raise) the original OpenAI SDK error after recording an `error`-status event with `error_message` — your existing `try`/`catch` (`try`/`except`) around the call keeps working unchanged.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  try {
    await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: "Hello" }],
    });
  } catch (err) {
    // The original OpenAI SDK error — a rate limit, timeout, guardrail block, etc.
    console.error(err);
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  try:
      client.chat.completions.create(
          model="gpt-4o",
          messages=[{"role": "user", "content": "Hello"}],
      )
  except Exception as err:
      # The original OpenAI SDK error
      print(err)
  ```
</CodeGroup>

A few behaviors are worth knowing about:

* **Status classification.** TypeScript inspects `err.status` and reports `rate_limited` for HTTP 429 and `timeout` for HTTP 408 (in addition to `error` for everything else, and `guardrail_blocked` as the `error_code` when a `GuardrailBlockedError` is thrown). The Python wrapper does not currently classify errors this way — every exception is recorded with status `error`.
* **Guardrail blocks.** In TypeScript, a guardrail block still enqueues an `error`-status event (with `error_code: "guardrail_blocked"`) before re-throwing. In Python, `GuardrailBlockedError` is re-raised immediately without an error event being recorded.
* **Config-driven resilience.** Retries, timeouts, concurrency limits, fallback models, and A/B testing are not options you pass to `wrapOpenAI()`/`patch_openai()` directly — they're project-level rules pushed from the Zespan dashboard via [config propagation](/sdk/config-propagation) and applied automatically to every call. The TypeScript wrapper applies all of these (model override, A/B test, retry with backoff, timeout, concurrency limiting, and fallback-on-error). The Python wrapper currently applies model override, A/B testing, and fallback-on-error, but not retry, timeout, or concurrency limiting.

## Next steps

* [Guardrails](/sdk/guardrails) — full guardrail configuration and `GuardrailBlockedError` reference
* [Config propagation](/sdk/config-propagation) — how model overrides, fallbacks, retries, and timeouts get pushed to running apps
* [Agent tracing](/sdk/agent-tracing) — wrap multi-step agent logic
* [Manual spans](/sdk/manual-spans) — add custom spans around non-LLM operations
* [PII redaction](/sdk/pii-redaction) — automatically redact sensitive data before storage
