> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://respan.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/_mcp/server.

# OpenAI SDK (tracing)

> Trace and observe OpenAI SDK calls with Respan — auto-instrumented spans, gateway routing, and prompt management.

The [OpenAI SDK](https://developers.openai.com/api/reference/python) is the official client for OpenAI's APIs, available for both Python and TypeScript/JavaScript. It supports Chat Completions and the Responses API. Respan gives you full observability over every OpenAI call, streamed response, and tool invocation — and gateway routing to 250+ models with prompt management.

#### Set up Respan

Create an account at [platform.respan.ai](https://platform.respan.ai) and grab an [API key](https://platform.respan.ai/platform/api/api-keys).

Run `npx @respan/cli setup` to set up with your coding agent.

#### Use Respan Gateway

See [OpenAI SDK gateway setup](/docs/gateway/openai-sdk) to route this integration through the Respan gateway.

#### Example projects

* [Python examples](https://github.com/respanai/respan/tree/main/python-sdks/examples/openai-sdk)
* [TypeScript examples](https://github.com/respanai/respan/tree/main/javascript-sdks/examples/openai-sdk)

## Setup

#### Install packages

```bash Python
pip install respan-ai respan-instrumentation-openai openai
```

```bash TypeScript
npm install @respan/respan @respan/instrumentation-openai openai
```

#### Set environment variables

```bash
export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"
export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
```

`OPENAI_API_KEY` is used for OpenAI requests. `RESPAN_API_KEY` is used to export traces to Respan.

#### Initialize and run

```python Python
from openai import OpenAI
from respan import Respan
from respan_instrumentation_openai import OpenAIInstrumentor

respan = Respan(instrumentations=[OpenAIInstrumentor()])

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4.1-nano",
    messages=[{"role": "user", "content": "Say hello in three languages."}],
)
print(response.choices[0].message.content)
```

```typescript TypeScript
import OpenAI from "openai";
import { Respan } from "@respan/respan";
import { OpenAIInstrumentor } from "@respan/instrumentation-openai";

const respan = new Respan({
  apiKey: process.env.RESPAN_API_KEY,
  baseURL: process.env.RESPAN_BASE_URL,
  instrumentations: [new OpenAIInstrumentor()],
});
await respan.initialize();

const client = new OpenAI();

const response = await client.chat.completions.create({
  model: "gpt-4.1-nano",
  messages: [{ role: "user", content: "Say hello in three languages." }],
});
console.log(response.choices[0].message.content);
```

#### View your trace

Open the [Traces page](https://platform.respan.ai/platform/traces) to see your auto-instrumented LLM spans.

## Configuration

| Parameter             | Type           | Default | Description                                                        |
| --------------------- | -------------- | ------- | ------------------------------------------------------------------ |
| `api_key`             | `str \| None`  | `None`  | Falls back to `RESPAN_API_KEY` env var.                            |
| `base_url`            | `str \| None`  | `None`  | Falls back to `RESPAN_BASE_URL` env var.                           |
| `instrumentations`    | `list`         | `[]`    | Plugin instrumentations to activate (e.g. `OpenAIInstrumentor()`). |
| `customer_identifier` | `str \| None`  | `None`  | Default customer identifier for all spans.                         |
| `metadata`            | `dict \| None` | `None`  | Default metadata attached to all spans.                            |
| `environment`         | `str \| None`  | `None`  | Environment tag (e.g. `"production"`).                             |

## Attributes

### In Respan()

Set defaults at initialization — these apply to all spans.

```python Python
from respan import Respan
from respan_instrumentation_openai import OpenAIInstrumentor

respan = Respan(
    instrumentations=[OpenAIInstrumentor()],
    customer_identifier="user_123",
    metadata={"service": "chat-api", "version": "1.0.0"},
)
```

```typescript TypeScript
import { Respan } from "@respan/respan";
import { OpenAIInstrumentor } from "@respan/instrumentation-openai";

// Note: default attributes are set via propagateAttributes() in TypeScript
const respan = new Respan({
  instrumentations: [new OpenAIInstrumentor()],
});
```

### With propagate\_attributes

Override per-request using a context scope.

```python Python
from openai import OpenAI
from respan import Respan, propagate_attributes
from respan_instrumentation_openai import OpenAIInstrumentor

respan = Respan(instrumentations=[OpenAIInstrumentor()])
client = OpenAI()

def handle_request(user_id: str, question: str):
    with propagate_attributes(
        customer_identifier=user_id,
        thread_identifier="conv_abc_123",
        metadata={"plan": "pro"},
    ):
        response = client.chat.completions.create(
            model="gpt-4.1-nano",
            messages=[{"role": "user", "content": question}],
        )
        print(response.choices[0].message.content)
```

```typescript TypeScript
import OpenAI from "openai";
import { Respan } from "@respan/respan";
import { OpenAIInstrumentor } from "@respan/instrumentation-openai";

const respan = new Respan({
  instrumentations: [new OpenAIInstrumentor()],
});
await respan.initialize();

const client = new OpenAI();

async function handleRequest(userId: string, question: string) {
  await respan.propagateAttributes(
    {
      customer_identifier: userId,
      thread_identifier: "conv_abc_123",
      metadata: { plan: "pro" },
    },
    async () => {
      const response = await client.chat.completions.create({
        model: "gpt-4.1-nano",
        messages: [{ role: "user", content: question }],
      });
      console.log(response.choices[0].message.content);
    }
  );
}
```

| Attribute             | Type   | Description                                           |
| --------------------- | ------ | ----------------------------------------------------- |
| `customer_identifier` | `str`  | Identifies the end user in Respan analytics.          |
| `thread_identifier`   | `str`  | Groups related messages into a conversation.          |
| `metadata`            | `dict` | Custom key-value pairs. Merged with default metadata. |

## Decorators (optional)

Decorators are not required. All OpenAI calls are auto-traced by the instrumentor. Use `@workflow` and `@task` (Python) or `withWorkflow` and `withTask` (TypeScript) to add structure when you want to group related calls into a named workflow with nested tasks.

```python Python
from openai import OpenAI
from respan import Respan, workflow, task
from respan_instrumentation_openai import OpenAIInstrumentor

respan = Respan(instrumentations=[OpenAIInstrumentor()])
client = OpenAI()

@task(name="generate_outline")
def outline(topic: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4.1-nano",
        messages=[
            {"role": "system", "content": "Create a brief outline."},
            {"role": "user", "content": topic},
        ],
    )
    return response.choices[0].message.content

@workflow(name="content_pipeline")
def pipeline(topic: str):
    plan = outline(topic)
    response = client.chat.completions.create(
        model="gpt-4.1-nano",
        messages=[
            {"role": "system", "content": "Write content from this outline."},
            {"role": "user", "content": plan},
        ],
    )
    print(response.choices[0].message.content)

pipeline("Benefits of API gateways")
```

```typescript TypeScript
import OpenAI from "openai";
import { Respan } from "@respan/respan";
import { OpenAIInstrumentor } from "@respan/instrumentation-openai";

const respan = new Respan({
  instrumentations: [new OpenAIInstrumentor()],
});
await respan.initialize();

const client = new OpenAI();

async function outline(topic: string) {
  return respan.withTask({ name: "generate_outline" }, async () => {
    const response = await client.chat.completions.create({
      model: "gpt-4.1-nano",
      messages: [
        { role: "system", content: "Create a brief outline." },
        { role: "user", content: topic },
      ],
    });
    return response.choices[0].message.content!;
  });
}

async function pipeline(topic: string) {
  return respan.withWorkflow({ name: "content_pipeline" }, async () => {
    const plan = await outline(topic);
    const response = await client.chat.completions.create({
      model: "gpt-4.1-nano",
      messages: [
        { role: "system", content: "Write content from this outline." },
        { role: "user", content: plan },
      ],
    });
    console.log(response.choices[0].message.content);
  });
}

await pipeline("Benefits of API gateways");
```

## Examples

### Streaming

Streaming responses are auto-traced like regular completions.

```python
stream = client.chat.completions.create(
    model="gpt-4.1-nano",
    messages=[{"role": "user", "content": "Write a haiku about Python."}],
    stream=True,
)

for chunk in stream:
    content = chunk.choices[0].delta.content
    if content:
        print(content, end="", flush=True)
```

### Tool calls

Function calling is auto-traced. Wrap the workflow with `@workflow` and `@task` decorators for a structured trace tree.

```python
import json
from openai import OpenAI
from respan import Respan, workflow, task
from respan_instrumentation_openai import OpenAIInstrumentor

respan = Respan(instrumentations=[OpenAIInstrumentor()])
client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the weather for a city.",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    }
]

@task(name="get_weather")
def get_weather(city: str) -> str:
    return f"Sunny, 72F in {city}"

@workflow(name="weather_assistant")
def run(question: str):
    messages = [{"role": "user", "content": question}]

    response = client.chat.completions.create(
        model="gpt-4.1-nano",
        messages=messages,
        tools=tools,
    )
    message = response.choices[0].message

    if message.tool_calls:
        messages.append(message)
        for tc in message.tool_calls:
            args = json.loads(tc.function.arguments)
            result = get_weather(**args)
            messages.append(
                {"role": "tool", "tool_call_id": tc.id, "content": result}
            )

        final = client.chat.completions.create(
            model="gpt-4.1-nano",
            messages=messages,
            tools=tools,
        )
        print(f"Answer: {final.choices[0].message.content}")

run("What's the weather in Paris?")
```

### Structured output

JSON mode with Pydantic models is auto-traced.

```python
from pydantic import BaseModel
from openai import OpenAI

client = OpenAI()

class MovieReview(BaseModel):
    title: str
    rating: int
    summary: str
    pros: list[str]
    cons: list[str]

response = client.beta.chat.completions.parse(
    model="gpt-4.1-nano",
    messages=[
        {"role": "system", "content": "You are a film critic. Rate movies 1-10."},
        {"role": "user", "content": "Review: The Matrix"},
    ],
    response_format=MovieReview,
)
result = response.choices[0].message.parsed
print(f"{result.title} - {result.rating}/10")
```