Skip to Content

How does Rius read OpenTelemetry GenAI tool call attributes?

Rius reads OpenTelemetry GenAI tool call attributes from execute_tool spans, shows them as tool spans, and moves the arguments and result to input.value and output.value. You set the operation name, the tool name, the call id and the tool type. Rius derives the span kind, groups the span under its agent, and renders the tool’s input and output in the console. The full contract is in the span attribute reference.

How it works

A tool call is a span whose gen_ai.operation.name is execute_tool. Rius reads these attributes on it:

  • gen_ai.tool.name: the tool’s own name, never the span name.
  • gen_ai.tool.call.id: the id of the model’s tool call this execution answers.
  • gen_ai.tool.type: function, extension, datastore and so on.
  • gen_ai.agent.name: on an execute_tool span, the agent executing the tool.
  • error.type: a low-cardinality error class such as tool_error, on a span that ended in an error.
  • gen_ai.tool.call.arguments and gen_ai.tool.call.result: the arguments the tool received and the result it returned.

Rius normalizes these in a few ways:

  • Span kind. Set gen_ai.operation.name or openinference.span.kind and Rius derives the other. execute_tool becomes the TOOL kind, and TOOL spans are the ones the trace views classify as tools.
  • Arguments and result. At ingest, gen_ai.tool.call.arguments and gen_ai.tool.call.result move to input.value and output.value, so the tool’s input and output render like any other span’s. A native input.value or output.value wins, and the two source keys are removed either way. Search and filter on input.value and output.value, not on the source keys.
  • Native keys win. Normalization fills gaps and never overwrites a gen_ai.* key you sent.
  • MCP tools. A tool called over the Model Context Protocol is an execute_tool span that also carries mcp.method.name set to tools/call. That attribute marks it as an MCP tool. A trace’s context attribution reports how many context tokens MCP tools took only when at least one span carries it. Without it the MCP share is unknown, not zero.

The model’s side of a tool call lives on the LLM spans, in the GenAI message shape: the assistant part of type tool_call in the output messages of the call that requested it, and the tool part of type tool_call_response in the input messages of the call after it. The tool definitions the model was shown go in gen_ai.tool.definitions.

Spans from other conventions, such as OpenInference, OpenLLMetry and the Vercel AI SDK, are mapped onto the same keys at ingest. See Trace LLM calls with OpenTelemetry for the sender paths.

Set it up

  1. Point your OpenTelemetry exporter at Rius with OTLP over HTTP. Use http/protobuf (recommended) or http/json, send to https://ingest.eu.console.rius-glassflow.com/v1/traces, and put your API key in an Authorization: Bearer header. Setup for each sender is on the interoperability page.
  2. Name the span the way the GenAI conventions ask, the operation followed by what it acted on, for example execute_tool get_weather, and set gen_ai.operation.name to execute_tool.
  3. Set gen_ai.tool.name, gen_ai.tool.call.id and gen_ai.tool.type on the span. Set gen_ai.agent.name to the agent that ran the tool.
  4. Set gen_ai.tool.call.arguments and gen_ai.tool.call.result, or set input.value and output.value directly. Rius ends up with the second pair either way.
  5. For an MCP tool, also set mcp.method.name to tools/call.
  6. On the LLM span that asked for the tool, put the assistant’s tool_call part in gen_ai.output.messages. The tool’s result goes in the input of the next LLM call: its gen_ai.input.messages repeat the history, the assistant tool_call and a tool message with a tool_call_response part, as 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"}] } ]
  1. Open the trace in the console and check the tool span. It should show as a tool, with its input and output on the Input / Output tab.

The tool’s gen_ai.tool.call.id should match the id in the model’s tool_call part, so you can tie the execution to the request that caused it.

What you get

  • Tool spans classified as tools in trace views and per-kind analytics, because execute_tool maps to the TOOL kind.
  • The tool’s name, call id, type and agent on the span, so you can tell which tool ran, for which model request, and under which agent.
  • The tool’s arguments and result on the span’s Input / Output tab. A span without content attributes shows “Content not captured” there, and its timings are unaffected.
  • A tool failure recorded with error.type, so it appears in error analysis.
  • Context attribution that separates MCP tools from local tools, when mcp.method.name is set.
  • Model calls that show the tool calls they made, from the tool_call part in their output, and the responses they were given, from the tool_call_response part in the next call’s input.

Content attributes are stored for trace inspection. If you strip content at export, the structure of the call remains and the arguments and result do not.

When something else fits

If you use the Rius SDK or one of its integrations, it already sets these attributes, so you do not write them by hand. If your senders are OpenInference, OpenLLMetry or the Vercel AI SDK, you can keep their spellings, because Rius maps them onto the same keys at ingest; the interoperability page links each guide. Hand-written attributes fit when you instrument a custom language or runtime yourself.

Still stuck? Get help

Last updated on