How does Rius read OpenInference tool call attributes?
Rius reads an OpenInference tool span by its openinference.span.kind value of TOOL, and stores input.value and output.value as the tool’s input and output. It also normalizes the model-side tool attributes, such as llm.tools.* and llm.input_messages., onto gen_ai. keys at ingest. You send standard OpenInference spans to Rius and they show up in the console as tool spans, with tool definitions and tool call messages in the GenAI shape.
How it works
Rius handles tool call data from OpenInference in five ways. Details are in the span attribute reference and on the OpenInference page.
- Span kind.
openinference.span.kindset toTOOLclassifies the span as a tool span. Set either the kind orgen_ai.operation.nameand Rius derives the other, so aTOOLspan gets theexecute_tooloperation. - Tool input and output.
input.valueandoutput.valueare kept as sent and shown as the span’s content. - Tool definitions. The
toolsmember ofllm.invocation_parametersand the indexedllm.tools.*schemas both becomegen_ai.tool.definitions. Thetool_choicemember becomesrius.request.tool_choice. - Tool calls in messages. The flattened
llm.input_messages.*andllm.output_messages.*attributes are reassembled into the GenAI message shape. There, a tool call is a part of typetool_calland its result is a part of typetool_call_response. - Tool span attributes. For
execute_toolspans the reference definesgen_ai.tool.name,gen_ai.tool.call.idandgen_ai.tool.type. Rius reads them as sent.
This is how the reassembled messages look:
[
{"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"}]
}
]Normalization moves each value onto its canonical key and removes the source key it mapped. A native gen_ai.* key always wins, because normalization fills gaps and never overwrites. Search and filter on the canonical key, for example gen_ai.tool.definitions, not on llm.tools.*.
Set it up
- Point an OpenInference instrumentor at the ingest endpoint. This is the full wiring without the Rius SDK. The OpenInference page covers it in more detail.
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: see "Raise the span attribute limit" below.
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)- 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. Past that, a span silently loses attributes such as the model, tool definitions or messages. The code above sets 4096 in Python. For any language you can set it through the environment instead:
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096-
Make sure your tool spans carry
openinference.span.kindset toTOOL. Instrumentors that set it need no change. If you create tool spans by hand, set it, or setgen_ai.operation.nametoexecute_tool, and Rius derives the other. Start those spans fromprovider.get_tracer(__name__), or register the provider withtrace.set_tracer_provider(provider), because a tracer from the unregistered global provider exports nothing. -
Run a call that uses a tool, then open the trace in the console and look at the tool span and the model span that requested it.
If your app is Python, the Rius SDK ships these same instrumentors as integrations and sets the exporter and the attribute limit for you.
What you get
- Tool spans in trace views. A span with kind
TOOLis classified as a tool span in trace views and in the per-kind analytics. - Tool input and output.
input.valueandoutput.valueappear as the content of the tool span. - Tool calls in the conversation. A model span with
llm.input_messages.*andllm.output_messages.*renders its prompt and completion on the span’s Input / Output tab, with the tool call in the requesting call’s output messages and the tool’s response in the next call’s input messages. A span without message attributes shows “Content not captured” there, and its timings, tokens and cost are unaffected. - Tool definitions on the model call. The tools the model was shown are stored under
gen_ai.tool.definitions, and the tool choice underrius.request.tool_choice. - Model analytics and cost.
llm.token_count.*and the model name fill thegen_ai.*token and model keys, so the model span that decides to call a tool gets a server-side cost. It is a list-price estimate in US dollars. Do not send a cost attribute.
Rius also never stores credential header attributes, such as http.request.header.authorization.
When something else fits
- If your app is Python, the Rius SDK integrations apply the same OpenInference mapping in your process before export. The one difference is that they skip the
llm.model_namefallback, which only the platform applies. - If you write tool spans by hand, set the
gen_ai.*attributes directly, and for MCP tool calls also setmcp.method.name. Trace LLM calls with OpenTelemetry compares every sender path, and the interoperability overview lists the others.