> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselemma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LangChain

> Instrument a LangChain app so each run is one nested trace in Lemma

[LangChain](https://www.langchain.com) chains and agents emit spans through Langfuse's callback handler. Add the handler, point Langfuse at Lemma, and wrap each run in one root span so chains, model calls, and tool calls become a single nested trace. Use the [Langfuse LangChain integration](https://langfuse.com/integrations/frameworks/langchain) as the reference.

<Note>
  **One agent execution = one trace.** Wrap the run in a single root span so every model and tool call nests under it. See the [trace contract](/reference/trace-contract).
</Note>

<Note>
  LangChain traces **render fully** in Lemma today. Automated [issue detection](/reference/good-vs-bad-traces) is being expanded to this shape — see [Good trace vs bad trace](/reference/good-vs-bad-traces) for current status.
</Note>

## Recipe

<Steps>
  <Step title="Install">
    <Tabs>
      <Tab title="Python">
        ```bash theme={null}
        pip install langchain langchain-openai langfuse opentelemetry-sdk opentelemetry-exporter-otlp
        ```
      </Tab>

      <Tab title="TypeScript">
        ```bash theme={null}
        npm install langchain @langchain/openai @langfuse/langchain @langfuse/otel @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Register the Langfuse → Lemma exporter">
    Register the exporter once at startup, before any chain runs. This matches [Setup](/tracing/instrumentation/setup).

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        # instrumentation.py — imported first, before your app code
        import os
        from opentelemetry import trace
        from opentelemetry.sdk.trace import TracerProvider
        from opentelemetry.sdk.trace.export import BatchSpanProcessor
        from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

        provider = TracerProvider()
        provider.add_span_processor(
            BatchSpanProcessor(
                OTLPSpanExporter(
                    endpoint=os.environ["LEMMA_BASE_URL"],
                    headers={
                        "Authorization": f"Bearer {os.environ['LEMMA_API_KEY']}",
                        "X-Lemma-Project-ID": os.environ["LEMMA_PROJECT_ID"],
                    },
                )
            )
        )
        trace.set_tracer_provider(provider)
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        // instrumentation.ts — imported first, before your app code
        import { LangfuseSpanProcessor } from "@langfuse/otel";
        import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
        import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";

        export const lemmaProcessor = new LangfuseSpanProcessor({
          exporter: new OTLPTraceExporter({
            url: process.env.LEMMA_BASE_URL,
            headers: {
              Authorization: `Bearer ${process.env.LEMMA_API_KEY}`,
              "X-Lemma-Project-ID": process.env.LEMMA_PROJECT_ID,
            },
          }),
        });

        new NodeTracerProvider({ spanProcessors: [lemmaProcessor] }).register();
        ```
      </Tab>
    </Tabs>

    Set the environment variables. Lemma-only export needs no `LANGFUSE_*` credentials.

    ```bash theme={null}
    export LEMMA_BASE_URL="https://api.uselemma.ai/otel/v1/traces"
    export LEMMA_API_KEY="lma_..."
    export LEMMA_PROJECT_ID="proj_..."
    ```
  </Step>

  <Step title="Add the Langfuse callback handler">
    Pass Langfuse's `CallbackHandler` to your chain or agent so its model and tool calls are captured as spans.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        from langfuse.langchain import CallbackHandler

        langfuse_handler = CallbackHandler()
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        import { CallbackHandler } from "@langfuse/langchain";

        const langfuseHandler = new CallbackHandler();
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Wrap the whole run in one root span">
    Wrap the chain invocation in a single root span and pass the handler in the call config. Every LangChain span then nests under one trace. Record the input and final output on the root, and set a stable agent name.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        from langfuse import get_client

        langfuse = get_client()

        def run_support_agent(user_message: str, thread_id: str) -> str:
            with langfuse.start_as_current_span(name="support-agent") as root:
                root.update(input=user_message)
                langfuse.update_current_trace(
                    name="support-agent",
                    session_id=thread_id,
                    metadata={"gen_ai.agent.name": "support-agent"},
                )

                result = chain.invoke(
                    {"input": user_message},
                    config={"callbacks": [langfuse_handler]},
                )

                root.update(output=result)
                return result
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        import { propagateAttributes, startActiveObservation } from "@langfuse/tracing";

        export async function runSupportAgent(userMessage: string, threadId: string) {
          return await startActiveObservation("support-agent", async (root) => {
            root.update({ input: userMessage });

            return await propagateAttributes(
              {
                traceName: "support-agent",
                sessionId: threadId,
                metadata: { "gen_ai.agent.name": "support-agent" },
              },
              async () => {
                const result = await chain.invoke(
                  { input: userMessage },
                  { callbacks: [langfuseHandler] },
                );

                root.update({ output: result });
                return result;
              },
            );
          });
        }
        ```
      </Tab>
    </Tabs>

    The LangChain spans created inside the callback become children of the root, producing one nested trace:

    ```text theme={null}
    support-agent              ← trace root (input, output)
    ├─ ChatOpenAI              ← generation (model, tokens)
    ├─ search_docs             ← tool call (args, result)
    └─ ChatOpenAI              ← generation (final answer)
    ```
  </Step>

  <Step title="Flush before the process exits">
    In short-lived runtimes, flush so the whole trace ships in one batch.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        from langfuse import get_client

        get_client().flush()
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript theme={null}
        import { lemmaProcessor } from "./instrumentation";

        await lemmaProcessor.forceFlush();
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

<Warning>
  If chain steps show up as their own separate traces, the chain ran outside the root's active context. Keep `chain.invoke` inside the root span and pass the handler on every call. See [Troubleshooting](/tracing/troubleshooting/common-problems#every-span-appears-as-a-separate-trace).
</Warning>

## Verify in Lemma

Open the [Lemma dashboard](https://platform.uselemma.ai) → **Traces** and confirm:

* **One trace per run** — a full chain or agent run is one trace, not one per model call.
* **Root has input and output** — the root span shows the user message and the final response.
* **Generations are nested** — each model call appears as a child generation with model and token usage.
* **Tools are nested** — each tool invocation appears as a child tool span with arguments and result.

## Next steps

<CardGroup cols={2}>
  <Card title="Trace contract" icon="file-check" href="/reference/trace-contract">
    The exact shape Lemma reads.
  </Card>

  <Card title="Setup" icon="plug" href="/tracing/instrumentation/setup">
    Wire the Langfuse → Lemma exporter.
  </Card>

  <Card title="Threads and sessions" icon="users" href="/tracing/instrumentation/context">
    Group multi-turn conversations with a thread id.
  </Card>

  <Card title="Good vs bad traces" icon="circle-check" href="/reference/good-vs-bad-traces">
    What issue detection looks for, per shape.
  </Card>
</CardGroup>
