Skip to Content

How do I record tool calls with OpenLLMetry?

Rius records tool calls from OpenLLMetry when the tool function runs under a Traceloop decorator of the tool kind. Rius turns that kind into a tool span at ingest and uses the decorated function’s name as the tool name. You add no Rius-specific attribute for this. If a tool is not decorated, you set the tool attributes yourself on a span you create.

How it works

OpenLLMetry marks each decorated step with traceloop.span.kind, which is one of workflow, task, agent or tool. When the span arrives, Rius maps that kind onto its own span categories, so a tool-kind span becomes a tool span, and an agent-kind span becomes an invoked-agent span. Rius also does the following:

  • Tool name: the name of the decorated function becomes the tool name.
  • Input and output: traceloop.entity.input and traceloop.entity.output become the span’s input.value and output.value, so the tool’s arguments and result render like any other span’s.
  • Nesting: spans nest, so a tool span sits under the workflow or agent span that called it.

So this setup already sets what Rius needs for a tool span. The attribute that matters is the span kind, and the Traceloop decorator supplies it. Spans that Rius classifies as tool calls carry the execute_tool operation, and the tool’s own name goes in gen_ai.tool.name. If an exception ends the function, the decorator sets an error status on the span and records the exception.

Two mappings are not done for OpenLLMetry yet: the per-completion gen_ai.completion.N.finish_reason of older releases, and the flattened tool definitions under llm.request.functions.*. Both are stored under their own names, so you can search on them but they do not feed analytics.

Set it up

  1. Point OpenLLMetry at Rius. This is the only setup that is not specific to tool calls. The OpenLLMetry page covers it in full, and Trace LLM calls with OpenTelemetry compares it with the other ways in.
from traceloop.sdk import Traceloop Traceloop.init( app_name="my-agent", api_endpoint="https://ingest.eu.console.rius-glassflow.com", headers={"Authorization": "Bearer <your API key>"}, )

Traceloop.init registers the tracer provider for you. In a short script, pass disable_batch=True so each span is exported when it ends instead of waiting in a queue the process may exit before flushing.

  1. Run each tool function under OpenLLMetry’s tool decorator, so its span carries the tool kind. Rius uses the decorated entity’s name, the function name unless you pass name, as the tool name:
from traceloop.sdk.decorators import tool @tool(name="get_weather") def get_weather(city: str) -> str: return lookup_weather(city)

The decorator records the function’s arguments and return value as traceloop.entity.input and traceloop.entity.output. With TRACELOOP_TRACE_CONTENT=false it leaves both out, so the tool span still arrives with its name and timing but no input or output.

  1. Run the agent once, open the trace in the console, and find the tool span under its parent. It should show the tool’s name, and its input and output should appear as the span’s input and output. Filtering the Spans tab by the Tool kind in the All kinds menu lists it.

  2. If a tool has no Traceloop decorator, create a span for it yourself and set the attributes Rius reads for tool calls, from the span attribute reference:

    • gen_ai.operation.name set to execute_tool, or openinference.span.kind set to TOOL. Set either one and Rius derives the other.
    • gen_ai.tool.name, the tool’s own name and not the span name.
    • Optionally gen_ai.tool.call.id, the id of the model’s tool call this execution answers, and gen_ai.tool.type.
    • input.value and output.value for the arguments and the result.

    Name the span the way the GenAI conventions ask, the operation followed by the tool, such as execute_tool get_weather.

  3. On older OpenLLMetry releases that record messages one attribute per field, a long agent run can pass OpenTelemetry’s default limit of 128 attributes per span, and the span then loses data. Raise it:

export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096

What you get

  • Tool calls as their own spans, nested under the agent or workflow that ran them, with the tool’s name and its input and output.
  • Tool kind filtering in the Spans tab. The pre-defined Tool Loop alert counts only spans of the tool kind, so a tool recorded as a different kind is never counted by it.
  • Model, token and cost analytics on the model-call spans in the same trace. OpenLLMetry’s older and newer attribute spellings are normalized onto the same keys at ingest, so search and filter on gen_ai.usage.input_tokens, not gen_ai.usage.prompt_tokens.
  • Tool spans for hand-made spans too, as long as they carry the tool operation or kind.

When something else fits

  • If your code is not instrumented with OpenLLMetry yet, the Rius SDK auto-instruments common LLM libraries with one init() call, and observe with the TOOL kind records your own tool functions.
  • If you build spans by hand in another OpenTelemetry SDK, the span attribute reference lists exactly what to set.

Still stuck? Get help

Last updated on