Span attribute reference
The wire format Rius reads. If you emit spans from anything other than the SDKs (a vanilla OpenTelemetry SDK, a collector pipeline, another language), this page tells you which attributes to set so your traces get the same analytics as SDK-emitted ones.
Where to send spans
OTLP/HTTP with protobuf encoding:
POST https://ingest.eu.console.rius-glassflow.com/v1/traces
Authorization: Bearer <your API key>
Content-Type: application/x-protobufThe contract in full:
- Host: region-qualified. All Rius endpoints carry the region in
the subdomain (
ingest.eu.console.rius-glassflow.com,eu.console.rius-glassflow.com); use your workspace’s region. Examples throughout these docs useeu. - Protocol: OTLP/HTTP, protobuf encoding only (
http/protobuf). OTLP/JSON is rejected with415; there is no gRPC endpoint. Protobuf is the default encoding of every official OTLP/HTTP exporter and of the collector’sotlphttpexporter, so this only matters if you changed it. - Compression:
Content-Encoding: gzipis accepted (and worth enabling; span batches compress well). - Body limit: 16 MiB per request. Exporters’ default batch sizes sit far below this.
- Auth:
Authorization: Bearerwith an API key from the console.401means the key is wrong or revoked.
Setup for each common sender is one page away: a vanilla OpenTelemetry SDK, OpenLLMetry, OpenInference, or an OpenTelemetry Collector.
Span taxonomy
| Attribute | Values | Effect |
|---|---|---|
openinference.span.kind | AGENT, LLM, TOOL, RETRIEVER, EMBEDDING, CHAIN | Classifies the span in trace views and per-kind analytics |
gen_ai.operation.name | chat, text_completion, embeddings, execute_tool, invoke_agent, … | The OpenTelemetry GenAI operation taxonomy; set it alongside the kind where one maps |
LLM analytics
These drive model analytics and cost computation:
| Attribute | Type | Notes |
|---|---|---|
gen_ai.request.model | string | The requested model |
gen_ai.usage.input_tokens | int | Prompt tokens |
gen_ai.usage.output_tokens | int | Completion tokens |
gen_ai.provider.name | string | e.g. openai, anthropic |
gen_ai.response.model | string | Model reported by the provider |
gen_ai.request.<param> | any | Request parameters, e.g. gen_ai.request.temperature |
gen_ai.response.finish_reasons | string[] | Why generation stopped |
Cost is computed server-side from gen_ai.request.model plus the two
gen_ai.usage.* token counts. A span missing any of the three gets no
cost. Never send a cost attribute; send tokens and the model name.
Content
| Attribute | Applies to | Notes |
|---|---|---|
gen_ai.input.messages / gen_ai.output.messages | LLM spans | JSON in the GenAI message shape (below) |
input.value / output.value | any span | Generic input/output, JSON or plain text |
Content attributes are stored for trace inspection and are the ones covered by the SDK’s privacy controls when you export through it.
The GenAI message shape
gen_ai.input.messages and gen_ai.output.messages carry a JSON array of
messages, each {"role", "parts": [...]}. Text parts are
{"type": "text", "content": ...}; tool interactions have dedicated part
types:
[
{"role": "user", "parts": [{"type": "text", "content": "What is 2+2?"}]},
{
"role": "assistant",
"parts": [
{"type": "tool_call", "id": "call_1", "name": "calculator", "arguments": "{\"expr\": \"2+2\"}"}
]
},
{
"role": "tool",
"parts": [{"type": "tool_call_response", "id": "call_1", "response": "4"}]
}
]This is the OpenTelemetry GenAI message schema; the SDK’s set_input() /
set_output() normalize OpenAI-style dicts into it
(message formats). When emitting directly,
produce this shape yourself so message rendering in the console works.
Spans carrying it render their prompt and completion on the span’s
Input / Output tab:

Spans without these attributes show “Content not captured” there; the rest of the span (timings, tokens, cost) is unaffected.
Span events
| Event name | Meaning |
|---|---|
gen_ai.first_token | First streamed chunk arrived; time to first token is derived as this event’s time minus the span start |
exception | Standard OpenTelemetry exception event (record_exception); surfaces in error analysis |
For streaming LLM calls, emit gen_ai.first_token once, when the first
content chunk arrives. The SDK’s
record_first_token() does exactly
this.
Resource attributes
| Attribute | Effect |
|---|---|
service.name | The service every trace view and filter groups by. Always set it. |
Supported conventions
What each attribute family gets today. “Analytics” means the promoted columns above (taxonomy, model, tokens, cost, TTFT); “stored” means the attribute lands with the span and is searchable in trace inspection either way.
| Convention | Attributes | Analytics | Stored |
|---|---|---|---|
| OTel GenAI | gen_ai.* as documented above | ✅ full | ✅ |
| OpenInference kind | openinference.span.kind | ✅ taxonomy | ✅ |
| OpenInference generic I/O | input.value / output.value | ✅ content | ✅ |
| OpenInference LLM | llm.model_name, llm.provider, llm.token_count.*, llm.finish_reason, llm.input_messages.* / llm.output_messages.* | ✅ normalized at ingest | ✅ |
| OpenLLMetry / Traceloop | traceloop.entity.*, legacy gen_ai.prompt.* / gen_ai.completion.* | ⏳ planned | ✅ |
| Anything else | your own attributes | no | ✅ |
“Normalized at ingest” means the platform copies the values into their
gen_ai.* equivalents before storage: llm.model_name fills the request and
response model, llm.token_count.prompt/completion fill the token counts
(which is what makes cost computation work), and the flattened
llm.input_messages.* / llm.output_messages.* are reassembled into the
GenAI message shape. Native gen_ai.* keys always win; normalization fills
gaps, never overwrites. The OpenInference first-token span event is likewise
renamed to gen_ai.first_token.
The practical consequence of the remaining ⏳ row: spans arrive, nest
correctly, and are fully inspectable, but model, token, and cost analytics
stay empty until that mapping ships. To get full analytics from those
emitters today, also set the gen_ai.* attributes from the tables above;
duplicating an attribute in two conventions is harmless.
Unknown attributes are never dropped and never an error.
Next steps
- Vanilla OpenTelemetry SDKs: emitting these attributes from any language.
- OpenTelemetry Collector: routing them through a collector pipeline.