Which OpenTelemetry attribute should my spans use for conversation id?
Rius reads gen_ai.conversation.id, the OpenTelemetry GenAI convention’s key, as the session when session.id is absent. Spans that follow the GenAI conventions therefore group into conversations without extra work. If you also send session.id, Rius reads that one first. Set the same value on every span of the trace, not only the root.
How it works
Rius groups traces into sessions, such as a conversation or an agent run. It reads the session for each span in this order:
session.id, the session key used by OpenInference and by the Rius SDKs.gen_ai.conversation.id, the key the OpenTelemetry GenAI conventions define for a conversation.- The trace id, when neither is set. The trace is then its own session.
Rius reads the session per span, not per trace. A child span that lacks the attribute falls back to the next option in the list, even when the root span has it. That is why you set the value on every span.
Rius also maps one other sender’s session key for you. Laminar’s lmnr.association.properties.session_id is normalized at ingest, so Laminar spans group into sessions without changes. Where you control the instrumentation, send gen_ai.conversation.id or session.id directly.
The full list of attributes Rius reads is on the span attribute reference.
Set it up
- Pick one key. Use
gen_ai.conversation.idif you follow the GenAI conventions. Usesession.idif you already send it from OpenInference or the Rius SDKs. If you send both, give them the same value. - Generate one stable string for each conversation, such as the id of your chat thread, and reuse it on every turn of that conversation.
- Stamp that value on every span in the trace: the root, each model call, each tool call. A span without it falls back to the trace id.
- If you use the Rius SDKs, skip the manual stamping. In Python,
rius.sessionsetssession.idfor the spans inside its scope. In TypeScript, usewithSession. For a whole process, setRIUS_SESSION_ID. - If you send spans from a plain OpenTelemetry SDK or a collector, send them to the ingest endpoint with your API key. The setup for each sender is in Trace LLM calls with OpenTelemetry.
This is what a model-call span looks like with the GenAI key. The values are examples that you replace with your own:
span name: chat gpt-4o
gen_ai.operation.name = chat
gen_ai.request.model = gpt-4o
gen_ai.usage.input_tokens = 812
gen_ai.usage.output_tokens = 164
gen_ai.conversation.id = conv_8f3a
user.id = u_1042The next turn of the same conversation is a new trace with a new trace id, but it carries the same gen_ai.conversation.id, so Rius groups the two traces into one session.
Send the span to the OTLP/HTTP endpoint:
POST https://ingest.eu.console.rius-glassflow.com/v1/traces
Authorization: Bearer <your API key>
Content-Type: application/x-protobufThe address is https://ingest.eu.console.rius-glassflow.com/v1/traces, and the region in the host is your workspace’s region.
What you get
- Traces that share a conversation id are grouped into one session, so a multi-turn conversation reads as one unit and not as unrelated traces.
- Spans that already follow the GenAI conventions group correctly without adding
session.id. - Cost is computed on each span from the model and token counts, so you never send a cost attribute. Send
gen_ai.request.modeland thegen_ai.usage.*token counts, as in the example span. - If you set
user.id,enduser.idorenduser.pseudo.id, Rius also attributes the requests to a user. A span with none of them counts as anonymous. - A missing conversation id does not cause an error. The trace becomes its own session.
When something else fits
- If you use the Rius SDKs, use their session scope or
RIUS_SESSION_IDinstead of stamping spans by hand. - If a session is a single agent run, not a multi-turn conversation, a per-run
session.idis enough, and you can leavegen_ai.conversation.idout.