Skip to Content

How do I set the span kind attribute on an OpenTelemetry span in JavaScript?

In JavaScript you set the attribute openinference.span.kind on the span with span.setAttributes(), and Rius uses it to classify the span in trace views. The values are AGENT, LLM, TOOL, RETRIEVER, EMBEDDING and CHAIN. Set gen_ai.operation.name as well if you want the span to follow the OpenTelemetry GenAI conventions. Rius derives whichever one of the two you leave out.

How it works

Rius reads two attributes to decide what a span is:

  • openinference.span.kind takes AGENT, LLM, TOOL, RETRIEVER, EMBEDDING or CHAIN. It classifies the span in trace views and per-kind analytics.
  • gen_ai.operation.name is the OpenTelemetry GenAI operation, such as chat, execute_tool, invoke_agent, embeddings or retrieval.

Set either one and Rius derives the other. chat, text_completion and generate_content map to LLM. invoke_agent and create_agent map to AGENT. execute_tool maps to TOOL, embeddings to EMBEDDING and retrieval to RETRIEVER. invoke_workflow and plan map to CHAIN. A CHAIN span gets no operation in return, because the conventions define none for a generic step. The Rius SDKs set both attributes, so setting both by hand gives the same result.

The kind also decides what Rius counts as an LLM call. A span of kind LLM is one, as is a span with no kind and no operation that carries gen_ai.request.model. Agent, tool, retrieval and embedding spans are not LLM calls, even when they carry a model.

RERANKER is also accepted. Rius has no reranker category yet, so a reranking span shows as LLM when it carries gen_ai.request.model, and as Other when it does not.

Set it up

  1. Register a tracer provider with an OTLP exporter and shut it down before the process exits. The vanilla OpenTelemetry SDKs page has the full setup, including the OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_EXPORTER_OTLP_HEADERS variables. Those variables only configure the exporter, and the provider is what sends spans. In Node.js:
import { NodeSDK } from '@opentelemetry/sdk-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' const sdk = new NodeSDK({ // No url or headers in code, so the exporter reads OTEL_EXPORTER_OTLP_* traceExporter: new OTLPTraceExporter(), // Rius ingests traces only metricReaders: [], logRecordProcessors: [], }) sdk.start() // ... your agent code ... // Flushes queued spans before the process exits (top-level await needs an ES module) await sdk.shutdown()
  1. Get a tracer from @opentelemetry/api, start a span, and set the kind next to the operation. This example traces a tool you call yourself, so it uses TOOL and execute_tool. Name the span the way the GenAI conventions ask, the operation followed by what it acted on:
import { trace } from '@opentelemetry/api' const tracer = trace.getTracer('my-agent') const span = tracer.startSpan('execute_tool get_weather') span.setAttributes({ 'openinference.span.kind': 'TOOL', 'gen_ai.operation.name': 'execute_tool', 'gen_ai.tool.name': 'get_weather', }) // ... run the tool ... span.end()
  1. For a model call, set the kind to LLM, the operation to chat, and add gen_ai.request.model and the token counts. Rius computes cost from those, so never send a cost attribute. The span attribute reference lists every attribute Rius reads.

  2. Nest spans through the normal OpenTelemetry parent context so they arrive as one trace, and wrap each agent run in a single root span. The general path for sending OpenTelemetry traces is in Trace LLM calls with OpenTelemetry.

What you get

  • Each span is classified by its kind in trace views and per-kind analytics. In the Spans tab you can filter by kind with the All kinds menu.
  • A span you mark TOOL is counted by the Tool Loop alert, which counts only spans of kind TOOL. A tool span with another kind is never counted.
  • A span of kind LLM counts as an LLM call, and with a model and token counts it gets a cost computed on the server. Costs are list-price estimates in US dollars.
  • Setting only one of the kind or the operation still works, because Rius derives the other.
  • A span with an unknown kind or no kind keeps its attributes as sent. Without an operation or a model it has nothing to derive a kind from.

When something else fits

  • If your project is in Python or TypeScript, the Rius SDKs set both the kind and the operation for you and add auto-instrumentation, so you don’t write these attributes by hand. The vanilla OpenTelemetry SDKs page lists what the SDKs add.
  • For a language other than JavaScript, set the same two attribute keys with that language’s OpenTelemetry API and see that SDK’s own documentation for the call.

Still stuck? Get help

Last updated on