Skip to Content

How does Rius read OpenInference prompt cache token attributes?

Rius reads the cache details that OpenInference records under its llm.token_count.* attributes and moves them onto the gen_ai.usage.* token keys when a span is ingested. Cache reads and writes are then priced at their own rates in the cost Rius computes. In the console you search and filter on the gen_ai.* keys, not on the llm.token_count.* names you sent.

How it works

OpenInference instrumentors write the prompt and completion counts as llm.token_count.prompt and llm.token_count.completion, with cache and reasoning details alongside them under llm.token_count.*. At ingest, Rius normalizes these onto the canonical keys and removes the source keys it mapped:

  • llm.token_count.prompt fills gen_ai.usage.input_tokens. This count includes cached tokens.
  • llm.token_count.completion fills gen_ai.usage.output_tokens. This count includes reasoning tokens.
  • llm.token_count.prompt_details.cache_read and llm.token_count.prompt_details.cache_write fill gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_write.input_tokens. Both are subsets of the input count.
  • llm.token_count.completion_details.reasoning fills gen_ai.usage.reasoning.output_tokens, a subset of the output count.

The cache-write count also has an older GenAI spelling, gen_ai.usage.cache_creation.input_tokens. Rius normalizes that onto gen_ai.usage.cache_write.input_tokens too.

Normalization fills gaps and never overwrites. If a span already carries a native gen_ai.* key, that value wins.

Cost follows from the normalized values. Rius computes it on the server from the request model and the token counts: input, output, cache read and cache write, each priced at its own rate. For an OpenInference span, the request model comes from llm.request.model_name, else the model member of llm.invocation_parameters, else llm.model_name. A span needs a request model and at least one count. Any count it leaves out is read as 0. A model the price catalog does not know gets no cost. Costs are list-price estimates in US dollars, and you never send a cost attribute.

The Rius SDKs apply the same OpenInference mapping inside your process before export. The only difference is that they skip the llm.model_name fallback, which only the platform applies.

Set it up

  1. Wire an OpenInference instrumentor to the Rius ingest endpoint without the SDK. Raise the span attribute limit in the same place, because OpenInference writes one attribute per message and tool field and OpenTelemetry keeps only 128 per span by default:
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. In any other language, raise the limit through the environment instead:
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096
  1. Make a call that uses prompt caching. Rius reads the cache counts the instrumentor recorded on the span. You set nothing for them.

  2. Search and filter on the canonical keys, such as gen_ai.usage.input_tokens and gen_ai.usage.cache_read.input_tokens. A filter on llm.token_count.prompt matches nothing, because Rius removes that key after mapping it.

The full wiring for this sender, including Node exporter choices, is on the OpenInference page. If your app is Python and you want the SDK to wire the exporter and the attribute limit for you, use the SDK integrations instead.

What you get

  • Token usage on OpenInference spans, including the cache read and cache write counts, stored under the gen_ai.usage.* keys.
  • Server-side cost for each call, with cache reads and writes priced at their own rates, as long as the span has a known request model and at least one count.
  • The same model analytics that native spans get, because the values sit on the same keys.
  • One set of keys to search and filter on across senders. OpenInference, OpenLLMetry and native GenAI spans all end up under the canonical names.

The span attribute reference lists every normalized family and the native-wins rule in its supported-conventions matrix. For how this sender compares with the SDK and other paths, see Trace LLM calls with OpenTelemetry. For the interoperability section as a whole, see Interoperability.

When something else fits

  • If you already emit the gen_ai.usage.* keys, including gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_write.input_tokens, Rius uses them as sent and no OpenInference mapping is involved.
  • If you want the attribute limit, privacy controls and exporter wired for you in a Python or TypeScript app, the Rius SDK integrations cover the same instrumentors.

Still stuck? Get help

Last updated on