Which OpenTelemetry attribute should my spans use for a tool call?
Rius expects gen_ai.tool.name on a span whose gen_ai.operation.name is execute_tool, which is the OpenTelemetry GenAI convention for a tool call. Set the tool’s own name in gen_ai.tool.name, never the span name. Rius also reads OpenInference, OpenLLMetry, Laminar and Vercel AI SDK tool spans and moves their values onto these keys at ingest.
How it works
A tool call is one span with the operation execute_tool. These are the attributes Rius reads on it:
gen_ai.operation.name: set it toexecute_tool. Rius derives the span kindTOOLfrom it.gen_ai.tool.name: the tool’s own name, for exampleget_weather.gen_ai.tool.call.id: the id of the model’s tool call that this execution answers.gen_ai.tool.type:function,extension,datastoreand so on.gen_ai.agent.name: on an execute_tool span, the agent executing the tool.error.type: a low-cardinality error class such astool_error, on a span that ended in an error.gen_ai.tool.call.argumentsandgen_ai.tool.call.result: the arguments and the result. Rius moves them toinput.valueandoutput.value, so you search on those two keys. A nativeinput.valueoroutput.valuewins.
Set either gen_ai.operation.name or openinference.span.kind and Rius derives the other: execute_tool and TOOL go together. Name the span the way the GenAI conventions ask, the operation followed by what it acted on, such as execute_tool get_weather. Rius does not require it, but the SDKs emit it, so mixed fleets read alike.
Other conventions Rius reads. Native gen_ai.* keys always win, and normalization only fills gaps:
- OpenInference:
openinference.span.kindwith the valueTOOL. - OpenLLMetry and Traceloop: the tool decorator kind becomes the span taxonomy, and the decorated entity’s name becomes the tool name.
- Laminar:
lmnr.span.typeofTOOL, pluslmnr.association.properties.metadata.tool_call_id, which becomes the tool call id. - Vercel AI SDK 5 and 6: the
ai.toolCall.*attributes become tool calls.
Search and filter on the canonical key, not on the source key.
The model’s request for a tool lives in the message content as a part of type tool_call, and the answer is a part of type tool_call_response. Those parts go in gen_ai.output.messages and gen_ai.input.messages on the model call’s span. The tool definitions the model was shown go in gen_ai.tool.definitions on that span.
Set it up
- Create a span for each tool execution and name it with the operation and the tool, for example
execute_tool get_weather.get_weatherstands for your own tool name. - Set
gen_ai.operation.nametoexecute_toolandgen_ai.tool.nameto the tool’s name. - Set
gen_ai.tool.call.idto the id the model gave the call, so the execution links to the model’s request. Addgen_ai.tool.typeandgen_ai.agent.nameif you have them. - Add the arguments and result, and set
error.typeif the tool failed.
An example span, shown as its name and attributes:
{
"name": "execute_tool get_weather",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.name": "get_weather",
"gen_ai.tool.call.id": "call_1",
"gen_ai.tool.type": "function",
"gen_ai.agent.name": "planner",
"gen_ai.tool.call.arguments": "{\"city\": \"Berlin\"}",
"gen_ai.tool.call.result": "18 C, clear"
}
}- If the tool runs over the Model Context Protocol, also set
mcp.method.nametotools/call. This marks the span as an MCP tool, so a trace’s context attribution can report how many context tokens MCP tools took and their share. Without it, that share is unknown, not zero. You can also setmcp.protocol.version, which is stored with the span. - Send the spans to Rius over OTLP/HTTP. The full list of attributes, the send contract and the status codes are on Span attribute reference. The wider picture of sending OpenTelemetry traces is in Trace LLM calls with OpenTelemetry.
The Rius SDKs set the operation and the span kind for you. You only need to set these attributes yourself when you emit spans from a vanilla OpenTelemetry SDK, a collector pipeline or another language.
What you get
- Tool spans classified as
TOOLin trace views and per-kind analytics, whichever of the two classification attributes you send. - The tool’s own name on every execution, from
gen_ai.tool.name, so one tool reads the same across traces. - Each execution tied to the model’s request through
gen_ai.tool.call.id. - The tool’s input and output rendered like any other span’s, because arguments and result land on
input.valueandoutput.value. - Errors surfaced from
error.type. - For MCP tools, a split between MCP and local tools in context attribution.
A tool span is not counted as an LLM call, even when it carries a model. Cost comes from the model calls, which Rius computes from the model name and token counts. Never send a cost attribute on a tool span or anywhere else.
When something else fits
If you already emit OpenInference, OpenLLMetry, Laminar or Vercel AI SDK spans, you can keep their attributes, because Rius normalizes them at ingest. Use the gen_ai.* keys for any new instrumentation you write, and filter on those canonical keys in the console.