How do I set the prompt and completion message attribute on an OpenTelemetry span in Python?
Rius reads the prompt and completion from the gen_ai.input.messages and gen_ai.output.messages attributes, which you set in Python with span.set_attribute and a JSON string. Each value is a JSON array of messages in the OpenTelemetry GenAI message shape. Rius then shows the prompt and completion on the span’s Input / Output tab.
How it works
Rius expects two keys on the span for a model call:
gen_ai.input.messagesholds the prompt messages.gen_ai.output.messagesholds the completion.
OpenTelemetry attribute values in Python cannot be lists of dicts, so you pass each one as a string from json.dumps. The JSON is an array of messages. Each message has a role and a list of parts. A text part has type set to text and the text under content. Tool interactions use their own part types, shown below.
[
{"role": "user", "parts": [{"type": "text", "content": "What is 2+2?"}]},
{
"role": "assistant",
"parts": [
{"type": "tool_call", "id": "call_1", "name": "calculator", "arguments": "{\"expr\": \"2+2\"}"}
]
},
{
"role": "tool",
"parts": [{"type": "tool_call_response", "id": "call_1", "response": "4"}]
}
]When you emit spans directly, you build this shape yourself, because message rendering in the console depends on it. A system prompt that you send apart from the chat history goes in gen_ai.system_instructions, as a JSON array of parts with type set to text and the text under content. Rius counts it as the system prompt when it breaks down a call’s context.
The messages are content only. For Rius to treat the span as a model call and price it, also set the model and token counts. Rius computes cost on its side from gen_ai.request.model and the gen_ai.usage.* counts, so you never send a cost attribute. See the span attribute reference for every key.
Set it up
- Register a tracer provider that exports to Rius. Install
opentelemetry-sdkandopentelemetry-exporter-otlp-proto-http, then register the provider. Importing the exporter fromopentelemetry.exporter.otlp.proto.httpselects http/protobuf. With no arguments the exporter reads the standard OTEL_EXPORTER_OTLP_* variables, which point it athttps://ingest.eu.console.rius-glassflow.comand carry your API key as a bearer authorization header. The variables send nothing on their own. The vanilla OpenTelemetry SDKs page lists them all. For a wider view of this path, see Trace LLM calls with OpenTelemetry.
from 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 variables before you start the process:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.eu.console.rius-glassflow.com"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer <your API key>"- Open a span for the model call and set the messages. Name the span the way the GenAI conventions ask, the operation followed by the model. Set the input messages before the call and the output messages after it. Here
call_the_modelstands for your own function, and the response fields are your own.
import json
from opentelemetry import trace
tracer = trace.get_tracer("my-agent")
with tracer.start_as_current_span("chat gpt-4o") as span:
span.set_attribute("openinference.span.kind", "LLM")
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.request.model", "gpt-4o")
span.set_attribute("gen_ai.provider.name", "openai")
span.set_attribute("gen_ai.input.messages", json.dumps([
{"role": "user", "parts": [{"type": "text", "content": "What is 2+2?"}]},
]))
response = call_the_model() # your own function
span.set_attribute("gen_ai.usage.input_tokens", response.usage.prompt_tokens)
span.set_attribute("gen_ai.usage.output_tokens", response.usage.completion_tokens)
span.set_attribute("gen_ai.response.model", response.model)
span.set_attribute("gen_ai.output.messages", json.dumps([
{"role": "assistant", "parts": [{"type": "text", "content": response.text}]},
]))-
Nest this span under a root span for the agent run, the normal OpenTelemetry way with parent context, so the calls arrive as one trace.
-
Run the code, open the trace in Rius and select the model-call span. The prompt and completion appear on its Input / Output tab.
What you get
- The prompt and completion render on the span’s Input / Output tab, with tool calls and tool results shown as their own parts.
- The span counts as a model call in model analytics, because it carries the model and token counts.
- Rius computes cost for the span from the model and the token counts, as a list-price estimate in US dollars. A model that Rius’s price catalog does not know gets an empty cost.
- A span without the message attributes shows “Content not captured” on that tab. Its timings, tokens and cost are unaffected, so you can add the messages later without changing anything else.
- If an instrumentation library emits OpenInference or OpenLLMetry attributes, Rius reassembles their flattened messages into the same GenAI shape at ingest. You then search and filter on the canonical keys.
When something else fits
- If you want a library to record calls for you, use the Rius Python SDK. Its
set_input()andset_output()normalize OpenAI-style message dicts into this shape, and its auto-instrumentation records supported clients without manual attributes. - If your instrumentation library already emits OpenInference or OpenLLMetry attributes, keep it. Rius maps them onto the canonical keys, so you do not need to set the messages by hand.
Related
- Span attribute reference
- How do I set the prompt and completion message attribute on an OpenTelemetry span in JavaScript?
- How do I set the prompt cache token attribute on an OpenTelemetry span in Python?
- How do I set the prompt cache token attribute on an OpenTelemetry span in JavaScript?
Still stuck? Get help