Skip to Content

How do I attribute LlamaIndex traces to end users?

Rius attributes LlamaIndex traces to an end user when you run your queries inside a user scope with rius.user, which stamps user.id on every span of the request. The Users page then shows each user’s traces, failures, cost and time to first token. You do not change how you build your indexes or query engines.

How it works

The Rius SDK stamps the identifier you pass as the user.id attribute on every span started inside the scope. That includes the spans the LlamaIndex integration creates automatically: the retriever spans, the embedding spans and the model calls inside a query. The scope rides OpenTelemetry context, so it nests (the innermost wins), unwinds when the block ends, and follows async work.

A few rules shape where you put it:

  • Scope the user around each request, not at startup. observe takes no user argument, because a decorator is bound once at import time and a user belongs to one request.
  • There is no process-wide default and no environment variable for the user. A global user would attribute every request a process handles to one person.
  • A trace with no user on any span counts as anonymous. Nothing is minted for you.
  • The user scope combines freely with a session scope, so you can group a conversation’s turns and also tie them to the person.

The LlamaIndex integration is Python only, so this page uses rius.user. See LlamaIndex for what the integration captures, and Tracing your code for user and session scopes in full.

Set it up

  1. Install the SDK with the LlamaIndex extra and call init() once at startup. The extra installs the instrumentation, not llama-index itself.
pip install "glassflow-rius[llama-index]" llama-index
import rius rius.init(api_key="ri_...", service_name="my-agent")
  1. Wrap the code that handles one request in rius.user, passing the identifier your application already has for that person. Here request and query_engine stand for your own objects.
import rius with rius.user(request.user_id): response = query_engine.query(request.question)
  1. Make sure every code path that calls LlamaIndex runs inside such a scope. Background jobs, scheduled tasks and anything outside a request handler need their own rius.user block, or their traces land in the anonymous row.

  2. Pass an opaque identifier, not an email or a name. The value is stored on every span and shown in the console. If only sensitive identifiers exist, hash them first.

  3. Verify in one minute. Run one query inside a scope with a test ID such as test-user-1. Open Users, type test-user-1, click Find, and open the row. Once the trace has finished, the page shows 1 trace, your agent under Agents that served this user, and the trace under Recent traces.

What you get

The Users page is a register of the end users your agents serve. It counts only finished traces, so a run appears once every span of it has ended. For each user it gives you:

  • The list. Traces, failed traces and cost in the window (15m, 1h, 1d, 7d by default, or 30d), the agents that served the user, and when they were last seen. Sort by traces, failed traces or cost. The list shows the top 50 users for the chosen sort, so search by exact user ID to reach anyone else.
  • The anonymous row. It always comes first and opens the trace list filtered to traces with no end user. A line above the list says how many of the window’s traces carry a user, for example 400 of 471. A large anonymous row means some LlamaIndex code runs outside a user scope.
  • A user’s page. Tiles for traces, failed traces, error rate, cost (total and average per trace), sessions and TTFT p50. Below them are the agents that served the user, the 8 most recent traces and the 6 latest failed traces. A row opens the trace, where the nested LlamaIndex spans show the retrieval step and the model call.
  • Support lookups. When a customer says it did not work, search their exact ID, check Failed traces, and open the trace from the time they wrote in.
  • Filtering. The trace list has a User column, and the user_id field is available in the filter builder.

For wider monitoring across agents, see Monitor AI agents in production.

When something else fits

  • If you build with LlamaIndex.TS, there is no LlamaIndex integration in TypeScript, so query-engine and retriever structure is not captured. The TypeScript SDK has withUser for the same scope around the provider calls it traces.
  • If you need a user on one span with no children, user_id= on the span and generation helpers does the same job without a block.
  • If you only need to group the turns of one conversation, a session scope is the better fit, because a session is not tied to a person.

Still stuck? Get help

Last updated on