Skip to Content
Rius

How does Rius read OpenInference token usage attributes?

Rius reads OpenInference token usage from the llm.token_count attributes and moves the counts onto the gen_ai.usage keys at ingest, so token analytics and cost work. The prompt count becomes input tokens and the completion count becomes output tokens. Cache and reasoning details are mapped too. You search and filter on the gen_ai.usage keys, not on the original llm.token_count names.

How it works

OpenInference instrumentors write token counts under llm.token_count.*. When a span arrives, Rius normalizes them: it moves the values onto their canonical keys before storage and removes the source keys it mapped.

OpenInference attributeStored as
llm.token_count.promptgen_ai.usage.input_tokens
llm.token_count.completiongen_ai.usage.output_tokens
The cache and reasoning details of those countsgen_ai.usage.cache_read.input_tokens, gen_ai.usage.cache_write.input_tokens and gen_ai.usage.reasoning.output_tokens

Three rules shape the result:

  • Native keys win. If a span already carries a gen_ai.* key, Rius keeps it. Normalization fills gaps and never overwrites.
  • The input count includes cached tokens. gen_ai.usage.input_tokens counts prompt tokens including cached ones, and the cache counts are subsets of it. The output count includes reasoning tokens.
  • Cost needs a model. Rius computes cost on the server from the request model plus the token counts. For OpenInference spans, the request model comes from llm.request.model_name, else the model member of llm.invocation_parameters, else llm.model_name.

Because the source keys are removed, a filter on llm.token_count.prompt or gen_ai.usage.prompt_tokens matches nothing, while a filter on gen_ai.usage.input_tokens matches. The Rius SDKs apply the same OpenInference mapping in your process before export. The full contract is in the span attribute reference.

Set it up

  1. Point your OpenInference instrumentor at the Rius ingest endpoint with an OTLP exporter. This example uses the OpenAI instrumentor. The OpenInference guide covers the wiring in full.
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 attribute limit so long agent loops keep their usage and model 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. OpenInference writes one attribute per message field and per tool field, and OpenTelemetry keeps only 128 attributes per span by default. A span that passes the limit silently loses attributes, and the usage can go with them. If you do not set it in code as above, set it through the environment:
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096
  1. Make sure each model call span carries a model name, through llm.model_name or one of the other sources listed above. A span with no model gets no cost.

  2. Search and filter on the gen_ai.usage.* keys when you inspect traces. For the other ways to send traces, see Trace LLM calls with OpenTelemetry and the interoperability overview.

What you get

  • Token usage on each model call span, stored under gen_ai.usage.input_tokens and gen_ai.usage.output_tokens, with cache and reasoning counts where the instrumentor reports them.
  • Server-side cost for each call, computed from the model and the token counts, with cache reads and writes each priced at their own rate. Costs are list-price estimates in US dollars. A span with no model, a model the price catalog does not know, or none of the counts gets no cost.
  • Model analytics from the same normalized keys, so OpenInference-instrumented calls are analyzed like spans that use the gen_ai.* keys natively.
  • No cost attribute to send. Send tokens and the model name, and Rius does the pricing.

When something else fits

  • If your code already emits the gen_ai.usage.* keys, send those directly. They are stored as sent and need no mapping.
  • If your app is Python or TypeScript, the Rius SDK ships these same instrumentors as integrations and sets the attribute limit for you.
  • If your spans use another convention, the span attribute reference lists which families Rius normalizes.

Still stuck? Get help

Last updated on