Skip to Content

How do I get LLM cost with the OpenTelemetry JavaScript SDK?

Rius computes LLM cost for you when each model-call span from the OpenTelemetry JavaScript SDK carries gen_ai.request.model and token counts. The plain SDK does not set these attributes itself, so you add them to the span in your own code. You never send a cost value, because Rius works it out from the model name and the tokens.

How it works

Rius prices a span on the server. It reads the requested model from gen_ai.request.model and the token counts from the gen_ai.usage.* attributes, then prices each count at its own rate:

  • gen_ai.usage.input_tokens: prompt tokens, including cached ones.
  • gen_ai.usage.output_tokens: completion tokens, including reasoning ones.
  • gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_write.input_tokens: prompt-cache reads and writes, which are subsets of the input count.

A span needs the model and at least one count. A count you leave out is read as 0. A span with no model, or a model the price catalog does not know, gets an empty cost, not zero. Costs are list-price estimates in US dollars from LiteLLM’s public model-price catalog, not billed amounts.

The OpenTelemetry JavaScript SDK and its exporter only carry spans to Rius. They do not create model-call spans or fill in a model or tokens. What a span carries depends on the attributes your instrumentation or your own code sets. If an OpenInference instrumentor emits the model and its llm.token_count.prompt and completion values, Rius maps them onto the keys above at ingest, and cost works the same way. Never add a cost attribute of your own.

Set it up

  1. Register a tracer provider that exports to Rius. This is the shared setup, covered in full on Vanilla OpenTelemetry SDKs. Install @opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-proto and @opentelemetry/api. Set OTEL_EXPORTER_OTLP_ENDPOINT to https://ingest.eu.console.rius-glassflow.com and OTEL_EXPORTER_OTLP_HEADERS to authorization=Bearer followed by your API key. The protocol comes from the exporter package you import, exporter-trace-otlp-proto, so OTEL_EXPORTER_OTLP_PROTOCOL is not needed here. Also set OTEL_SERVICE_NAME, and set OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT to 4096 so long LLM spans keep their attributes. Then start the SDK and shut it down before the process exits, or queued spans are never 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. Wrap each model call in a span. Set the model before the call, and set the token counts from the provider’s response after it. callTheModel stands for your own function that calls the provider:
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, }) span.end()
  1. Use a model name the price catalog knows, and set both token counts from the provider’s usage fields. If the provider reports cached prompt tokens, send them in the cache read and cache write attributes as well, so each is priced at its own rate.

  2. Run one call, open Traces in the console, and open the new trace. The span shows the model, the token counts and the cost. If the cost is empty, check the model name and the token attributes. The full attribute list is in the span attribute reference.

What you get

  • A cost on every model-call span, computed by Rius from the model and the tokens, with cache reads and writes priced at their own rates.
  • Cost totals and a per-model cost breakdown across your traces.
  • Spans that count as LLM calls in the console and in alerts, because the span kind and operation are set.
  • No cost or latency value to calculate or send from your code.

The Trace LLM calls with OpenTelemetry guide covers the other ways to send traces.

When something else fits

  • If you use TypeScript and want model and token attributes set for you, the Rius SDK auto-instruments supported libraries, so you do not write the attributes by hand.
  • If your model is not in LiteLLM’s public price catalog, its cost stays empty. Rius does not take custom prices or negotiated discounts, so the figure it shows is always a list-price estimate.

Still stuck? Get help

Last updated on