Skip to Content

How does Rius read OpenInference prompt and completion message attributes?

Rius reassembles the llm.input_messages.* and llm.output_messages.* attributes from OpenInference into the GenAI message attributes at ingest. The result is stored under gen_ai.input.messages and gen_ai.output.messages, and the prompt and completion show on the span’s Input / Output tab in the console. You send nothing extra: point an OpenInference instrumentor at the Rius ingest endpoint. The same mapping also fills the model, token counts and cost.

How it works

OpenInference writes one attribute per message field, so a conversation arrives as many indexed attributes under two prefixes:

  • llm.input_messages.* for the prompt messages.
  • llm.output_messages.* for the completion messages.

At ingest, Rius gathers those flattened attributes into one JSON array in the GenAI message shape and stores it under gen_ai.input.messages and gen_ai.output.messages. Each message is a role plus a list of parts. Text parts carry the content, and tool calls and tool results have their own part types:

[ {"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"}] } ]

These related OpenInference attributes are handled in the same pass:

  • openinference.span.kind sets the span kind (LLM, TOOL, CHAIN and so on).
  • input.value and output.value are kept as they are, as generic span content.
  • llm.model_name, llm.provider and llm.token_count.* become the model, provider and token counts under their gen_ai.* keys, which is what lets Rius compute cost.
  • llm.tools.* and the tools member of llm.invocation_parameters become gen_ai.tool.definitions. Anthropic’s system parameter becomes the leading system message.

Native gen_ai.* keys always win. Normalization fills gaps and never overwrites a value you sent. Rius also removes the OpenInference source keys it mapped, so search and filter on the gen_ai.* key. A filter on llm.token_count.prompt matches nothing, while gen_ai.usage.input_tokens matches.

The full mapping is in the supported-conventions matrix of the span attribute reference.

Set it up

  1. Wire an OpenInference instrumentor to the Rius ingest endpoint with an OTLP exporter. This example uses the OpenAI instrumentor with an API key from the console:
from opentelemetry.sdk.trace import SpanLimits, TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from openinference.instrumentation.openai import OpenAIInstrumentor # Raise the span attribute limit, so long agent loops keep every attribute provider = TracerProvider(span_limits=SpanLimits(max_span_attributes=4096)) provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="https://ingest.eu.console.rius-glassflow.com/v1/traces", headers={"Authorization": "Bearer <your API key>"}, ) ) ) OpenAIInstrumentor().instrument(tracer_provider=provider)
  1. Raise the span attribute limit. Because OpenInference writes one attribute per message field, OpenTelemetry’s default of 128 attributes per span is passed within a few turns of an agent loop. The span then silently loses attributes, such as the model, tools, usage or messages. The code above raises it in Python. For any language you can set it through the environment instead:
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096
  1. Use the protobuf exporter. Rius accepts OTLP over HTTP with http/protobuf or http/json, and has no gRPC endpoint. In Node, use @opentelemetry/exporter-trace-otlp-proto.

  2. Run your app, then open a trace in the console and select an LLM span.

If your app is Python, the Rius SDK ships these same instrumentors as integrations and applies the OpenInference mapping in your process before export. Setup for running OpenInference without the SDK is on the OpenInference page, and Trace LLM calls with OpenTelemetry compares every sender path on one page. The interoperability overview lists the other ways in.

What you get

  • The prompt and completion of each LLM call render on the span’s Input / Output tab, with tool calls and tool results shown as their own parts.
  • Spans without message attributes show “Content not captured” on that tab. Their timings, tokens and cost are unaffected.
  • Token counts and the model come from the same OpenInference span, so Rius computes cost for the call. Costs are list-price estimates in US dollars.
  • On streamed calls from the Python OpenAI instrumentor, the first-token event is normalized to gen_ai.first_token, which feeds time to first token.
  • Search and filters use the gen_ai.* keys, for example gen_ai.usage.input_tokens.

A few things to know about content. Embedding vectors are dropped and the embedded texts are kept, and inline media bytes are replaced by a placeholder recording the media type and size. Without the SDK there are no privacy controls in your process, so everything the instrumentor records is sent as recorded, message content included.

When something else fits

  • If you build the spans yourself and not with an OpenInference instrumentor, set gen_ai.input.messages and gen_ai.output.messages directly in the GenAI message shape, as the span attribute reference describes.
  • If your app is Python or TypeScript and you want the exporter, attribute limit and privacy controls wired for you, use the Rius SDK instead of a hand-built provider.

Still stuck? Get help

Last updated on