# `LangChain.OpenTelemetry`
[🔗](https://github.com/brainlid/langchain/blob/v0.9.3/lib/open_telemetry.ex#L10)

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](https://opentelemetry.io/docs/specs/semconv/gen-ai/) (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 {: .warning}
>
> `: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 Event                       | OTel Span Name              | Kind       |
|---------------------------------------|-----------------------------|------------|
| `[: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](https://langfuse.com/) 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` key    | Span attribute                     |
|-------------------------|------------------------------------|
| `:langfuse_trace_name`  | `langfuse.trace.name`              |
| `:langfuse_user_id`     | `langfuse.user.id`                 |
| `:langfuse_session_id`  | `langfuse.session.id`              |
| `:langfuse_tags`        | `langfuse.trace.tags`              |
| `:langfuse_metadata`    | `langfuse.trace.metadata.*`        |

# `setup`
[🔗](https://github.com/brainlid/langchain/blob/v0.9.3/lib/open_telemetry.ex#L118)

```elixir
@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`
[🔗](https://github.com/brainlid/langchain/blob/v0.9.3/lib/open_telemetry.ex#L202)

```elixir
@spec teardown() :: :ok
```

Detaches all OpenTelemetry handlers from LangChain telemetry events.

# `without_tracing`
[🔗](https://github.com/brainlid/langchain/blob/v0.9.3/lib/open_telemetry.ex#L166)

```elixir
@spec without_tracing((-&gt; 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)

---

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