Skip to Content

How do I attribute the Vercel AI SDK traces to end users?

Rius attributes Vercel AI SDK traces to end users when you wrap your calls in withUser, which stamps user.id on every span started inside the scope. The Users page then shows each user’s traces, failures, cost and time to first token. A call made outside any user scope is anonymous.

How it works

A user scope carries your own identifier for the person a request served. Wrap each request in withUser(userId, callback) from @glassflow-ai/rius. Every span started inside the callback carries user.id, including spans created by auto-instrumentation. The Vercel AI SDK integration is one of those, so the agent span, the chat span for each model call and the execute_tool spans of a generateText call all fall under the same user.

  • Per request. A user is a property of each request, so scope it around the request. observe takes no user argument for this reason.
  • Nesting. Scopes nest, and the innermost wins. They follow async work and combine with a session scope from withSession.
  • No default. There is no process-wide user and no environment variable for it, because a global user would attribute every request to one person.
  • Anonymous. A span outside any user scope has no end user. A trace with no end user on any span counts as anonymous.

Rius also reads enduser.id and enduser.pseudo.id, and takes the first of user.id, enduser.id and enduser.pseudo.id that is set. With the SDK, withUser is the way to set it. For the full user and session scopes, see Tracing your code.

Set it up

  1. Install the SDK. Rius needs @ai-sdk/otel only on ai 7, which also needs Node 22 or newer. On ai 5 and 6 there is nothing more to install. Setup for each ai version is covered in Vercel AI SDK.
npm install @glassflow-ai/rius @opentelemetry/api npm install @ai-sdk/otel
  1. Initialize Rius once, and await ready so the integration is attached before the first call. Then wrap the work for each request in withUser. Pass an opaque ID from your own system, such as an account ID, rather than 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.
import { init, withUser } from '@glassflow-ai/rius' import { openai } from '@ai-sdk/openai' import { generateText } from 'ai' const rius = init({ apiKey: 'ri_...', serviceName: 'my-agent' }) await rius.ready let text = '' await withUser('test-user-1', async () => { const result = await generateText({ model: openai('gpt-4o-mini'), prompt: 'Summarize this ticket', }) text = result.text }) await rius.shutdown() // export queued spans before the process exits
  1. On ai 5 and 6, also pass experimental_telemetry: { isEnabled: true } on each call. Those versions emit no spans without it, so the user scope would have nothing to stamp.

  2. In a real server, pass the ID from the request, for example request.userId, instead of the fixed test value. Keep every code path that calls the AI SDK inside the scope. Any path outside it lands in the anonymous row.

  3. Verify. 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, not the members of your Rius organization. Its figures count only finished traces.

  • The list. For a window of 15m, 1h, 1d, 7d (the default) or 30d, each user shows traces, failed traces, cost, the agents that served them and when they were last seen. Sort by traces, failed traces or cost to find who drives cost or hits failures. The list shows the top 50 users for the chosen sort, so search by exact user ID to reach anyone else.
  • Coverage. A line above the list says how many traces in the window carry an end user, for example “400 of 471 traces carry an end user”. The anonymous row comes first. A large anonymous row means some AI SDK calls run outside a user scope.
  • One user’s page. Tiles for Traces, Failed traces, Error rate, Cost with average per trace, Sessions and TTFT p50. Streamed AI SDK calls carry time to first token, so that tile has data for them. Below the tiles you see up to three agents that served the user, their 8 most recent traces and their 6 latest traces with an errored span.
  • Support. When a customer says it did not work, search their exact ID, open 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_id field in the filter builder.

To see how this fits with agent health and cost, read Monitor AI agents in production.

When something else fits

If the AI SDK calls run in a process that handles one person only, such as a single-user script, you still scope the user, because Rius has no process-wide user setting. If you want to group the turns of one conversation rather than everything one person did, use withSession instead, or alongside withUser.

Still stuck? Get help

Last updated on