Skip to Content

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

Rius reads the model name from the gen_ai.request.model attribute, which you set with span.setAttributes from the @opentelemetry/api package. Set it to the model you asked for, for example gpt-4o, and the span counts toward model analytics. With token counts on the same span, Rius also computes its cost. The same key works from any OpenTelemetry SDK, not only JavaScript.

How it works

Rius follows the OpenTelemetry GenAI conventions, so the key for the requested model is gen_ai.request.model, a string. Three related attributes are worth knowing:

  • gen_ai.request.model is the model you requested. Cost computation and model analytics use it.
  • gen_ai.response.model is the model the provider reports back. Set it after the call returns.
  • gen_ai.provider.name names the provider, for example openai or anthropic.

Rius computes cost on its side from gen_ai.request.model plus the gen_ai.usage.* token counts. A span needs the request model and at least one token count. A span with no model, or a model that Rius’s price catalog does not know, gets no cost. You never send a cost attribute.

The model name also affects whether a span counts as an LLM call. A span counts when its kind is LLM, set directly through openinference.span.kind or derived from gen_ai.operation.name values such as chat. A span with neither a kind nor an operation counts when it carries gen_ai.request.model. Agent, tool and embedding spans that carry a model are not LLM calls, but their cost still appears in the per-model breakdown and the cost totals.

If your instrumentation emits OpenInference attributes, Rius maps llm.model_name onto the request model at ingest when no other model key is present. Search and filter on gen_ai.request.model, because the source keys are removed after mapping.

Set it up

  1. Register a tracer provider that exports to Rius, once per process. Vanilla OpenTelemetry SDKs covers the endpoint, the API key and the exporter variables in full. Set the endpoint and your key first. Without them the exporter sends to localhost and nothing reaches Rius:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.eu.console.rius-glassflow.com" export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer <your API key>"

In Node.js, the setup looks like this, and it must shut down before the process exits so queued spans are sent:

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. Start a span for the model call and set the model name on it. Name the span the way the GenAI conventions ask, the operation followed by the model, such as chat gpt-4o. Set the kind and operation too, so the span is classified as an LLM call.

  2. After the call returns, add the token counts and the response model, then end the span. The model name and the counts together are what give the span a cost.

import { trace } from '@opentelemetry/api' const tracer = trace.getTracer('my-agent') const span = tracer.startSpan('chat gpt-4o') span.setAttributes({ 'openinference.span.kind': 'LLM', 'gen_ai.operation.name': 'chat', 'gen_ai.request.model': 'gpt-4o', 'gen_ai.provider.name': 'openai', }) const response = await callTheModel() span.setAttributes({ 'gen_ai.usage.input_tokens': response.usage.prompt_tokens, 'gen_ai.usage.output_tokens': response.usage.completion_tokens, 'gen_ai.response.model': response.model, 'gen_ai.output.messages': JSON.stringify([ { role: 'assistant', parts: [{ type: 'text', content: response.text }] }, ]), }) span.end()
  1. Open the trace in Rius and check the span. If the cost is empty, check that the model name matches a model the price catalog knows and that at least one token count is set. The span attribute reference lists every attribute Rius reads, and Trace LLM calls with OpenTelemetry shows the wider path.

What you get

  • The span is classified as a model call, and its model name is stored under gen_ai.request.model, the key to search and filter on.
  • The span appears in model analytics and in the cost-by-model breakdown.
  • With token counts set, the span gets a cost in US dollars, a list-price estimate computed by Rius. A model the catalog does not know gets an empty cost, not zero.
  • The span’s prompt and completion render on its Input / Output tab when you set the message attributes.
  • Nested spans arrive as one trace, so the model call sits under the agent run that made it.

When something else fits

  • If you use the Rius TypeScript SDK or one of its integrations, the SDK sets the model name for you, so you do not need to call setAttributes by hand.
  • If your instrumentation already emits OpenInference or OpenLLMetry attributes, leave them as they are. Rius maps them onto gen_ai.request.model at ingest.
  • For streaming calls, add the first-chunk timing described in the span attribute reference so time to first token is derived.

Still stuck? Get help

Last updated on