How do I set the conversation id attribute on an OpenTelemetry span in Python?
Rius reads the OpenTelemetry GenAI key gen_ai.conversation.id as the session of a span, so you set it with span.set_attribute("gen_ai.conversation.id", your_id). Every trace and span that carries the same value groups into one session in Rius. Set it on every span of the trace, not only the root, because Rius reads it span by span. The key is part of the OpenTelemetry GenAI conventions, so you need no Rius-specific code.
How it works
Rius resolves the session of each span from three places, in this order:
session.id, if the span carries it.gen_ai.conversation.id, ifsession.idis absent.- The trace id, if neither is set. Each trace is then its own session.
So spans that follow the GenAI conventions group into sessions without any session.id. If you send both keys, session.id is the one Rius reads first.
The lookup happens per span. A root span with the conversation id and child spans without it leaves those children falling back to their trace id. Stamp the id on each span you create, including the model call and tool spans under the root.
The value is a string. Use any stable identifier that your application already has for the conversation, such as a chat thread id. Prefer an opaque id to anything that identifies a person.
This page covers a plain OpenTelemetry setup in Python. If you use the Rius SDK instead, its session scope (rius.session) stamps session.id on the spans for you, and you do not set the attribute by hand.
Set it up
- Register a tracer provider that exports to Rius. This is the standard OpenTelemetry Python setup. The full configuration, including the endpoint, protocol and headers, is on the vanilla OpenTelemetry SDKs page. In short, install the SDK and the http/protobuf exporter, set the
OTEL_EXPORTER_OTLP_*variables, and register the provider:
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-httpfrom opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# With no arguments, the exporter reads the OTEL_EXPORTER_OTLP_* variables
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)- Set the conversation id on each span. Call
set_attributewithgen_ai.conversation.idon the root span and on every child span. Hereconversation_idis your own variable:
from opentelemetry import trace
tracer = trace.get_tracer("my-agent")
conversation_id = "thread-8841" # your own conversation identifier
with tracer.start_as_current_span("invoke_agent planner") as agent_span:
agent_span.set_attribute("openinference.span.kind", "AGENT")
agent_span.set_attribute("gen_ai.operation.name", "invoke_agent")
agent_span.set_attribute("gen_ai.conversation.id", conversation_id)
with tracer.start_as_current_span("chat gpt-4o") as llm_span:
llm_span.set_attribute("openinference.span.kind", "LLM")
llm_span.set_attribute("gen_ai.operation.name", "chat")
llm_span.set_attribute("gen_ai.request.model", "gpt-4o")
llm_span.set_attribute("gen_ai.conversation.id", conversation_id)-
Reuse the same id for every turn. Each user message in a conversation usually produces its own trace. Pass the same
conversation_idto each one, and Rius groups those traces together. -
Add the model’s token counts if you want cost. Rius computes cost from
gen_ai.request.modelplus thegen_ai.usage.*token counts. The span attribute reference lists them. Never send a cost attribute.
If you repeat the attribute in many places, wrap start_as_current_span in a small helper of your own that sets it every time. The broader walkthrough of sending LLM spans through OpenTelemetry is in Trace LLM calls with OpenTelemetry.
What you get
- Traces that share a
gen_ai.conversation.idappear as one session in Rius, so a multi-turn conversation reads as a single unit instead of separate traces. - No extra attribute is needed. You do not have to duplicate the value into
session.id. - Cost, tokens and model analytics keep working on the same spans, because the conversation id only affects grouping.
- The same code works with any OpenTelemetry-based sender in Python, since the key comes from the GenAI conventions rather than from Rius.
- Spans without the key stay valid. They are grouped by trace id, and nothing is rejected.
When something else fits
- If you already send
session.id, or use OpenInference, which sets it, keep that key. Rius readssession.idfirst, so you do not need both. - If you use the Rius Python SDK, use its session scope,
rius.session, instead of setting the attribute by hand. - For a language other than Python, set the same key with that SDK’s own span API, and follow its OpenTelemetry documentation for setup.