Skip to Content

How do I capture prompts and responses with the OpenTelemetry JavaScript SDK?

Rius shows the prompt and the response on a span when you set gen_ai.input.messages and gen_ai.output.messages on it as JSON. The OpenTelemetry JavaScript SDK does not set these attributes for you, so you add them to each LLM span in your own code. Spans without them still arrive with timings, tokens and cost, and show “Content not captured” on the Input / Output tab.

How it works

Rius reads the OpenTelemetry GenAI attributes from each span. Two of them carry content:

Both are JSON strings in the GenAI message shape: an array of messages, each with a role and a list of parts. A text part is {"type": "text", "content": ...}. Rius renders spans that carry this shape on the span’s Input / Output tab.

A plain Node.js OpenTelemetry setup does not emit these attributes, because nothing in it knows your prompt or your model’s reply. Only code that sets them puts content on the span. If you want a system prompt kept apart from the chat history, set gen_ai.system_instructions as a JSON array of text parts. For a span that is not an LLM call, input.value and output.value carry generic input and output as JSON or plain text.

To get cost and model analytics on the same span, also set the model and token counts. Rius computes the cost, so never send a cost attribute. The span attribute reference lists every attribute Rius reads.

Set it up

  1. Register a tracer provider with an OTLP exporter that points at Rius. The Vanilla OpenTelemetry SDKs page has the full Node.js, Python and Go setup. In Node.js you install @opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-proto and @opentelemetry/api, then set the exporter variables:

    export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.eu.console.rius-glassflow.com" export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer <your API key>"
  2. Raise the attribute limit by setting OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT to 4096. OpenTelemetry keeps 128 attributes per span by default and drops the rest without an error, so a long span can lose its content.

  3. Start the SDK, then set the content on the span around your model call. Set the input before the call and the output after it:

    import { NodeSDK } from '@opentelemetry/sdk-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' import { trace } from '@opentelemetry/api' 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() 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', 'gen_ai.input.messages': JSON.stringify([ { role: 'user', parts: [{ type: 'text', content: prompt }] }, ]), }) 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() // Flushes queued spans before the process exits (top-level await needs an ES module) await sdk.shutdown()

    Here prompt and callTheModel are your own code.

  4. Run one call, open Traces in the console, and open the new trace. The span’s Input / Output tab shows the prompt and the reply.

What you get

  • The prompt and the response on the span’s Input / Output tab, rendered as messages.
  • Cost for the call, computed by Rius from the model and the token counts. Costs are list-price estimates in US dollars.
  • The span in model analytics, with its model and token usage.
  • Control over what leaves your process. Content is only on a span if your code sets it, so you can leave the attributes off for sensitive calls and keep the model, tokens and timings.

If you want the prompt and the response without writing this by hand, the Rius SDK for TypeScript auto-instruments libraries such as OpenAI and records the messages for you. The Trace LLM calls with OpenTelemetry guide covers that path and the others.

When something else fits

  • If you call OpenAI, Anthropic or another supported library from TypeScript, the Rius SDK captures messages automatically, so you skip the manual attributes.
  • If you already run OpenLLMetry or OpenInference instrumentors, point their exporter at Rius. Rius maps their message attributes onto the GenAI keys at ingest.

Still stuck? Get help

Last updated on