Skip to Content

How do I group the traces of one conversation into a session with LiteLLM?

Rius groups the traces of one conversation into a session when you wrap each turn’s LiteLLM calls in rius.session(conversation_id), using the same id on every turn. The SDK stamps that id as session.id on every span started inside the block, so each turn stays its own trace and the session ties the turns together. The LiteLLM integration is Python only.

How it works

A trace is one causal unit of work, such as a single chat turn. A session is a correlation unit that you assign, so it can span many traces. You mint the id, or reuse one you already have such as a conversation id, and the SDK writes it as session.id on every span in scope. See Concepts for the data model.

  • Scope by block. rius.session(conversation_id) is a context manager. Each span your code starts inside it carries the id, including the span Rius creates for each litellm.completion call.
  • Nested spans carry it too. If the openai extra is also installed, LiteLLM’s call through the OpenAI client appears as a second model span under the LiteLLM span. That span is in scope too, so it carries the same id.
  • Scopes nest. The innermost session wins, and the scope ends with the block, even when an error is raised.
  • No id, no grouping. A trace with no session id is grouped as its own single-trace session. The SDK never generates a process-wide id for you.

Set it up

  1. Install the LiteLLM integration. The extra installs the instrumentation, not litellm itself. With a global rius.init(), the integration then turns on automatically. The LiteLLM integration page covers setup in full.

    pip install "glassflow-rius[litellm]" litellm
  2. Wrap each turn in a session scope that uses the conversation’s id. Pass the same id on every turn of that conversation.

    import rius import litellm rius.init(api_key="ri_...", service_name="my-agent") def handle_turn(conversation_id: str, message: str) -> str: with rius.session(conversation_id): response = litellm.completion( model="gpt-4o-mini", # or any provider LiteLLM routes to messages=[{"role": "user", "content": message}], ) return response.choices[0].message.content
  3. If one turn makes several LiteLLM calls or tool calls, put the whole turn inside one with rius.session(...) block. If you trace the turn with rius.observe, call the decorated function inside the block so its span is in scope too. Tracing your code shows observe and the other span helpers.

  4. To get a fresh id instead, call rius.session() with no argument. The block yields the id, so you can log it or pass it to the next turn.

    with rius.session() as session_id: handle_turn(session_id, "hello")
  5. For a script or batch job that handles exactly one session per process, set the id once at startup with init(session_id=...) or the RIUS_SESSION_ID environment variable. An active session scope overrides it. Do not use this in a server that handles many users, because every conversation would merge into one session. Scope each request instead.

  6. If you call a LiteLLM proxy from TypeScript through the OpenAI client, wrap those calls with withSession(conversationId, async () => { ... }) from @glassflow-ai/rius. The OpenAI integration traces the calls, and the scope stamps the id on their spans.

What you get

  • One session per conversation. Each turn is its own trace, and the shared session.id ties the turns together.
  • Spans you did not write carry the id. LiteLLM’s spans, and any nested OpenAI client span, pick it up from the scope.
  • A session independent of the process. A conversation that outlives a deploy or moves between replicas stays one session, because the grouping rides on the traces.
  • A quick check that grouping works. Compare an agent’s session count with its trace count. If the two are equal, nothing is setting session.id, and the sessions are single-trace groups, not real conversations.
  • Model analytics and cost per call as before. Cost is computed from the model and token counts, so you send no cost value. See Monitor AI agents in production for how sessions fit into watching an agent over time.

Run two turns with the same conversation id, then open the traces in the console. They share the session id, and the model, token counts and cost show on each LiteLLM span.

When something else fits

  • If your process serves exactly one conversation or job, such as a CLI run, a process-wide RIUS_SESSION_ID is simpler than a scope around every call.
  • If you run LiteLLM as a proxy server, it is a separate process. Instrument the proxy itself, or send its OpenTelemetry traces through the interoperability path, and set the session id where the proxy’s spans are created.

Still stuck? Get help

Last updated on