Skip to main content

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.

TracePilot’s rule is simple: if your code makes an async call, you can wrap it. Use tp.wrapOpenAI for any call to OpenAI’s chat completions API, and tp.wrapToolCall for any other async operation you want to trace — web searches, database queries, external API calls, file reads, or anything else. This works with AutoGen, custom agent loops, event-driven systems, or any orchestration pattern you’ve built.

Core pattern

Every wrapped call returns { result, spanId }. Pass spanId as parentSpanId to subsequent calls to build a parent-child span tree in the dashboard. The result is the original return value, unchanged.
import { TracePilot } from 'tracepilot-sdk';
import OpenAI from 'openai';

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

async function runAgent(userInput: string) {
  await tp.startTrace('my-agent');

  const messages = [{ role: 'user' as const, content: userInput }];

  // Wrap any async call — result is unchanged, spanId links spans together
  const { result, spanId } = await tp.wrapOpenAI(
    () => openai.chat.completions.create({ model: 'gpt-4o-mini', messages }),
    messages,
    undefined, // parentSpanId — undefined means this is the root span
    1,
  );

  console.log(result.choices[0].message.content);
}

Patterns

Each step passes its spanId as parentSpanId for the next step, creating a chain of linked spans.
async function sequentialAgent(query: string) {
  await tp.startTrace('sequential-agent');

  const step1Messages = [{ role: 'user' as const, content: query }];

  // Step 1 — Plan
  const { result: plan, spanId: planSpanId } = await tp.wrapOpenAI(
    () =>
      openai.chat.completions.create({
        model: 'gpt-4o',
        messages: step1Messages,
      }),
    step1Messages,
    undefined,
    1,
  );

  // Step 2 — Fetch data based on the plan
  const { result: data, spanId: dataSpanId } = await tp.wrapToolCall(
    'fetch-data',
    () => fetchFromDatabase(plan.choices[0].message.content!),
    planSpanId, // child of the planning step
    2,
  );

  // Step 3 — Synthesize using fetched data
  const step3Messages = [
    ...step1Messages,
    { role: 'assistant' as const, content: plan.choices[0].message.content! },
    { role: 'user' as const, content: `Use this data: ${JSON.stringify(data)}` },
  ];

  const { result: answer } = await tp.wrapOpenAI(
    () =>
      openai.chat.completions.create({
        model: 'gpt-4o',
        messages: step3Messages,
      }),
    step3Messages,
    dataSpanId, // child of the data-fetch step
    3,
  );

  return answer.choices[0].message.content;
}

async function fetchFromDatabase(query: string): Promise<Record<string, unknown>> {
  // Replace with your real database client
  return { records: [], query };
}

AutoGen compatibility

AutoGen uses async function calls internally. Wrap at the same level — intercept the OpenAI call inside your AutoGen agent’s reply function, or wrap the tool functions your agent registers.
import { ConversableAgent } from 'autogen';

// Wrap the underlying OpenAI call inside your AutoGen reply function
const tracedReplyFunction = async (
  messages: { role: string; content: string }[],
  parentSpanId?: string,
  stepOrder?: number,
) => {
  const { result, spanId } = await tp.wrapOpenAI(
    () =>
      openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages,
      }),
    messages,
    parentSpanId,
    stepOrder,
  );

  return { content: result.choices[0].message.content, spanId };
};

// Register it on your AutoGen agent
const agent = new ConversableAgent({
  name: 'traced-agent',
  llmConfig: { model: 'gpt-4o-mini' },
});

// Use tracedReplyFunction wherever your agent generates replies
AutoGen support is available for any async call pattern. If AutoGen exposes a custom reply function hook in your version, use tracedReplyFunction there. Refer to your AutoGen version’s docs for the exact hook name.

Building a custom orchestration loop

If you’re running your own agent loop, wrap every call inside the loop body and thread spanId values forward.
async function agentLoop(goal: string, maxSteps = 5) {
  await tp.startTrace('agent-loop');

  let messages = [{ role: 'user' as const, content: goal }];
  let parentSpanId: string | undefined;

  for (let step = 1; step <= maxSteps; step++) {
    // LLM decides what to do next
    const { result: decision, spanId } = await tp.wrapOpenAI(
      () =>
        openai.chat.completions.create({
          model: 'gpt-4o',
          messages,
        }),
      messages,
      parentSpanId,
      step,
    );

    const content = decision.choices[0].message.content!;

    // Check if the agent has finished
    if (content.includes('[DONE]')) {
      break;
    }

    // Execute the tool the agent requested
    const toolName = parseToolName(content); // your parsing logic
    const toolArgs = parseToolArgs(content);

    const { result: toolResult, spanId: toolSpanId } = await tp.wrapToolCall(
      toolName,
      () => executeTool(toolName, toolArgs),
      spanId, // child of this step's LLM span
      step,
    );

    // Feed the tool result back into the conversation
    messages = [
      ...messages,
      { role: 'assistant' as const, content },
      { role: 'user' as const, content: `Tool result: ${JSON.stringify(toolResult)}` },
    ];

    parentSpanId = toolSpanId; // next iteration links to this tool call
  }
}

function parseToolName(content: string): string {
  // Replace with your parsing logic
  return content.split(':')[0].trim();
}

function parseToolArgs(content: string): Record<string, unknown> {
  return {};
}

async function executeTool(
  name: string,
  args: Record<string, unknown>,
): Promise<unknown> {
  // Dispatch to your tool implementations
  return { tool: name, args };
}
The key rule: wrap any async call you want to trace, and pass parentSpanId to link related spans. Even if you only wrap some calls, those spans still appear in the dashboard — you don’t need to wrap everything at once.