Configure Telemetry: OTel GenAI Semantic Conventions
If you’re building a framework, orchestrator, or agent runtime, implement the OpenTelemetry Semantic Conventions for GenAI directly instead of coupling to the Rhesis SDK. Rhesis already ships a translation layer that consumes gen_ai.* spans and maps them into the Rhesis ai.* schema: this is exactly how the Microsoft Agent Framework and Pydantic AI integrations work today.
Who This Is For
- You’re building a framework other people will use, and don’t want them coupled to Rhesis
- You want telemetry that works with any OTel-compatible backend (Jaeger, Honeycomb, Datadog, …) and Rhesis
- You already emit OTel spans and want to align with the emerging standard
If you’re building an application rather than a framework, use Auto-Instrumentation or Decorators instead: they’re less setup for the same result.
Operation Mapping
The spec defines several GenAI operation types. Here’s how each maps to Rhesis today. The “Rhesis Translation” column is the exported span’s name, which is distinct from its ai.operation.type attribute value (see Attribute Mapping below):
| GenAI Operation | Span Name Pattern | Rhesis Span Name | Status |
|---|---|---|---|
chat | chat {model} | ai.llm.invoke | Fully supported |
invoke_agent | invoke_agent {agent_name} | ai.agent.invoke | Fully supported |
create_agent | create_agent {agent_name} | ai.agent.invoke | Fully supported |
execute_tool | execute_tool {tool_name} | ai.tool.invoke | Fully supported |
embeddings | embeddings {model} | ai.embedding.generate | Fully supported |
invoke_workflow | invoke_workflow {name} | function.{framework}.* | Fallback: no semantic mapping yet |
plan | plan {agent_name} | function.{framework}.* | Fallback: no semantic mapping yet |
chat, invoke_agent, create_agent, execute_tool, and embeddings translate fully: correct attribute mapping, token extraction, and prompt/completion event synthesis included. invoke_workflow and plan land under a function.* fallback name: visible in traces, but without semantic interpretation yet.
Span name and ai.operation.type are two different fields, and they don’t always match textually. An embeddings span is named ai.embedding.generate, but its ai.operation.type attribute is set to embedding.create: both refer to the same operation, just via different naming (the span name follows the ai.<domain>.<action> convention; the attribute value tracks the GenAI spec’s own operation vocabulary more closely).
Attribute Mapping
The translation layer maps gen_ai.* attributes to ai.* and preserves the originals, so both coexist on the exported span:
| GenAI Attribute | Rhesis Attribute | Notes |
|---|---|---|
gen_ai.operation.name | ai.operation.type | chat→llm.invoke, invoke_agent→agent.invoke, execute_tool→tool.invoke, embeddings→embedding.create |
gen_ai.request.model | ai.model.name | Preferred over response.model when both present |
gen_ai.response.model | ai.model.name | Used when request model is absent |
gen_ai.provider.name | ai.model.provider | |
gen_ai.system | ai.model.provider | Legacy alias, same target |
gen_ai.usage.input_tokens | ai.llm.tokens.input | |
gen_ai.usage.output_tokens | ai.llm.tokens.output | |
| (computed) | ai.llm.tokens.total | input + output, synthesized |
gen_ai.request.temperature | ai.llm.temperature | |
gen_ai.request.max_tokens | ai.llm.max_tokens | |
gen_ai.response.finish_reasons | ai.llm.finish_reason | |
gen_ai.tool.name | ai.tool.name | |
gen_ai.tool.type | ai.tool.type | |
gen_ai.agent.name | ai.agent.name | |
gen_ai.conversation.id | ai.session.id |
Content Capture
The GenAI spec stores message content as structured JSON attributes:
Rhesis translates these into ai.prompt and ai.completion span events rendered in the trace UI. Tool I/O attributes (gen_ai.tool.call.arguments, gen_ai.tool.call.result) translate similarly into ai.tool.input / ai.tool.output events.
Populate these attributes: they’re what makes traces useful in the Rhesis UI (actual prompts and completions inline, not just span names). Privacy-sensitive deployments can opt out with RHESIS_DISABLE_CONTENT_CAPTURE=true.
Agent Handoffs
Rhesis synthesizes ai.agent.handoff spans from two patterns:
- Tool-call pattern: an
execute_toolspan wheregen_ai.tool.namestarts withhandoff_to_. The target agent is parsed from the suffix (handoff_to_researcher→researcher); the source agent is resolved by walking the span’s parent chain. - Message pattern: a
chatspan whosegen_ai.output.messagescontains atool_callpart namedhandoff_to_*. This covers frameworks (like MAF) where handoff middleware short-circuits before a tool-execution span is created.
If your framework supports agent-to-agent delegation, name your handoff tools handoff_to_{target_agent}. That’s the convention Microsoft Agent Framework uses, and it’s enough signal for Rhesis to draw handoff edges in the graph view with zero Rhesis-specific instrumentation.
Setup
Initialize the Rhesis client and enable translation
If your framework is already in the auto-instrument list:
This wraps the OTLP exporter with a translating exporter that rewrites gen_ai.* spans into the Rhesis schema before export.
Emit the minimum viable span set
If you’re building a new framework, this is what to emit for full Rhesis compatibility:
These spans work with any OTel-compatible backend (they follow the standard) and Rhesis translates them automatically into ai.llm.invoke, ai.agent.invoke, ai.tool.invoke, and ai.agent.handoff with full attribute mapping.
What Is Not Yet Translated
These GenAI spec features land in Rhesis as passthrough attributes, preserved on the span and shipped to the backend, but not yet rendered specially in the UI:
| GenAI Feature | Status | What Happens Today |
|---|---|---|
`invoke_workflow` operation | Fallback | Lands as `function.{framework}.*`, visible but uninterpreted |
`plan` operation | Fallback | Lands as `function.{framework}.*`, visible but uninterpreted |
gen_ai.plan.reasoning` / `gen_ai.plan.steps | Passthrough | Preserved as raw attributes |
gen_ai.workflow.name` / `gen_ai.workflow.id | Passthrough | Preserved as raw attributes |
gen_ai.data_source.id | Passthrough | Preserved as raw attributes |
gen_ai.output.type | Passthrough | Preserved as raw attributes |
gen_ai.usage.cache_creation.input_tokens | Passthrough | Preserved as raw attributes |
gen_ai.usage.cache_read.input_tokens | Passthrough | Preserved as raw attributes |
Memory operations (`create_memory`, `search_memory`, …) | Passthrough | Not yet emitted by any framework we integrate with |
Passthrough means the data is queryable but the Rhesis UI doesn’t render it specially yet: no plan visualization, no workflow grouping, no cache-hit indicators. As the GenAI spec stabilizes and frameworks adopt these operations, we’ll add semantic mappings.
Your framework now emits spec-native telemetry. Any application built on it gets Rhesis traces automatically, with zero Rhesis-specific code in the framework itself.
Related:
- OTel GenAI Semantic Conventions Spec
- Microsoft Agent Framework - a working example of this pattern
- Semantic Conventions Reference - the Rhesis
ai.*schema these spans translate into - Configuring Telemetry - back to the mode overview