# Python SDK

Install, initialize, and add sessions/events in Python.

The Brizz Python SDK provides automatic instrumentation for your AI applications, capturing traces, metrics, and logs with minimal configuration. It is fully compatible with OpenTelemetry.

## Installation

```bash
pip install brizz
```

## Initialization

To ensure all libraries are correctly instrumented, **initialize Brizz before importing other AI libraries**.

```python
import os
from brizz import Brizz

# 1. Initialize Brizz
Brizz.initialize(
    api_key=os.environ.get("BRIZZ_API_KEY"),
    app_name="my-ai-app",
)

# 2. Import your AI libraries (OpenAI, LangChain, etc.)
from openai import OpenAI
```

:::warning Import Order
If you use `python-dotenv`, load your environment variables **before** importing `brizz`.
:::

## Sessions

Sessions are the core unit of analysis in Brizz. They group related operations (like a multi-turn chat) into a single entity.

### Basic Usage

Use the `start_session` context manager to wrap your agent's execution.

```python
from brizz import start_session

with start_session("session-123"):
    # All LLM calls and events here are linked to session-123
    response = openai.chat.completions.create(...)
```

### Adding Metadata

You can attach user IDs and other custom properties to a session. This is highly recommended for filtering and user journey analysis.

```python
with start_session("session-123") as session:
    session.update_properties(
        user_id="user-42",
        plan="enterprise",
        feature="support-bot"
    )

    # Your agent logic...
```

### Per-Turn Context

Pass kwargs to `set_input` / `set_output` to attach metadata to a single turn. Each turn's bag is rendered under a collapsible **Context** panel on that message in the dashboard.

```python
with start_session("session-123") as session:
    session.set_input("Why is my bill high?", selected_invoice="INV-9182")
    reply = openai.chat.completions.create(...)
    session.set_output(
        reply.choices[0].message.content,
        message_id="msg-42",
        sources=["doc-abc"],
    )
```

### Session Title Generation

Wrap LLM-based title generation so it doesn't appear in the conversation:

```python
from brizz import start_session, start_session_title

with start_session("session-123") as session:
    response = openai.chat.completions.create(...)

    with start_session_title() as title:
        t = openai.chat.completions.create(model="gpt-4", messages=[...])
        title.set_title(t.choices[0].message.content)
```

You can also use `start_session("session-123", mode="title")` to mark an entire session scope as title generation.

### Muting internal calls

Some LLM calls — classification, routing, guardrail checks, internal summarization — aren't part of the conversation your user had. Wrap them so their content is left out of the captured conversation. The call still runs and its latency/tokens/cost are recorded; only the user prompt, assistant reply, and tool calls are dropped.

```python
import brizz

# Keep an internal call out of the conversation (everything).
with brizz.mute():
    summary = agent.run("Summarize this conversation for internal logging.")

# Hide the text but keep the tool calls.
with brizz.mute(tools=False):
    reply = agent.run("…a prompt you'd rather not store…")
```

See [Mute messages](/docs/instrument/mute.md) for one-sided muting, muting tool calls on their own, async (`amute`), and reuse.

### Accessing the Session from Anywhere

Use `get_active_session` to retrieve the current session from nested functions without passing it as a parameter:

```python
from brizz import get_active_session, start_session

def log_step(step_name: str):
    session = get_active_session()
    if session:
        session.update_properties(last_step=step_name)

with start_session("session-123") as session:
    log_step("start")  # Accesses the session automatically
    response = openai.chat.completions.create(...)
```

Returns `None` when called outside a session scope.

### Async Support

For asynchronous applications, use `astart_session`.

```python
from brizz import astart_session

async with astart_session("session-123") as session:
    session.update_properties(user_id="user-42")
    await agent.run(...)
```

## Custom Events

Emit custom events to track specific business logic or user actions that aren't automatically captured.

```python
from brizz import emit_event

emit_event(
    "tool.search.completed",
    attributes={"query": "pricing", "results_count": 5},
    body={"top_result": "Pricing Page"}
)
```

## Configuration

You can configure the SDK via environment variables or the `initialize` method.

| Environment Variable | Description |
|----------------------|-------------|
| `BRIZZ_API_KEY` | Your Brizz API Key (Required) |
| `BRIZZ_APP_NAME` | Name of your application |
| `BRIZZ_ENVIRONMENT` | Deployment environment (e.g., `production`, `staging`) |

### PII Masking

Brizz can automatically mask sensitive information (emails, phone numbers, keys) in your traces.

```python
Brizz.initialize(
    api_key=os.environ["BRIZZ_API_KEY"],
    masking=True,  # Enable default masking rules
)
```

## Integrations

### Langfuse

If you already use Langfuse, see the [Langfuse SDK page](/docs/sdks/langfuse.md) — Brizz ingests Langfuse OTel spans directly, and the page covers init, session grouping, and per-turn context.

### LangSmith

If you use LangChain + LangSmith, route LangSmith's per-run telemetry through Brizz's TracerProvider. Brizz stays out of LangChain instrumentation and ingests LangSmith's OTel-native spans.

```python
from dotenv import load_dotenv
load_dotenv()

import os
os.environ.setdefault("LANGSMITH_TRACING", "true")
os.environ.setdefault("LANGSMITH_OTEL_ENABLED", "true")

from brizz import Brizz

# Initialize BEFORE importing langchain/langsmith so Brizz's TracerProvider
# is the global one. LangSmith's OTel exporter reuses it.
Brizz.initialize(
    api_key=os.getenv("BRIZZ_API_KEY"),
    app_name="my-app",
    allowed_instrumentations=[],
)
```

Wrap your LangChain calls in a Brizz session to group them:

```python
from brizz import start_session, astart_session
from langchain_core.messages import HumanMessage

# `llm` is your existing LangChain chat model (ChatOpenAI, ChatAnthropic, etc.).

with start_session("chat-session-123"):
    response = llm.invoke([HumanMessage(content="Hello")])

# Async equivalent
async with astart_session("chat-session-123"):
    response = await llm.ainvoke([HumanMessage(content="Hello")])

# Alternative — no start_session needed:
# Pass thread_id (or session_id / conversation_id) in LangChain's per-call
# metadata. Brizz reads langsmith.metadata.thread_id and groups the calls
# into the same Brizz session automatically.
response = llm.invoke(
    [HumanMessage(content="Hello")],
    config={"metadata": {"thread_id": "chat-session-123"}},
)
```

## Complete example

A single file you can copy, set `BRIZZ_API_KEY` + `OPENAI_API_KEY`, and run with `python main.py`.

```python
# main.py
# Load env vars BEFORE importing brizz so BRIZZ_API_KEY is available at init time.
from dotenv import load_dotenv
load_dotenv()

import os

# Initialize Brizz BEFORE importing any AI libraries (openai, langchain, anthropic, ...).
# Auto-instrumentation hooks them at import time — wrong order and spans go missing.
from brizz import Brizz, start_session, emit_event

Brizz.initialize(
    api_key=os.environ["BRIZZ_API_KEY"],
    app_name="my-ai-app",
    environment=os.getenv("APP_ENV", "development"),
    masking=True,  # mask emails / phones / API keys; flip off if you handle PII yourself
)

# Now safe to import AI libs.
from openai import OpenAI
client = OpenAI()


def run_turn(session_id: str, user_id: str, user_message: str) -> str:
    with start_session(session_id) as session:
        # Session-wide properties — show up on every span in the session.
        session.update_properties(user_id=user_id, plan="enterprise")

        # set_input attaches the user message + per-turn context (kwargs render
        # under the collapsible "Context" panel on that message).
        session.set_input(user_message, selected_invoice="INV-9182")

        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": user_message}],
        )
        reply = response.choices[0].message.content

        # set_output stamps the assistant reply + per-turn metadata.
        session.set_output(reply, message_id=response.id, sources=["doc-abc"])

        # User feedback — stays linked to this session via the active scope.
        # Map "feedback.positive" / "feedback.negative" to system events in
        # Org Settings -> Event so it powers filters and the Overview chart.
        emit_event(
            "feedback.positive",
            attributes={"category": "helpfulness"},
            body={"comment": "Exactly what I needed!", "context": "billing-assistant"},
        )
        return reply


if __name__ == "__main__":
    print(run_turn("session-123", "user-42", "Why is my bill high?"))
```

**Gotchas**

- Load `.env` before importing `brizz`.
- Call `Brizz.initialize()` before importing OpenAI / LangChain / Anthropic.
- For async code, use `astart_session(...)` (the `with` becomes `async with`).
- Routing through Langfuse or LangSmith? Pass `allowed_instrumentations=[]` (see the [Langfuse SDK page](/docs/sdks/langfuse.md)).

## See also

- [Sessions](/docs/instrument/sessions.md) — capture and enrich sessions in depth.
- [Custom events](/docs/instrument/custom-events.md) — emit business events and feedback from Python.
- [Record metrics](/docs/instrument/record-metric.md) — report your own eval scores and measurements with `record_metric`.
- [Identify users](/docs/instrument/identify-users.md) — attach user properties so analytics roll up by person.
- [PII & privacy](/docs/instrument/pii-and-privacy.md) — masking configuration.
- [Mute messages](/docs/instrument/mute.md) — keep internal or unrelated calls out of the captured conversation.
