# LiveKit Agents

Install Brizz with the LiveKit Agents voice framework.

Quickstart for the LiveKit Agents integration shown during onboarding. For the full Python SDK reference, see the [Python SDK guide](/docs/sdks/python.md).

## Install

:::tabs
:::tab[pip]
```bash
pip install brizz
```
:::tab[uv]
```bash
uv add brizz
```
:::tab[poetry]
```bash
poetry add brizz
```
:::

LiveKit Agents emits OpenTelemetry spans natively, so no extra Brizz package or plugin is needed — Brizz picks them up as soon as it's initialized.

The examples below use LiveKit's OpenAI and Silero plugins. Your agent already has whichever plugins it needs; add these only if you're starting from the example:

:::tabs
:::tab[pip]
```bash
pip install "livekit-agents[openai,silero]"
```
:::tab[uv]
```bash
uv add "livekit-agents[openai,silero]"
```
:::tab[poetry]
```bash
poetry add "livekit-agents[openai,silero]"
```
:::

## Initialize and run

Brizz reads `BRIZZ_DSN` — create one under **Organization Settings → API Keys → Create Telemetry API Key → Server DSN** (see [Server DSN](/docs/admin/server-dsn.md)), then put it in your agent's environment:

```bash
export BRIZZ_DSN="https://<credential>@<ingest-host>/<service-name>"
```

```python
from dotenv import load_dotenv
load_dotenv()

from brizz import Brizz, astart_session

# LiveKit runs each job in its own process, which re-imports this module —
# initialize at import time so every job process is instrumented.
# Reads BRIZZ_DSN; pass dsn="..." to set it in code.
Brizz.initialize()

from livekit.agents import Agent, AgentSession, JobContext, WorkerOptions, cli
from livekit.plugins import openai, silero


async def entrypoint(ctx: JobContext):
    # Recommended: name the session yourself so you can attach attributes to it.
    async with astart_session(ctx.room.name):
        session = AgentSession(
            stt=openai.STT(),
            llm=openai.LLM(model="gpt-4o-mini"),
            tts=openai.TTS(),
            vad=silero.VAD.load(),
        )
        await session.start(
            room=ctx.room,
            agent=Agent(instructions="You are a helpful voice assistant."),
        )


if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```

## Complete example

A single file you can copy and set `BRIZZ_DSN` on (`.env` works — the example loads it with `load_dotenv()`). Run `python agent.py console` to talk to it in the terminal without a LiveKit room, or `python agent.py dev` once your LiveKit credentials are set.

It hands off from a greeter to a weather specialist, so the session renders one lane per agent.

```python
# agent.py
from dotenv import load_dotenv
load_dotenv()

from brizz import Brizz, astart_session, emit_event

# LiveKit runs each job in its own process, which re-imports this module —
# initialize at import time so every job process is instrumented.
Brizz.initialize()

from livekit.agents import Agent, AgentSession, JobContext, RunContext, WorkerOptions, cli, function_tool
from livekit.plugins import openai, silero


@function_tool
async def lookup_weather(context: RunContext, city: str) -> str:
    """Look up the current weather for a city."""
    return f"It is 21 degrees and sunny in {city}."


class WeatherAgent(Agent):
    """Answers weather questions."""

    def __init__(self) -> None:
        super().__init__(
            instructions="You are the weather specialist. Answer weather questions in one sentence.",
            tools=[lookup_weather],
        )


@function_tool
async def transfer_to_weather(context: RunContext) -> Agent:
    """Hand the conversation to the weather specialist."""
    return WeatherAgent()


class Greeter(Agent):
    """Greets the caller and hands weather questions to the specialist."""

    def __init__(self) -> None:
        super().__init__(
            instructions=(
                "You are the greeter. Greet the user in one sentence. "
                "Whenever the user asks about the weather, call transfer_to_weather."
            ),
            tools=[transfer_to_weather],
        )


async def entrypoint(ctx: JobContext):
    # Recommended: name the session yourself so you can attach attributes to it.
    async with astart_session(ctx.room.name):
        session = AgentSession(
            stt=openai.STT(),
            llm=openai.LLM(model="gpt-4o-mini"),
            tts=openai.TTS(),
            vad=silero.VAD.load(),
        )
        await session.start(room=ctx.room, agent=Greeter())

        # User feedback — map "feedback.positive" / "feedback.negative" to system
        # events in Org Settings -> Event so it powers filters and the Overview chart.
        # Call this wherever your room UI reports a thumbs-up/thumbs-down.
        emit_event("feedback.positive", attributes={"category": "helpfulness"})


if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```

**Gotchas**

- Call `Brizz.initialize()` at module import time, not inside `if __name__ == "__main__":`. LiveKit runs each job in its own process, which re-imports this module — initializing under the main guard means job processes never get instrumented.
- `async with astart_session(ctx.room.name)` inside the entrypoint is the recommended way to name a session and get a `Session` object for attributes, events, and feedback. Skip it and Brizz falls back to the room name automatically.
- Prefer the room name as the session id. Anything the agent starts inside the `astart_session` block keeps that id, but work kicked off after the block exits falls back to the room name — so a custom id can split one call across two sessions.
- Sending telemetry to Brizz doesn't stop LiveKit Cloud from receiving its own — both exports run side by side.
- Voice sessions render as conversations: system prompt, user turns, agent replies, tool calls with their results, and errors. Per-turn instructions passed to `generate_reply(instructions=…)` appear as context on the reply they shaped.
- A session that hands off between agents shows each agent's turns separately, with the system prompt and tools that agent ran under.
- A caller talking over the agent lands on the session timeline as an `agent_interrupted` event, on the turn it cut short.

## Speech-to-speech

An agent built on a realtime model swaps the STT → LLM → TTS trio for a single model. Everything else in the example stays the same, and sessions capture both sides of the conversation either way:

```python
session = AgentSession(llm=openai.realtime.RealtimeModel())
```

## See also

- [Python SDK](/docs/sdks/python.md) — full SDK reference.
- [Sessions](/docs/instrument/sessions.md) — session capture patterns.
- [Identify users](/docs/instrument/identify-users.md) — attach user properties to LiveKit runs.
