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.kindtakesAGENT,LLM,TOOL,RETRIEVER,EMBEDDINGorCHAIN. It classifies the span in trace views and per-kind analytics.gen_ai.operation.nameis the OpenTelemetry GenAI operation, such aschat,execute_tool,invoke_agent,embeddingsorretrieval.
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
- 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_PROTOCOLandOTEL_EXPORTER_OTLP_HEADERSvariables. 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()- 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 usesTOOLandexecute_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()-
For a model call, set the kind to
LLM, the operation tochat, and addgen_ai.request.modeland the token counts. Rius computes cost from those, so never send a cost attribute. The span attribute reference lists every attribute Rius reads. -
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
TOOLis counted by the Tool Loop alert, which counts only spans of kindTOOL. A tool span with another kind is never counted. - A span of kind
LLMcounts 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.