ReqLLM. OpenTelemetry. Adapter behaviour
(ReqLLM v1.14.0)
View Source
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.