How do I attribute LangChain traces to end users?
Rius attributes LangChain traces to an end user when you run the chain inside a user scope, which stamps user.id on every span of the request. You use rius.user in Python or withUser in TypeScript. The Users page then lists each user with their traces, failed traces, cost and time to first token. Traces that run outside a scope count as anonymous.
How it works
A user scope is a block of code that carries one end user’s identifier. Every span started inside the block gets that identifier as the user.id attribute, including the spans the LangChain integration creates for chains, tools, retrievers and model calls. Rius reads the end user from user.id, enduser.id or enduser.pseudo.id and takes the first one that is set.
- The scope follows async work and nests, and the innermost scope wins.
- It combines with a session scope, so you can group a conversation and attribute it to a person at the same time.
observetakes no user argument, because a decorator is bound once at import time and a user belongs to each request. Put the scope around the request instead.- 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 end user on any span counts as anonymous.
The Users section of the tracing guide covers the scope in full, and the LangChain integration page covers what the chain spans contain.
Set it up
-
Install the SDK with the LangChain extra and call
init()once at startup. This is the same setup as any LangChain trace, so the LangChain integration page has the details, including the TypeScript install.pip install "glassflow-rius[langchain]" langchainimport rius rius.init(api_key="ri_...", service_name="my-agent") -
Wrap each request’s chain call in a user scope, passing the identifier your application already has for that person.
chainandrequesthere are your own names.import rius with rius.user(request.user_id): result = chain.invoke({"question": request.text}) # every span of the request carries user.idIn TypeScript, use
withUser:import { withUser } from '@glassflow-ai/rius' await withUser(request.userId, async () => { await chain.invoke({ question: request.text }) // every span of the request carries user.id }) -
Put the scope at the entry point of the request, such as your route handler, so that every chain, tool and model call below it is covered. Code paths that call LangChain outside a scope produce anonymous traces.
-
Choose 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.
-
Verify in one minute. Run one request inside a scope with a test ID such as
test-user-1. Open Users, typetest-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 people or accounts your agents serve, not the members of your Rius organization. Figures count only finished traces.
- The list: per user, their traces, failed traces and cost in the window (15m, 1h, 1d, 7d by default, or 30d), the agents that served them, and when they were last seen. Sort by traces, failed traces or cost. The list shows the top 50 for the chosen sort, and you reach anyone else by searching their exact user ID.
- Coverage: a line above the list says how many traces carry an end user, for example “400 of 471 traces carry an end user”. The anonymous row comes first. A large one means some code paths run outside a user scope.
- A user’s page: tiles for Traces, Failed traces, Error rate, Cost with the average per trace, Sessions, and TTFT p50 (median time to first token over their streaming calls).
- Context for the user: up to three agents that served them, their 8 most recent traces and their 6 latest traces with an errored span. Each opens the trace.
- Support questions: when a customer says it didn’t 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 clicking a user filters the list to them. The same filter is the
user_idfield in the filter builder. - Summaries: when root-cause analysis is on for the workspace, an admin can click Summarise this user for a written account of what that user experienced.
To see what a user costs compared with the rest, compare their cost per trace with the agent’s average on the Agents page. For the wider picture, see Monitor AI agents in production.
When something else fits
- If you want to group the turns of one conversation rather than attribute them to a person, use a session scope, and combine it with a user scope when you need both.
- Coding-agent sessions from the Rius plugin carry the developer’s sign-in email as
user.idwithout a scope, and OpenHands conversations take auser_idpassed toConversation, so neither needs the scope shown here.