LangChain.OpenTelemetry.SpanHandler (LangChain v0.9.5)

Copy Markdown View Source

Telemetry handler that creates OpenTelemetry spans from LangChain telemetry events.

Spans follow a subset of the GenAI Semantic Conventions (v1.40+) — see LangChain.OpenTelemetry.Attributes for the attributes that are emitted:

Telemetry EventOTel Span NameKindgen_ai.operation.name
[:langchain, :chain, :execute, ...]invoke_agent llm_chain:internalinvoke_agent
[:langchain, :llm, :call, ...]chat {model}:clientchat
[:langchain, :tool, :call, ...]execute_tool {tool_name}:internalexecute_tool

Span hierarchy is automatic for synchronous work: chain → LLM call → tool call run in the same process, so parent context propagation works via the process dictionary.

Attribute inheritance

Attributes a caller supplies through custom_context[:otel_attributes] (see LangChain.OpenTelemetry.Attributes.passthrough_attributes/1), along with gen_ai.conversation.id, are stashed on the OpenTelemetry context when the chain span opens, so the chat and execute_tool spans nested under it carry them too. Without this they would land on the chain span only, and a backend could group traces by tenant or session but not filter spans by them.

This matters most for the chat span, which is the one span that cannot reach custom_context at all: its telemetry metadata is built inside LangChain.ChatModels.ChatModel.llm_telemetry_span/3, called from a provider's call/3, and that callback takes no context argument.

Two properties worth knowing:

  • Inherited attributes always lose to attributes the span derives itself, so an inherited value can never mask a real one (notably gen_ai.request.model).
  • The stash rides the OpenTelemetry context, not baggage, so it follows the trace across process boundaries — including async: true tools — but is never serialized onto outbound requests. It is also unwound by the same OpenTelemetry.Ctx.detach/1 that ends the span, so there is no extra lifecycle to leak.

Disable with inherit_attributes: false (see LangChain.OpenTelemetry.Config).

Async tools

Tools declared with async: true execute in a separate Task process, and the OpenTelemetry context is not inherited across a process boundary. For tools run by LLMChain's built-in executor this is handled automatically — the chain captures the current context before spawning each async tool and re-attaches it inside the Task, so async tool spans nest under the chain span. If you spawn your own processes running LangChain operations, do the same yourself: capture OpenTelemetry.Ctx.get_current/0 before spawning and OpenTelemetry.Ctx.attach/1 inside the process (e.g. via the :on_tool_pre_execution callback).

Usage

This module is used internally by LangChain.OpenTelemetry.setup/1. You typically don't need to interact with it directly.

Summary

Functions

Returns the OpenTelemetry context key used to carry inherited span attributes.

Returns the list of telemetry events this handler attaches to.

Telemetry handler callback. Dispatches to the appropriate handler based on the event.

Returns the telemetry handler ID prefix used for attaching/detaching.

Functions

ctx_attributes_key()

@spec ctx_attributes_key() :: term()

Returns the OpenTelemetry context key used to carry inherited span attributes.

Exposed for LangChain.OpenTelemetry.Enrich, which lets a host seed the same attributes from outside a chain run.

events()

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

Returns the list of telemetry events this handler attaches to.

handle_event(event, measurements, metadata, config)

@spec handle_event([atom()], map(), map(), LangChain.OpenTelemetry.Config.t()) :: :ok

Telemetry handler callback. Dispatches to the appropriate handler based on the event.

handler_id()

@spec handler_id() :: String.t()

Returns the telemetry handler ID prefix used for attaching/detaching.