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

Native `:telemetry` emitter for ReqLLM request and reasoning lifecycle.

Every event for a logical request shares the same `request_id`, so request
lifecycle, reasoning lifecycle, and token usage can be correlated without
provider-specific parsing. The OpenTelemetry bridge (`ReqLLM.OpenTelemetry`)
is built on top of these events — attach handlers here for billing, tenant
attribution, or any integration that should not depend on an OpenTelemetry
SDK.

## Event families

| Event                                | Measurements                   |
|--------------------------------------|--------------------------------|
| `[:req_llm, :request, :start]`       | `system_time`                  |
| `[:req_llm, :request, :retry]`       | `duration`, `system_time`      |
| `[:req_llm, :request, :stop]`        | `duration`, `system_time`      |
| `[:req_llm, :request, :exception]`   | `duration`, `system_time`      |
| `[:req_llm, :reasoning, :start]`     | `system_time`                  |
| `[:req_llm, :reasoning, :update]`    | `system_time`                  |
| `[:req_llm, :reasoning, :stop]`      | `duration`, `system_time`      |
| `[:req_llm, :token_usage]`           | token + cost counters          |
| `[:req_llm, :tool_call_args_lost]`   | `count`                        |

`duration` is in native monotonic time units — convert with
`System.convert_time_unit/3` if you want milliseconds.

## Request metadata

All request lifecycle events carry the same base metadata map: `request_id`,
`operation`, `mode`, `provider`, `model`, `transport`, `reasoning`,
`request_summary`, `response_summary`, `http_status`, `finish_reason`,
`usage`, `request_options`, `server`, `request_started_system_time`.
Streaming requests additionally carry `streaming`. The full shape and a worked
example live in the [Telemetry guide](https://hexdocs.pm/req_llm/telemetry.html).

Reasoning events never include raw thinking text — they are metadata-only
even with payload capture enabled.

## Payload modes

Default is metadata-only. Opt into raw payloads globally or per call:

    config :req_llm, telemetry: [payloads: :raw]

    ReqLLM.generate_text(model, prompt, telemetry: [payloads: :raw])

Raw payloads are still sanitized — reasoning text is redacted, binary parts
are summarized by byte size and media type, embeddings report vector counts
rather than vectors. Use with care in multi-tenant systems.

## Token usage compatibility

`[:req_llm, :token_usage]` remains available for existing consumers and now
fires for streaming as well as non-streaming requests. For new integrations,
prefer `[:req_llm, :request, :stop]` — it includes duration, finish reason,
summaries, and normalized reasoning metadata alongside usage.

## See also

- [Telemetry guide](https://hexdocs.pm/req_llm/telemetry.html) — full event
  shapes, reasoning normalization, payload capture, attach examples
- [V1 telemetry contract](https://hexdocs.pm/req_llm/telemetry-contract.html) —
  stability, units, redaction, and complete event inventory
- `ReqLLM.OpenTelemetry` — the auto-attached GenAI client span bridge
- `ReqLLM.Telemetry.OpenTelemetry` — dependency-free OTel mapper

# `context`

```elixir
@type context() :: %{
  request_id: String.t(),
  model: LLMDB.Model.t(),
  operation: atom(),
  mode: lifecycle_mode(),
  transport: transport(),
  payload_mode: payload_mode(),
  reasoning_contract: reasoning_contract(),
  original_opts: keyword(),
  request_options: map(),
  server: map(),
  request_summary: map(),
  request_payload: any(),
  request_started?: boolean(),
  request_stopped?: boolean(),
  started_at: integer() | nil,
  request_started_system_time: integer() | nil,
  first_chunk_at: integer() | nil,
  requested_reasoning: map(),
  effective_reasoning: map(),
  reasoning_started?: boolean(),
  reasoning_started_at: integer() | nil,
  reasoning_observation: map(),
  response_summary_state: map()
}
```

# `lifecycle_mode`

```elixir
@type lifecycle_mode() :: :sync | :stream
```

# `payload_mode`

```elixir
@type payload_mode() :: :none | :raw
```

# `reasoning_contract`

```elixir
@type reasoning_contract() ::
  :openai_effort
  | :openai_or_thinking
  | :anthropic_thinking
  | :platform_anthropic
  | :google_budget
  | :alibaba_thinking
  | :thinking_toggle
  | :zenmux_reasoning
  | :unsupported
```

# `transport`

```elixir
@type transport() :: :req | :finch | :websocket | :in_process
```

# `emit_token_usage`

```elixir
@spec emit_token_usage(LLMDB.Model.t(), map() | nil, keyword()) :: :ok
```

Emits the compatibility token usage event.

# `events`

```elixir
@spec events() :: [[atom()]]
```

Returns every native telemetry event emitted by ReqLLM V1.

Use `stable_events/0` when attaching a long-lived integration. The remaining
events are included here so diagnostic tooling can attach without maintaining
a duplicate inventory.

# `exception_request`

```elixir
@spec exception_request(context(), Exception.t() | term(), keyword()) :: context()
```

Emits request exception telemetry and returns the updated context.

# `experimental_events`

```elixir
@spec experimental_events() :: [[atom()]]
```

Returns events that remain available in V1 but whose detailed shape is
experimental.

# `new_context`

```elixir
@spec new_context(LLMDB.Model.t(), keyword(), keyword()) :: context()
```

Builds a telemetry context for a request lifecycle.

# `observe_response`

```elixir
@spec observe_response(context(), any()) :: context()
```

Observes a terminal response and updates response and reasoning state.

# `observe_stream_chunk`

```elixir
@spec observe_stream_chunk(context(), ReqLLM.StreamChunk.t()) :: context()
```

Folds a streaming chunk into the telemetry context and emits milestone
reasoning events when applicable.

Called by ReqLLM's streaming pipeline (`ReqLLM.StreamServer`) for every
chunk produced during a streaming request. Returns an updated context
with:

- `first_chunk_at` set to `System.monotonic_time/0` on the first non-empty
  content chunk or first tool call — this feeds
  `streaming.time_to_first_chunk` on the request lifecycle metadata and
  `gen_ai.client.operation.time_to_first_chunk` on the OpenTelemetry
  bridge.
- `response_summary` counters incremented for text bytes, thinking bytes,
  tool calls, etc.
- A `[:req_llm, :reasoning, :update]` event emitted with
  `milestone: :content_started` the first time a reasoning chunk is
  observed.

Hosts integrating against the low-level streaming API (`ReqLLM.Streaming`)
do not normally call this directly — ReqLLM threads it through the
streaming pipeline. The high-level `stream_text/3` / `stream_object/4`
APIs use it transparently.

# `put_request_context`

```elixir
@spec put_request_context(Req.Request.t(), context()) :: Req.Request.t()
```

Stores telemetry context in a Req request.

# `put_response_context`

```elixir
@spec put_response_context(Req.Response.t(), context()) :: Req.Response.t()
```

Stores telemetry context in a Req response private map.

# `put_server_from_source`

```elixir
@spec put_server_from_source(context(), any()) :: context()
```

Pre-populates `context.server` from a request source that `start_request`
cannot read directly (e.g. an HTTPContext for streaming flows). Has no
effect if the source yields no server info.

# `reasoning_metadata`

```elixir
@spec reasoning_metadata(context(), map()) :: map()
```

Returns the normalized metadata map for reasoning lifecycle events.

# `request_context`

```elixir
@spec request_context(Req.Request.t()) :: context() | nil
```

Reads telemetry context from a Req request.

# `request_context_key`

```elixir
@spec request_context_key() :: atom()
```

Returns the private key used to store telemetry context on Req requests.

# `request_metadata`

```elixir
@spec request_metadata(context(), map()) :: map()
```

Returns the normalized request metadata map for request lifecycle events.

# `stable_events`

```elixir
@spec stable_events() :: [[atom()]]
```

Returns the V1 stable telemetry core.

This includes request lifecycle events and the backwards-compatible token
usage event. Stream first-output timing is carried by request lifecycle
metadata rather than a separate event.

# `start_request`

```elixir
@spec start_request(context(), any()) :: context()
```

Emits request start telemetry and returns the updated context.

# `stop_request`

```elixir
@spec stop_request(context(), any(), keyword()) :: context()
```

Emits request stop telemetry and returns the updated context.

# `usage_from_response`

```elixir
@spec usage_from_response(any()) :: map() | nil
```

Extracts token usage metadata from a Req response private map.

---

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