# `ReqLLM.Telemetry.OpenTelemetry`
[🔗](https://github.com/agentjido/req_llm/blob/main/lib/req_llm/telemetry/open_telemetry.ex#L1)

Dependency-free helpers for mapping ReqLLM telemetry metadata to
OpenTelemetry GenAI span data.

This module does not depend on an OpenTelemetry SDK and does not start or stop
spans on your behalf. Instead, it translates ReqLLM's native `:telemetry`
metadata into:

- GenAI span names
- GenAI span attributes
- span status hints
- span events (`exception`, optional `gen_ai.client.inference.operation.details`)

Content capture is opt-in through the `:content` option:

- `:none` (default) — no message, instructions, or tool definitions are emitted.
- `:attributes` — `gen_ai.input.messages`, `gen_ai.system_instructions`,
  `gen_ai.tool.definitions`, and `gen_ai.output.messages` are attached as
  span attributes.
- `:event` — the same payload is attached to a single
  `gen_ai.client.inference.operation.details` span event instead of the
  span attributes.

When content capture is on, ReqLLM request telemetry must also enable payload
capture with `telemetry: [payloads: :raw]`, otherwise the request payload is
not available to map.

Reasoning text remains redacted in every content mode — reasoning parts are
intentionally omitted from `gen_ai.input.messages` / `gen_ai.output.messages`.

# `content_mode`

```elixir
@type content_mode() :: :none | :attributes | :event
```

# `metric_record`

```elixir
@type metric_record() :: ReqLLM.OpenTelemetry.Metrics.histogram_record()
```

# `otel_event`

```elixir
@type otel_event() :: %{name: String.t(), attributes: map()}
```

# `request_start_stub`

```elixir
@type request_start_stub() :: %{
  name: String.t(),
  kind: :client,
  attributes: map(),
  events: [otel_event()]
}
```

# `request_terminal_stub`

```elixir
@type request_terminal_stub() :: %{
  attributes: map(),
  status: span_status(),
  events: [otel_event()],
  metrics: [metric_record()],
  tool_spans: [tool_span_stub()]
}
```

# `span_status`

```elixir
@type span_status() :: :ok | {:error, String.t()}
```

# `tool_span_stub`

```elixir
@type tool_span_stub() :: %{
  name: String.t(),
  kind: :internal,
  attributes: map(),
  status: span_status(),
  start_time: integer() | nil,
  end_time: integer() | nil
}
```

# `request_exception`

```elixir
@spec request_exception(map(), keyword()) :: request_terminal_stub()
```

Builds terminal span data for a `[:req_llm, :request, :exception]` event.

# `request_start`

```elixir
@spec request_start(map(), keyword()) :: request_start_stub()
```

Builds span creation data for a `[:req_llm, :request, :start]` event.

In `content: :event` mode the inference event payload is intentionally
deferred to the terminal stub (`request_stop/2` / `request_exception/2`)
so the host emits exactly one `gen_ai.client.inference.operation.details`
event per span, carrying both request and response content. Start-side
request content is still attached as span attributes when
`content: :attributes`.

# `request_stop`

```elixir
@spec request_stop(map(), keyword()) :: request_terminal_stub()
```

Builds terminal span data for a `[:req_llm, :request, :stop]` event.

Pass `measurements: %{duration: native}` to populate `metrics` and the
`gen_ai.response.time_to_first_chunk` span attribute on streaming requests.
Pass `langfuse: true` to add `langfuse.observation.cost_details` (JSON-encoded)
whenever ReqLLM has computed a cost breakdown.

# `tool_spans`

```elixir
@spec tool_spans(map(), keyword()) :: [tool_span_stub()]
```

Builds `gen_ai.execute_tool` sub-span stubs for server-side builtin
tool calls present on the response message.

Only entries flagged via `ReqLLM.ToolCall.builtin?/1` are surfaced —
user-defined function tool execution happens in the caller's process
and must be instrumented there.

When `metadata.builtin_tool_timing` carries wall-clock nanoseconds for
a given call id (streaming path), the stub propagates `start_time` /
`end_time` so the translator can emit a span with the measured
duration. Otherwise the fields are `nil`, and the translator falls
back to "start span / end span" back-to-back — effectively a
zero-width marker recording that the invocation occurred inside the
parent's lifetime.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
