# OpenAI SDK (Python)

The InteractiveAI SDK provides a drop-in replacement for the OpenAI Python SDK that enables full observability by changing a single import line. This integration works with both OpenAI and Azure OpenAI.

```diff
- import openai
+ from interactiveai.openai import openai
```

Alternative imports:

```python
from interactiveai.openai import OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI
```

InteractiveAI automatically captures:

* All prompts and completions, including streaming, async, and function calls
* Latencies
* API errors
* Model usage (tokens) and cost (USD)

The integration is fully interoperable with the `@observe()` decorator and the low-level tracing SDK.

***

### How It Works

#### 1. Install InteractiveAI SDK

The integration requires OpenAI SDK version `>=0.27.8`. Streaming and async support require OpenAI SDK `>=1.0.0`.

```bash
pip install interactiveai openai
```

#### 2. Switch to InteractiveAI Wrapped OpenAI SDK

Add your InteractiveAI credentials as environment variables. Obtain your project keys from **Settings > API Keys** in the InteractiveAI platform.

```
INTERACTIVEAI_SECRET_KEY="sk-..."
INTERACTIVEAI_PUBLIC_KEY="pk-..."
INTERACTIVEAI_BASE_URL="https://app.interactive.ai"
```

Replace the OpenAI import:

```diff
- import openai
+ from interactiveai.openai import openai
```

Alternative imports:

```python
from interactiveai.openai import OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI
```

Optionally, verify the connection to the server. Not recommended for production usage.

```python
from interactiveai import get_client

get_client().auth_check()
```

#### 3. Use the OpenAI SDK as Usual

No changes to your existing OpenAI code are required.

***

### Troubleshooting

#### Queuing and Batching

The InteractiveAI SDK queues and batches events in the background to minimize the impact on application performance. In long-running applications, this works without additional configuration.

For short-lived processes such as scripts or serverless functions, flush pending events before the process exits:

```python
from interactiveai import get_client
from interactiveai.openai import openai

interactiveai = get_client()
interactiveai.flush()
```

#### Debug Mode

Enable debug mode for more detailed logging when troubleshooting integration issues.

```python
from interactiveai import Interactive
from interactiveai.openai import openai

interactiveai = Interactive(debug=True)
```

Or via environment variable:

```
export INTERACTIVEAI_DEBUG=true
```

#### Sampling

Control the volume of traces sent to the server by adjusting the sample rate.

```python
from interactiveai import Interactive
from interactiveai.openai import openai

interactiveai = Interactive(sample_rate=0.1)
```

Or via environment variable:

```
export INTERACTIVEAI_SAMPLE_RATE=0.1
```

#### Disabling Tracing

Tracing can be disabled entirely when needed.

```python
from interactiveai import Interactive
from interactiveai.openai import openai

interactiveai = Interactive(tracing_enabled=False)
```

Or via environment variable:

```
export INTERACTIVEAI_TRACING_ENABLED=false
```

***

### Advanced Usage

#### Custom Trace Properties

You can pass additional properties directly in OpenAI method calls to enrich the generated traces:

| Property                | Description                                                             |
| ----------------------- | ----------------------------------------------------------------------- |
| `name`                  | Identifies a specific type of generation.                               |
| `metadata`              | Dictionary with additional information visible in InteractiveAI.        |
| `trace_id`              | Associates the generation with an existing trace.                       |
| `parent_observation_id` | Nests the generation under a specific observation. Requires `trace_id`. |

#### Setting Trace Attributes

To attach `session_id`, `user_id`, and `tags` to your traces, use `propagate_attributes` within an enclosing span:

```python
from interactiveai import get_client, propagate_attributes
from interactiveai.openai import openai

interactiveai = get_client()

with interactiveai.start_as_current_observation(as_type="span", name="calculator-request") as span:
    with propagate_attributes(
        session_id="session_123",
        user_id="user_456",
        tags=["calculator"]
    ):
        result = openai.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "You are a very accurate calculator."},
                {"role": "user", "content": "1 + 1 = "}
            ],
            name="test-chat",
            metadata={"someMetadataKey": "someValue"},
        )
```

#### Grouping Multiple Calls into a Trace

By default, the integration creates a separate trace for each OpenAI call. To group multiple calls under a single trace, add non-OpenAI observations, or have full control over the trace structure, wrap your logic with a context manager or the `@observe()` decorator.

{% tabs %}
{% tab title="Context Manager" %}

```python
from interactiveai import get_client, propagate_attributes
from interactiveai.openai import openai

interactiveai = get_client()

with interactiveai.start_as_current_observation(as_type="span", name="capital-poem-generator") as span:
    with propagate_attributes(
        user_id="user_123",
        session_id="session_456",
        tags=["poetry", "capital"]
    ):
        capital = openai.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "What is the capital of the country?"},
                {"role": "user", "content": "Bulgaria"}
            ],
            name="get-capital",
        ).choices[0].message.content

        poem = openai.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "You are a poet. Create a poem about this city."},
                {"role": "user", "content": capital}
            ],
            name="generate-poem",
        ).choices[0].message.content
```

{% endtab %}

{% tab title="@observe() Decorator " %}

```python
from interactiveai import observe
from interactiveai.openai import openai

@observe()
def capital_poem_generator(country):
    capital = openai.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "What is the capital of the country?"},
            {"role": "user", "content": country}
        ],
        name="get-capital",
    ).choices[0].message.content

    poem = openai.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a poet. Create a poem about this city."},
            {"role": "user", "content": capital}
        ],
        name="generate-poem",
    ).choices[0].message.content

    return poem

capital_poem_generator("Bulgaria")
```

{% endtab %}
{% endtabs %}

#### Token Usage on Streamed Responses

OpenAI returns token usage on streamed responses only when `include_usage` is set to `True` in `stream_options`. When enabled, OpenAI sends a final chunk with an empty `choices` list containing the usage data. Ensure your application handles these empty chunks to avoid index errors.

```python
from interactiveai import get_client
from interactiveai.openai import openai

client = openai.OpenAI()

stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "How are you?"}],
    stream=True,
    stream_options={"include_usage": True},
)

result = ""

for chunk in stream:
    if chunk.choices:
        result += chunk.choices[0].delta.content or ""

get_client().flush()
```

#### Structured Output

For structured output parsing, use the `response_format` argument on `openai.chat.completions.create()` rather than the Beta API. This approach preserves full compatibility with InteractiveAI trace attributes and metadata.

If your workflow relies on Pydantic definitions for `response_format`, the `type_to_response_format_param` utility from the OpenAI SDK converts Pydantic models to the expected dictionary format:

```python
from interactiveai import get_client
from interactiveai.openai import openai
from openai.lib._parsing._completions import type_to_response_format_param
from pydantic import BaseModel

class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

completion = openai.chat.completions.create(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "Extract the event information."},
        {"role": "user", "content": "Alice and Bob are going to a science fair on Friday."},
    ],
    response_format=type_to_response_format_param(CalendarEvent),
)

print(completion)

get_client().flush()
```

#### Error Tracking

The integration automatically captures OpenAI API errors and surfaces them through the `level` and `status_message` fields in the InteractiveAI dashboard.

```python
from interactiveai.openai import openai

# Force an error by pointing to a non-existent host
openai.base_url = "https://abc123.com"

country = openai.chat.completions.create(
    name="will-error",
    model="gpt-3.5-turbo",
    messages=[
        {"role": "user", "content": "How are you?"}
    ],
)
```

Failed requests appear in the dashboard with error details, enabling monitoring of API reliability across your application.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.interactive.ai/integrations/model-providers/openai-sdk-python.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
