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

Behaviour the OpenTelemetry bridge uses to talk to a tracer.

`ReqLLM.OpenTelemetry` ships `ReqLLM.OpenTelemetry.OTelAdapter` as the
default implementation, which calls the standard `:otel_tracer` and
`:otel_span` API. Implement this behaviour to swap in a different tracer,
inject extra attributes on every span (e.g. caller-context like
`langfuse.user.id`), or run the bridge in test mode without an OpenTelemetry
SDK.

Pass your module via `:adapter`:

    ReqLLM.OpenTelemetry.attach("req-llm-otel", adapter: MyApp.ReqLLMAdapter)

## Required callbacks

`available?/0`, `start_span/3`, `set_attributes/3`, `add_event/4`,
`set_status/4`, `end_span/2`.

## Optional callbacks (metrics)

`metrics_available?/0`, `record_histogram/2`. The bridge only invokes
these when both `available?/0` and `metrics_available?/0` return `true`.

## Optional callbacks (child spans for server-side tool execution)

`start_child_span/5`, `end_span_at/3`. The bridge invokes these to emit
`gen_ai.execute_tool` child spans for server-side builtin tool calls
(e.g. `web_search_call` on the OpenAI Responses API). Adapters that
don't implement them get a fallback: the bridge calls `start_span/3` +
`end_span/2` instead, which records the same data but loses the
parent-child relationship (the sub-spans appear as siblings).

## Example — inject caller-context on every ReqLLM span

The cleanest way to wrap the default adapter is to delegate everything and
override just `start_span/3` to merge in extra attributes. Include the
optional callbacks in the delegation if your app uses tools — otherwise
server-side tool sub-spans render as siblings of the LLM span instead of
children, losing the call-tree shape Langfuse / Honeycomb / Grafana use to
group tool execution under its parent.

    defmodule MyApp.ReqLLMAdapter do
      @behaviour ReqLLM.OpenTelemetry.Adapter

      # required callbacks
      defdelegate available?(), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate set_attributes(s, a, c), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate add_event(s, n, a, c), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate set_status(s, k, m, c), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate end_span(s, c), to: ReqLLM.OpenTelemetry.OTelAdapter

      # optional callbacks — delegate to preserve metrics + child-span shape
      defdelegate metrics_available?(), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate record_histogram(r, c), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate start_child_span(p, n, a, o, c), to: ReqLLM.OpenTelemetry.OTelAdapter
      defdelegate end_span_at(s, t, c), to: ReqLLM.OpenTelemetry.OTelAdapter

      # the one callback we actually customize
      def start_span(name, attrs, config) do
        extras = %{"langfuse.user.id" => Process.get(:current_user_id)}
        ReqLLM.OpenTelemetry.OTelAdapter.start_span(name, Map.merge(attrs, extras), config)
      end
    end

    ReqLLM.OpenTelemetry.attach("req-llm-otel", adapter: MyApp.ReqLLMAdapter)

See the Telemetry guide's caller-context section for when to use this
versus a parent span or OTel baggage.

# `add_event`

```elixir
@callback add_event(term(), atom() | String.t(), map(), keyword()) :: :ok
```

# `available?`

```elixir
@callback available?() :: boolean()
```

# `end_span`

```elixir
@callback end_span(term(), keyword()) :: :ok
```

# `end_span_at`
*optional* 

```elixir
@callback end_span_at(span :: term(), end_time :: integer(), config :: keyword()) :: :ok
```

# `metrics_available?`
*optional* 

```elixir
@callback metrics_available?() :: boolean()
```

# `record_histogram`
*optional* 

```elixir
@callback record_histogram(map(), keyword()) :: :ok
```

# `set_attributes`

```elixir
@callback set_attributes(term(), map(), keyword()) :: :ok
```

# `set_status`

```elixir
@callback set_status(term(), :ok | :error, String.t() | nil, keyword()) :: :ok
```

# `start_child_span`
*optional* 

```elixir
@callback start_child_span(
  parent :: term(),
  name :: String.t(),
  attributes :: map(),
  opts :: %{optional(:kind) =&gt; atom(), optional(:start_time) =&gt; integer()},
  config :: keyword()
) :: term()
```

# `start_span`

```elixir
@callback start_span(String.t(), map(), keyword()) :: term()
```

---

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