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

# Add TracePilot AI tracing to your LangChain agents

> Add TracePilot tracing to LangChain agents by wrapping the underlying OpenAI completion calls — without changing how your chains or agents work.

TracePilot traces the OpenAI calls that LangChain makes under the hood. You don't wrap LangChain itself — you intercept the `openai.chat.completions.create` call at the point where LangChain invokes it, using a custom LangChain callback or a thin wrapper around the OpenAI client. This gives you full span visibility without touching your chain or agent logic.

## How the pattern works

LangChain calls OpenAI internally. TracePilot's `wrapOpenAI` intercepts that call, captures inputs, outputs, tokens, and latency as a span, then returns the original result untouched. LangChain never knows TracePilot is there.

<Note>
  TracePilot wraps OpenAI calls — it does not wrap LangChain itself. Your chains, agents, and tools continue to work exactly as before.
</Note>

## Setup

<Steps>
  <Step title="Install dependencies">
    ```bash theme={null}
    npm install tracepilot-sdk langchain @langchain/openai openai
    ```
  </Step>

  <Step title="Create a traced OpenAI client">
    Extend the LangChain `ChatOpenAI` class to intercept completions calls before they reach OpenAI. Pass `tp.wrapOpenAI` as the call executor inside the `_generate` override.

    ```typescript theme={null}
    import { TracePilot } from 'tracepilot-sdk';
    import { ChatOpenAI } from '@langchain/openai';
    import OpenAI from 'openai';
    import { BaseMessage, HumanMessage } from '@langchain/core/messages';

    const tp = new TracePilot(process.env.TRACEPILOT_API_KEY!);
    const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

    class TracedChatOpenAI extends ChatOpenAI {
      private parentSpanId?: string;
      private stepOrder?: number;

      constructor(opts: ConstructorParameters<typeof ChatOpenAI>[0] & {
        parentSpanId?: string;
        stepOrder?: number;
      }) {
        super(opts);
        this.parentSpanId = opts.parentSpanId;
        this.stepOrder = opts.stepOrder;
      }

      async _generate(messages: BaseMessage[], options: this['ParsedCallOptions']) {
        const openAIMessages = messages.map((m) => ({
          role: m._getType() === 'human' ? 'user' : 'assistant',
          content: m.content as string,
        }));

        const { result, spanId } = await tp.wrapOpenAI(
          () =>
            openai.chat.completions.create({
              model: this.modelName,
              messages: openAIMessages,
            }),
          openAIMessages,
          this.parentSpanId,
          this.stepOrder,
        );

        // Store the spanId for downstream steps to use as parentSpanId
        (this as any)._lastSpanId = spanId;

        // Re-use the parent class to convert the OpenAI response to LangChain format
        return super._generate(messages, options);
      }
    }
    ```
  </Step>

  <Step title="Build your chain with the traced model">
    Use `TracedChatOpenAI` anywhere you'd use `ChatOpenAI`. Pass `stepOrder` to match LangChain's chain step numbering so spans appear in the correct order in the dashboard.

    ```typescript theme={null}
    import { StringOutputParser } from '@langchain/core/output_parsers';
    import { ChatPromptTemplate } from '@langchain/core/prompts';

    async function runSupportChain(userQuestion: string) {
      await tp.startTrace('support-chain');

      const prompt = ChatPromptTemplate.fromMessages([
        ['system', 'You are a helpful customer support agent.'],
        ['human', '{question}'],
      ]);

      const model = new TracedChatOpenAI({
        modelName: 'gpt-4o-mini',
        stepOrder: 1,
      });

      const chain = prompt.pipe(model).pipe(new StringOutputParser());

      const answer = await chain.invoke({ question: userQuestion });

      console.log(answer);
      return answer;
    }

    runSupportChain('How do I export my data?');
    ```
  </Step>
</Steps>

## Multi-step chains

When your chain has multiple LLM steps, pass `stepOrder` to each `TracedChatOpenAI` instance and use `parentSpanId` to link them into a tree.

```typescript theme={null}
async function runResearchChain(topic: string) {
  await tp.startTrace('research-chain');

  const messages = [{ role: 'user' as const, content: `Summarize: ${topic}` }];

  // Step 1 — Generate a search plan
  const { result: plan, spanId: planSpanId } = await tp.wrapOpenAI(
    () =>
      openai.chat.completions.create({
        model: 'gpt-4o',
        messages,
      }),
    messages,
    undefined, // no parent — this is the root span
    1,
  );

  const followUp = [
    ...messages,
    { role: 'assistant' as const, content: plan.choices[0].message.content! },
    { role: 'user' as const, content: 'Now write a detailed report based on that plan.' },
  ];

  // Step 2 — Write the report (child of Step 1)
  const { result: report } = await tp.wrapOpenAI(
    () =>
      openai.chat.completions.create({
        model: 'gpt-4o',
        messages: followUp,
      }),
    followUp,
    planSpanId, // link to the planning span
    2,
  );

  return report.choices[0].message.content;
}
```

<Tip>
  Set `stepOrder` to match LangChain's chain step index. This keeps spans in the correct sequence in the dashboard, making it easy to spot which step produced a bad output.
</Tip>

## What you'll see in the dashboard

Each `wrapOpenAI` call creates one span. For a two-step chain, you get a parent span for Step 1 and a child span for Step 2 nested beneath it. Click any span to inspect the exact messages sent to OpenAI, the response, token usage, latency, and cost.
