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