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

# OpenAI Agents Traces

> Trace OpenAI Agents runs, tool calls, handoffs, and nested OpenAI model calls.

Pair OpenAI Agents instrumentation with OpenAI instrumentation so nested model
calls are captured. Once `setup()` runs (pass both modules in TypeScript;
Python auto-detects installed SDKs), the agent run, its tool calls, handoffs,
and nested model calls are all captured automatically. Wrap the run in
`agentSpan()` / `agent_span()` when you want stable identity and session
grouping.

<Info>
  Three layers, in increasing order of effort:

  * **`setup()`** traces the whole run automatically (pass the SDK modules in TypeScript; Python auto-detects). Enough on its own.
  * **`agentSpan()` / `agent_span()`** adds a stable `agentId`, `agentName`, `role`, and `sessionId` so the Agents dashboard groups runs by conversation, plus your high-level input and output.
  * **`manualSpan()` / `manual_span()`** captures steps the SDK never sees: your own retrieval, routing, or sub-steps inside a tool.

  For the full breakdown of when to reach for each, see [How Tracing Fits Together](/integrations/traces/overview#how-tracing-fits-together).
</Info>

## Install

<CodeGroup>
  <Metadata text="integrations/traces/openai-agents-install-typescript" />

  ```bash TypeScript theme={"system"}
  bun add @inference/tracing openai @openai/agents zod
  ```

  <Metadata text="integrations/traces/openai-agents-install-python" />

  ```bash Python theme={"system"}
  pip install 'inference-catalyst-tracing[openai-agents]'
  ```
</CodeGroup>

## Configure Export

Set the Catalyst endpoint and token before your app starts. `setup()` reads
these in every example below. Generate a token at
[API Keys](https://inference.net/dashboard/api-keys).

<Metadata text="integrations/traces/openai-agents-env" />

```bash theme={"system"}
export CATALYST_OTLP_ENDPOINT="https://telemetry.inference.net"
export CATALYST_OTLP_TOKEN="<your-token>"
export CATALYST_SERVICE_NAME="support-agent"
```

## Minimal Setup

Pass the SDK modules to `setup()` in TypeScript, or let it auto-detect in
Python, then run your agent. The run, any tool calls, and the nested model calls
are all captured with no further code. This is everything you need for a single
one-shot agent.

<CodeGroup>
  <Metadata text="integrations/traces/openai-agents-minimal-typescript" />

  ```typescript TypeScript theme={"system"}
  import { setup } from "@inference/tracing";
  import * as agents from "@openai/agents";
  import { Agent, run } from "@openai/agents";
  import OpenAI from "openai";

  const tracing = await setup({
    modules: { openai: OpenAI, openaiAgents: agents },
  });

  const supportAgent = new Agent({
    name: "SupportAgent",
    instructions: "Help customers with order questions.",
    model: "gpt-4o-mini",
  });

  // No agentSpan needed: the run and its nested model calls are captured
  // automatically.
  const result = await run(supportAgent, "Where is order ABC-123?");
  console.log(result.finalOutput);

  await tracing.shutdown();
  ```

  <Metadata text="integrations/traces/openai-agents-minimal-python" />

  ```python Python theme={"system"}
  from inference_catalyst_tracing import setup
  from agents import Agent, Runner

  tracing = setup()

  support_agent = Agent(
      name="SupportAgent",
      instructions="Help customers with order questions.",
      model="gpt-4o-mini",
  )

  # No agent_span needed: the run and its nested model calls are captured
  # automatically.
  result = Runner.run_sync(support_agent, "Where is order ABC-123?")
  print(result.final_output)

  tracing.shutdown()
  ```
</CodeGroup>

## Group Under An Agent

Wrap the run in `agentSpan()` to give it stable identity and session grouping.
This is the recommended shape for anything beyond a one-shot script. Pass
`userId` to record `user.id` so you can filter traces by user on the dashboard,
just like `sessionId`. For arbitrary keys, attach any custom attributes you want
to filter traces by inside the callback (`organization.id`, `chat.id`,
`order.id`). See
[Adding Custom Attributes](/integrations/traces/manual-spans#adding-custom-attributes).

<CodeGroup>
  <Metadata text="integrations/traces/openai-agents-single-typescript" />

  ```typescript TypeScript theme={"system"}
  import { agentSpan, setup } from "@inference/tracing";
  import * as agents from "@openai/agents";
  import { Agent, run, tool } from "@openai/agents";
  import OpenAI from "openai";
  import { z } from "zod";

  const tracing = await setup({
    modules: { openai: OpenAI, openaiAgents: agents },
  });

  const lookupOrder = tool({
    name: "lookup_order",
    description: "Look up an order by ID.",
    parameters: z.object({ orderId: z.string() }),
    execute: async ({ orderId }) =>
      JSON.stringify({ orderId, status: "shipped", total: 42.5 }),
  });

  const supportAgent = new Agent({
    name: "SupportAgent",
    instructions: "Use tools to help customers with order questions.",
    tools: [lookupOrder],
    model: "gpt-4o-mini",
  });

  const userMessage = "Where is order ABC-123?";
  await agentSpan(
    {
      agentId: "support-agent",
      agentName: "Support Agent",
      spanName: "support-agent.run",
      sessionId: "conversation-order-abc-123",
      userId: "user_8675309",
      role: "support",
      system: "openai",
    },
    async (span) => {
      span.setInput(userMessage);
      // For arbitrary keys, attach whatever your app keys on. Any attribute is
      // filterable from the dashboard and CLI, e.g.
      // `inf span list --metadata "organization.id=org_42"`.
      span.setAttribute("organization.id", "org_42");
      const result = await run(supportAgent, userMessage, { maxTurns: 4 });
      span.setOutput(String(result.finalOutput ?? ""));
    },
  );

  await tracing.shutdown();
  ```

  <Metadata text="integrations/traces/openai-agents-single-python" />

  ```python Python theme={"system"}
  import json

  from inference_catalyst_tracing import agent_span, setup
  from agents import Agent, Runner, function_tool

  tracing = setup()


  @function_tool
  def lookup_order(order_id: str) -> str:
      """Look up an order by ID."""
      return json.dumps({"order_id": order_id, "status": "shipped", "total": 42.5})


  support_agent = Agent(
      name="SupportAgent",
      instructions="Use tools to help customers with order questions.",
      tools=[lookup_order],
      model="gpt-4o-mini",
  )

  user_message = "Where is order ABC-123?"
  with agent_span(
      tracing.tracer,
      agent_id="support-agent",
      agent_name="Support Agent",
      span_name="support-agent.run",
      session_id="conversation-order-abc-123",
      user_id="user_8675309",
      agent_role="support",
      system="openai",
  ) as span:
      span.set_input(user_message)
      # For arbitrary keys, attach whatever your app keys on. Any attribute is
      # filterable from the dashboard and CLI, e.g.
      # `inf span list --metadata "organization.id=org_42"`.
      span.set_attribute("organization.id", "org_42")
      result = Runner.run_sync(support_agent, user_message, max_turns=4)
      span.set_output(str(result.final_output))

  tracing.shutdown()
  ```
</CodeGroup>

## Multi-Agent Handoff

Wrap the triage request once. The trace tree groups the triage agent,
specialist agent, nested model calls, and tools under the customer request.

<CodeGroup>
  <Metadata text="integrations/traces/openai-agents-handoff-typescript" />

  ```typescript TypeScript theme={"system"}
  import { Agent, handoff, run, tool } from "@openai/agents";

  const issueRefund = tool({
    name: "issue_refund",
    description: "Issue a refund for an order.",
    parameters: z.object({ orderId: z.string(), amount: z.number() }),
    execute: async ({ orderId, amount }) =>
      JSON.stringify({ ok: true, orderId, refundId: "RFD-2201", amount }),
  });

  const refundsAgent = new Agent({
    name: "RefundsAgent",
    instructions: "Handle refund requests and use issue_refund.",
    tools: [issueRefund],
    model: "gpt-4o-mini",
  });

  const billingAgent = new Agent({
    name: "BillingAgent",
    instructions: "Answer billing questions. Do not issue refunds.",
    model: "gpt-4o-mini",
  });

  const triageAgent = new Agent({
    name: "TriageAgent",
    instructions: "Route refund requests to RefundsAgent.",
    handoffs: [handoff(refundsAgent), handoff(billingAgent)],
    model: "gpt-4o-mini",
  });

  await agentSpan(
    {
      agentId: "triage-agent",
      agentName: "Triage Agent",
      spanName: "triage-agent.run",
      sessionId: "conversation-refund-abc-123",
      role: "triage",
      system: "openai",
    },
    async (span) => {
      const input = "I need a refund for order ABC-123, total $42.50.";
      span.setInput(input);
      const result = await run(triageAgent, input, { maxTurns: 8 });
      span.setOutput(String(result.finalOutput ?? ""));
    },
  );
  ```

  <Metadata text="integrations/traces/openai-agents-handoff-python" />

  ```python Python theme={"system"}
  import json

  from inference_catalyst_tracing import agent_span
  from agents import Agent, Runner, function_tool, handoff


  @function_tool
  def issue_refund(order_id: str, amount: float) -> str:
      """Issue a refund for an order."""
      return json.dumps(
          {"ok": True, "order_id": order_id, "refund_id": "RFD-2201", "amount": amount}
      )


  refunds_agent = Agent(
      name="RefundsAgent",
      instructions="Handle refund requests and use issue_refund.",
      tools=[issue_refund],
      model="gpt-4o-mini",
  )

  billing_agent = Agent(
      name="BillingAgent",
      instructions="Answer billing questions. Do not issue refunds.",
      model="gpt-4o-mini",
  )

  triage_agent = Agent(
      name="TriageAgent",
      instructions="Route refund requests to RefundsAgent.",
      handoffs=[handoff(refunds_agent), handoff(billing_agent)],
      model="gpt-4o-mini",
  )

  with agent_span(
      tracing.tracer,
      agent_id="triage-agent",
      agent_name="Triage Agent",
      span_name="triage-agent.run",
      session_id="conversation-refund-abc-123",
      agent_role="triage",
      system="openai",
  ) as span:
      user_input = "I need a refund for order ABC-123, total $42.50."
      span.set_input(user_input)
      result = Runner.run_sync(triage_agent, user_input, max_turns=8)
      span.set_output(str(result.final_output))
  ```
</CodeGroup>

Use one stable ID for the outer customer-facing run. If you also emit separate
AGENT spans for handoff specialists, give each specialist its own stable
`agent.id`, such as `refunds-agent` or `billing-agent`.

## When To Add Manual Spans

The Agents SDK only captures the work it runs for you: the model calls, and the
tools you register with `tool()`. Anything you do yourself inside the
`agentSpan` callback is invisible to it. You do not wrap the SDK's own tool
calls in `manualSpan()`, those are already captured, and doing so would just
produce a duplicate span. You reach for `manualSpan()` for the steps the SDK
never sees.

Two common cases:

1. **A step around the run.** You fetch context from a vector store before the
   run, or validate and reshape the output after it. Neither is an SDK call, so
   author a `RETRIEVER` or `CHAIN` span yourself.
2. **Sub-steps inside a tool.** The SDK emits one TOOL span per tool
   invocation. If that tool's `execute` does something you want broken out (a
   vector search, a downstream API call, a transform), wrap it in a child span.
   It nests under the SDK's TOOL span automatically.

<CodeGroup>
  <Metadata text="integrations/traces/openai-agents-manual-typescript" />

  ```typescript TypeScript theme={"system"}
  import { manualSpan, SpanKindValues } from "@inference/tracing";

  // Case 2: a sub-step inside a registered tool. The SDK already opened a TOOL
  // span for this call; the RETRIEVER span below nests inside it.
  const lookupOrder = tool({
    name: "lookup_order",
    description: "Look up an order by ID.",
    parameters: z.object({ orderId: z.string() }),
    execute: async ({ orderId }) => {
      const docs = await manualSpan(
        {
          spanName: "order_docs.search",
          spanKind: SpanKindValues.RETRIEVER,
          input: { orderId, k: 4 },
        },
        async (span) => {
          const results = await vectorStore.search(orderId, { k: 4 });
          span.setOutput(results.map((d) => ({ id: d.id, score: d.score })));
          return results;
        },
      );

      return JSON.stringify({ orderId, status: "shipped", context: docs.length });
    },
  });

  await agentSpan(
    {
      agentId: "support-agent",
      agentName: "Support Agent",
      spanName: "support-agent.run",
      sessionId: "conversation-order-abc-123",
      role: "support",
      system: "openai",
    },
    async (span) => {
      const userMessage = "Where is order ABC-123?";
      span.setInput(userMessage);

      // Case 1: a pre-run step the SDK never sees. You classify the request
      // yourself before handing it to the agent.
      const route = await manualSpan(
        {
          spanName: "router.classify",
          spanKind: SpanKindValues.CHAIN,
          input: { userMessage },
        },
        async (chain) => {
          const decision = classifyRequest(userMessage);
          chain.setOutput(decision);
          return decision;
        },
      );

      const result = await run(supportAgent, userMessage, { maxTurns: 4 });
      span.setOutput(String(result.finalOutput ?? ""));
    },
  );
  ```

  <Metadata text="integrations/traces/openai-agents-manual-python" />

  ```python Python theme={"system"}
  from inference_catalyst_tracing import agent_span, manual_span, SpanKindValues
  from agents import Runner, function_tool


  # Case 2: a sub-step inside a registered tool. The SDK already opened a TOOL
  # span for this call; the RETRIEVER span below nests inside it.
  @function_tool
  def lookup_order(order_id: str) -> str:
      """Look up an order by ID."""
      with manual_span(
          tracing.tracer,
          name="order_docs.search",
          span_kind=SpanKindValues.RETRIEVER,
          input={"order_id": order_id, "k": 4},
      ) as span:
          results = vector_store.search(order_id, k=4)
          span.set_output([{"id": d.id, "score": d.score} for d in results])

      return json.dumps(
          {"order_id": order_id, "status": "shipped", "context": len(results)}
      )


  with agent_span(
      tracing.tracer,
      agent_id="support-agent",
      agent_name="Support Agent",
      span_name="support-agent.run",
      session_id="conversation-order-abc-123",
      agent_role="support",
      system="openai",
  ) as span:
      user_message = "Where is order ABC-123?"
      span.set_input(user_message)

      # Case 1: a pre-run step the SDK never sees. You classify the request
      # yourself before handing it to the agent.
      with manual_span(
          tracing.tracer,
          name="router.classify",
          span_kind=SpanKindValues.CHAIN,
          input={"user_message": user_message},
      ) as chain:
          decision = classify_request(user_message)
          chain.set_output(decision)

      result = Runner.run_sync(support_agent, user_message, max_turns=4)
      span.set_output(str(result.final_output))
  ```
</CodeGroup>

The rule of thumb: if the Agents SDK runs the code (a model call, a registered
tool), it is captured for you. If you run the code (retrieval, routing,
validation, or a sub-step inside a tool), wrap it in `manualSpan`. See
[Manual Spans](/integrations/traces/manual-spans) for the full set of span
kinds and the handle API.

## Flushing

Every example above ends with `await tracing.shutdown()`, which flushes batched
spans before the process exits. Long-lived servers and serverless or edge
runtimes need a different flush strategy, since the process does not exit after
each run. See
[Flushing and process lifecycle](/integrations/traces/quickstart#flushing-and-process-lifecycle)
for all three shapes.

## Next Steps

<CardGroup cols={2}>
  <Card title="Manual spans" icon="pen-nib" href="/integrations/traces/manual-spans">
    Author TOOL, CHAIN, and RETRIEVER spans for work the SDK does not capture.
  </Card>

  <Card title="Agent identity" icon="fingerprint" href="/integrations/traces/agent-identity">
    Pick stable `agent.id` and `session.id` values for the Agents dashboard.
  </Card>

  <Card title="Production agent example" icon="kitchen-set" href="/integrations/traces/production-agent-example">
    A production-shaped agent with custom tool spans, end to end.
  </Card>

  <Card title="Attributes reference" icon="tags" href="/integrations/traces/attributes">
    Every `Attr.*` constant and `SpanKindValues` value the SDK emits.
  </Card>
</CardGroup>
