LangChain.OpenTelemetry (LangChain v0.9.0)

Copy Markdown View Source

OpenTelemetry integration for LangChain.

Attaches to LangChain's :telemetry events and translates them into OpenTelemetry spans and metrics following a subset of the GenAI Semantic Conventions (v1.40+). See LangChain.OpenTelemetry.Attributes for exactly which attributes are (and are not) emitted.

Setup

Add the optional dependencies to your mix.exs:

{:opentelemetry_api, "~> 1.4"},
{:opentelemetry, "~> 1.5"},
{:opentelemetry_exporter, "~> 1.8"}

Then call setup/1 in your application startup (e.g., Application.start/2):

LangChain.OpenTelemetry.setup()

Options

Options are passed through to LangChain.OpenTelemetry.Config:

  • :capture_input_messages - Record input messages as span attributes. Default: false. When enabled, serializes LangChain.Message structs sent to the LLM into gen_ai.input.messages as a JSON string. May contain sensitive data.
  • :capture_output_messages - Record output messages as span attributes. Default: false. When enabled, serializes the LLM response into gen_ai.output.messages. May contain sensitive data.
  • :capture_tool_arguments - Record tool call arguments. Default: false. Serializes tool call arguments into gen_ai.tool.call.arguments.
  • :capture_tool_results - Record tool call results. Default: false. Captures tool return values into gen_ai.tool.call.result.
  • :enable_metrics - Re-emit operation-duration and token-usage metric events. Default: true. See the note below.

Metrics require a consumer

:enable_metrics does not record OpenTelemetry histograms or counters on its own. It re-emits LangChain telemetry as the intermediary events [:langchain, :otel, :operation, :duration] and [:langchain, :otel, :token, :usage]. To turn these into real metrics, attach a consumer such as Telemetry.Metrics (with an OpenTelemetry reporter), PromEx, or equivalent. Without a consumer, enable_metrics: true has no observable effect. See LangChain.OpenTelemetry.MetricsHandler for the emitted event shapes.

Span Mapping

Telemetry EventOTel Span NameKind
[:langchain, :chain, :execute, ...]invoke_agent llm_chain:internal
[:langchain, :llm, :call, ...]chat {model}:client
[:langchain, :tool, :call, ...]execute_tool {tool_name}:internal

Langfuse Integration

Langfuse can ingest OpenTelemetry traces via its OTLP-compatible endpoint. Configure the OTel exporter to point at your Langfuse instance:

# config/runtime.exs
config :opentelemetry_exporter,
  otlp_protocol: :http_protobuf,
  otlp_endpoint: "https://your-langfuse-host/api/public/otel",
  otlp_headers: [
    {"Authorization", "Basic " <> Base.encode64("pk-lf-...:sk-lf-...")}
  ]

Langfuse-specific attributes via custom_context

Set custom_context on your LLMChain to propagate Langfuse trace attributes:

chain =
  %{llm: llm, messages: messages}
  |> LLMChain.new!()
  |> Map.put(:custom_context, %{
    langfuse_user_id: current_user.id,
    langfuse_session_id: session_id,
    langfuse_tags: ["production", "v2"],
    langfuse_metadata: %{env: "prod", feature: "chat"}
  })

These are mapped to span attributes on the root chain span:

custom_context keySpan attribute
:langfuse_trace_namelangfuse.trace.name
:langfuse_user_idlangfuse.user.id
:langfuse_session_idlangfuse.session.id
:langfuse_tagslangfuse.trace.tags
:langfuse_metadatalangfuse.trace.metadata.*

Summary

Functions

Attaches OpenTelemetry handlers to LangChain telemetry events.

Detaches all OpenTelemetry handlers from LangChain telemetry events.

Executes a function with OpenTelemetry tracing suppressed.

Functions

setup(opts \\ [])

@spec setup(keyword()) :: :ok

Attaches OpenTelemetry handlers to LangChain telemetry events.

Examples

LangChain.OpenTelemetry.setup()
LangChain.OpenTelemetry.setup(capture_input_messages: true, enable_metrics: false)

teardown()

@spec teardown() :: :ok

Detaches all OpenTelemetry handlers from LangChain telemetry events.

without_tracing(fun)

@spec without_tracing((-> result)) :: result when result: any()

Executes a function with OpenTelemetry tracing suppressed.

Any LangChain operations inside the function will NOT create OTEL spans or traces, and — when metrics are enabled — will NOT emit duration/token metric events either. Useful for lightweight utility chains (translation, topic generation) that should not appear in your traces or skew your metrics.

Suppression is scoped to the calling process. Work that runs in a separate process spawned inside the block (e.g. an async: true tool) does not inherit it, the same way OTel span context does not cross the process boundary.

Example

LangChain.OpenTelemetry.without_tracing(fn ->
  LLM.chain() |> LLMChain.run()
end)