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

# OpenAI Agents JS

> Trace OpenAI Agents JS runs with OpenInference and send spans to Arize AX for LLM observability.

[OpenAI Agents JS](https://github.com/openai/openai-agents-js) is OpenAI's TypeScript framework for building agents — instructions, tools, handoffs, guardrails, and the `run` function that drives them. Arize AX captures every Agents JS run — agent invocations, tool calls, handoffs, guardrails, and the underlying LLM calls — via the [`@arizeai/openinference-instrumentation-openai-agents`](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-openai-agents) package, which registers as a first-class `TracingProcessor` against the SDK.

## Prerequisites

* Node.js 22+
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* An `OPENAI_API_KEY` from the [OpenAI Platform](https://platform.openai.com/api-keys)

## Launch Arize AX

1. Sign in to your [Arize AX account](https://app.arize.com/).
2. From **Space Settings**, copy your **Space ID** and **API Key**. You will set them as `ARIZE_SPACE_ID` and `ARIZE_API_KEY` below.

## Install

```bash theme={null}
npm install @openai/agents zod \
  @arizeai/openinference-instrumentation-openai-agents \
  @arizeai/openinference-semantic-conventions \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/resources \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/semantic-conventions
```

## Configure credentials

```bash theme={null}
export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="openai-agents-js-tracing-example"
export OPENAI_API_KEY="<your-openai-api-key>"
```

## Setup tracing

```typescript theme={null}
// instrumentation.ts
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import {
  SEMRESATTRS_PROJECT_NAME,
} from "@arizeai/openinference-semantic-conventions";
import {
  OpenAIAgentsInstrumentation,
} from "@arizeai/openinference-instrumentation-openai-agents";
import * as agents from "@openai/agents";

const projectName =
  process.env.ARIZE_PROJECT_NAME ?? "openai-agents-js-tracing-example";

export const provider = new NodeTracerProvider({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: projectName,
    [SEMRESATTRS_PROJECT_NAME]: projectName,
  }),
  spanProcessors: [
    new SimpleSpanProcessor(
      new OTLPTraceExporter({
        url: "https://otlp.arize.com/v1/traces",
        headers: {
          "arize-space-id": process.env.ARIZE_SPACE_ID ?? "",
          "arize-api-key": process.env.ARIZE_API_KEY ?? "",
        },
      }),
    ),
  ],
});

provider.register();

const instrumentation = new OpenAIAgentsInstrumentation({
  tracerProvider: provider,
});
instrumentation.manuallyInstrument(agents);

console.log("Arize AX tracing initialized for OpenAI Agents JS.");
```

<Note>
  The instrumentor implements the Agents SDK's first-class `TracingProcessor` interface rather than monkey-patching imports, so `manuallyInstrument(agents)` must receive the same `@openai/agents` namespace your application code imports. Pass `exclusiveProcessor: false` to the constructor to run alongside the SDK's default OpenAI tracing exporter instead of replacing it.
</Note>

## Run OpenAI Agents JS

```typescript theme={null}
// example.ts

// Importing instrumentation first ensures tracing is set up before the
// Agents SDK is used.
import { provider } from "./instrumentation";

import { Agent, run } from "@openai/agents";

// The agent reads OPENAI_API_KEY from the environment.
const agent = new Agent({
  name: "Assistant",
  instructions: "You are a concise factual assistant.",
  model: "gpt-5.5",
});

const result = await run(
  agent,
  "Why is the ocean salty? Answer in two sentences.",
);

console.log(result.finalOutput);

// Flush any pending spans before the process exits.
await provider.forceFlush();
```

### Expected output

```text wrap theme={null}
Arize AX tracing initialized for OpenAI Agents JS.
The ocean is salty because rivers continuously dissolve mineral salts from rocks and soil and carry them to the sea, where they accumulate over millions of years. Water leaves the ocean through evaporation but the salts remain, steadily concentrating until reaching today's roughly 3.5% salinity.
```

## Verify in Arize AX

1. Open your Arize AX space and select project **`openai-agents-js-tracing-example`**.
2. You should see a new trace within \~30 seconds with this shape: an `Agent workflow` root span (AGENT) wraps an `Assistant` agent span (AGENT) and a `response` LLM child span (model `gpt-5.5`) with the prompt, response, and token usage attached. Tool, handoff, and guardrail spans appear when the agent invokes them.
3. If no traces appear, see [Troubleshooting](#troubleshooting).

### Check from the skill, CLI, or SDK

Confirm spans are actually reaching your Arize AX project. Use whichever fits your workflow — the skill and CLI work for any framework; the SDK check is shown for each language.

<Tabs>
  <Tab title="Arize skill (agent)">
    Install the [Arize Skills](https://github.com/Arize-ai/arize-skills) plugin and let your coding agent check for you:

    ```bash theme={null}
    npx skills add Arize-ai/arize-skills
    ```

    Then prompt your agent:

    > Use the `arize-trace` skill to export and analyze recent traces from my project. Confirm spans are arriving, and summarize any errors or latency issues.
  </Tab>

  <Tab title="AX CLI">
    Export recent spans for your project — any rows mean traces are landing:

    ```bash theme={null}
    ax spans export "$ARIZE_PROJECT_NAME" --space "$ARIZE_SPACE_ID" \
      --limit 5 --stdout | jq 'length'
    ```

    A non-zero count confirms spans reached Arize AX. Run `ax auth login` first if you have not authenticated. See the [`ax spans` reference](/docs/api-clients/cli/spans).
  </Tab>

  <Tab title="SDK">
    Query the project's spans and check that at least one came back.

    <CodeGroup>
      ```python Python theme={null}
      import os
      from arize import ArizeClient

      client = ArizeClient(api_key=os.environ["ARIZE_API_KEY"])
      resp = client.spans.list(
          project=os.environ["ARIZE_PROJECT_NAME"],
          space=os.environ["ARIZE_SPACE_ID"],
          limit=5,
      )
      count = len(resp.spans)
      print(
          f"{count} span(s) found" if count else "No spans yet — recheck setup"
      )
      ```

      ```typescript TypeScript theme={null}
      // Reads ARIZE_API_KEY from the environment.
      import { listSpans } from "@arizeai/ax-client";

      const { data: spans } = await listSpans({
        project: process.env.ARIZE_PROJECT_NAME!,
        space: process.env.ARIZE_SPACE_ID!,
        limit: 5,
      });
      const count = spans.length;
      console.log(
        count ? `${count} span(s) found` : "No spans yet — recheck setup",
      );
      ```

      ```go Go theme={null}
      client, err := arize.NewClient(
          arize.Config{APIKey: os.Getenv("ARIZE_API_KEY")},
      )
      if err != nil {
          log.Fatal(err)
      }
      resp, err := client.Spans.List(ctx, spans.ListRequest{
          Project: os.Getenv("ARIZE_PROJECT_NAME"),
          Space:   os.Getenv("ARIZE_SPACE_ID"),
          Limit:   5,
      })
      if err != nil {
          log.Fatal(err)
      }
      fmt.Printf("%d span(s) found\n", len(resp.Spans))
      ```
    </CodeGroup>

    SDK span references: [Python](/docs/api-clients/python/version-8/client-resources/spans) · [TypeScript](/docs/api-clients/typescript/version-1/client-resources/spans) · [Go](/docs/api-clients/go/version-2/client-resources/spans).
  </Tab>
</Tabs>

## Span filter

The basic setup above exports every span the tracer provider receives. That's fine for a standalone Node script — nothing else is emitting through the provider. In Next.js / Vercel / NestJS / any runtime that ships its own OpenTelemetry auto-instrumentation, those instrumentations emit `POST` / `GET` spans for every fetch, every page render, every API route, and the Agents SDK spans nest under those HTTP roots inside your AX project. A single chat turn can balloon into 30+ unrelated infra spans.

OpenInference-tagged spans all carry an `openinference.span.kind` attribute that the `@arizeai/openinference-instrumentation-openai-agents` processor sets — checking for it is enough to drop non-OpenInference spans. Swap `SimpleSpanProcessor` in `instrumentation.ts` for a filtering subclass:

```typescript theme={null}
import {
  ReadableSpan,
  SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-base";
import { SemanticConventions } from "@arizeai/openinference-semantic-conventions";

function isOpenInferenceSpan(span: ReadableSpan): boolean {
  return (
    typeof span.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] ===
    "string"
  );
}

class OpenInferenceFilteringSpanProcessor extends SimpleSpanProcessor {
  onEnd(span: ReadableSpan): void {
    if (!isOpenInferenceSpan(span)) return;
    super.onEnd(span);
  }
}

// then in spanProcessors:
new OpenInferenceFilteringSpanProcessor(
  new OTLPTraceExporter({ /* ... */ }),
);
```

This is the same predicate `@arizeai/openinference-vercel` exports as `isOpenInferenceSpan` — inlined here so a non-Vercel app doesn't have to depend on a Vercel-specific package.

The trade-off: filtering removes the HTTP root spans, which orphans the surviving Agents SDK spans on the **Traces** tab (no parent to anchor them) — they remain visible on the **Spans** tab. If you also need a clean trace tree on the Traces tab, swap the filter for a span processor that promotes the SDK's top-level `Agent workflow` span to root by clearing its parent ID:

```typescript theme={null}
// root-aware-processor.ts
import { Context } from "@opentelemetry/api";
import {
  BatchSpanProcessor,
  ReadableSpan,
  Span,
  SpanExporter,
} from "@opentelemetry/sdk-trace-base";
import { getSession } from "@arizeai/openinference-core";
import {
  SemanticConventions,
  SESSION_ID,
} from "@arizeai/openinference-semantic-conventions";
import { LRUCache } from "lru-cache";

// Top-level OpenAI Agents SDK span names. `Agent workflow` is the
// implicit root the SDK creates for every `run()` invocation; individual
// agent runs surface as spans named after `Agent.name` (e.g. `Assistant`,
// `Researcher`). Match on the first whitespace-delimited token so suffixed
// run names (e.g. `Agent workflow some-trace-name`) still hit.
const ROOT_OI_SPAN_PREFIXES = [
  "Agent workflow",
];

function isRootOISpanByName(spanName: string): boolean {
  const head = spanName.split(" ")[0];
  return ROOT_OI_SPAN_PREFIXES.some(
    (prefix) =>
      spanName === prefix ||
      spanName.startsWith(prefix + " ") ||
      head === prefix.split(" ")[0],
  );
}

function isOpenInferenceSpan(span: ReadableSpan): boolean {
  return (
    typeof span.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] ===
    "string"
  );
}

interface RootAwareConfig {
  exporter: SpanExporter;
  /** LRU size for tracking which traces have a promoted root. */
  cacheSize?: number;
}

/**
 * Filters non-OpenInference spans (HTTP, fetch, etc.) and promotes the
 * first `Agent workflow` span in each trace to root by clearing its
 * parent IDs. Also propagates session ids from context onto every
 * emitted span.
 */
export class RootAwareOpenInferenceProcessor extends BatchSpanProcessor {
  private traceIds: LRUCache<string, boolean>;

  constructor(config: RootAwareConfig) {
    super(config.exporter);
    this.traceIds = new LRUCache({ max: config.cacheSize ?? 1000 });
  }

  onStart(span: Span, parentContext: Context): void {
    const session = getSession(parentContext);
    if (session?.sessionId) {
      span.setAttribute(SESSION_ID, session.sessionId);
    }

    const traceId = span.spanContext().traceId;
    if (
      isRootOISpanByName(span.name) &&
      !this.traceIds.has(traceId)
    ) {
      // parentSpanId is readonly on the public Span type; cast to clear.
      (span as unknown as { parentSpanId?: string }).parentSpanId =
        undefined;
      (span as unknown as { parentSpanContext?: unknown })
        .parentSpanContext = undefined;
      this.traceIds.set(traceId, true);
    }

    super.onStart(span, parentContext);
  }

  onEnd(span: ReadableSpan): void {
    if (!isOpenInferenceSpan(span)) return;
    super.onEnd(span);
  }

  shutdown(): Promise<void> {
    this.traceIds.clear();
    return super.shutdown();
  }
}
```

Wire it in by replacing the `SimpleSpanProcessor` (or `OpenInferenceFilteringSpanProcessor`) in `instrumentation.ts`:

```typescript theme={null}
import { RootAwareOpenInferenceProcessor } from "./root-aware-processor";

spanProcessors: [
  new RootAwareOpenInferenceProcessor({
    exporter: new OTLPTraceExporter({
      url: "https://otlp.arize.com/v1/traces",
      headers: {
        "arize-space-id": process.env.ARIZE_SPACE_ID ?? "",
        "arize-api-key": process.env.ARIZE_API_KEY ?? "",
      },
    }),
  }),
],
```

The recipe needs two additional dependencies: `npm install @arizeai/openinference-core lru-cache`.

## Troubleshooting

* **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `example.ts`. Enable OpenTelemetry debug logs with `export OTEL_LOG_LEVEL=debug` and re-run.
* **Agents spans missing.** `instrumentation.manuallyInstrument(agents)` must run before `Agent` is constructed or `run` is called. Make sure `import { provider } from "./instrumentation"` is the first import in your entry point, and that the same `@openai/agents` namespace passed to `manuallyInstrument(...)` is the one your application code imports.
* **`401` from OpenAI.** Verify `OPENAI_API_KEY` is set and has access to the default model. Swap `model: "gpt-5.5"` for a model your key can call.
* **Spans still going to the default OpenAI tracing exporter.** The instrumentor registers exclusively by default. If you need to keep the SDK's built-in exporter alongside Arize AX, pass `new OpenAIAgentsInstrumentation({ tracerProvider: provider, exclusiveProcessor: false })`.
* **Process exits before spans flush.** Spans are exported asynchronously (more so when you swap in the `RootAwareOpenInferenceProcessor` below, which batches); always `await provider.forceFlush()` (or `provider.shutdown()`) before the process exits to avoid losing trailing spans.
* **Tool / handoff / guardrail spans expected but not present.** These spans only emit when the agent actually invokes the corresponding feature. The minimal example above has none — add `tools`, `handoffs`, or `inputGuardrails` to the `Agent` constructor to see them.
* **AX project is full of `GET /`, `POST /api/chat`, or other infra spans.** Your runtime's auto-OTel (Next.js, Vercel, NestJS, …) is piping HTTP / fetch / page-render spans through the global provider. See [Span filter](#span-filter) above for the `OpenInferenceFilteringSpanProcessor` recipe (and the `RootAwareOpenInferenceProcessor` if you also need a clean trace tree on the Traces tab). In Next.js apps an additional defence is to skip `provider.register()` entirely and pass the provider directly to `new OpenAIAgentsInstrumentation({ tracerProvider })` — the instrumentor resolves its tracer off the provider you hand it, so global registration isn't needed.
* **Agents SDK spans orphaned on the Traces tab.** Expected when the `OpenInferenceFilteringSpanProcessor` is the only processor — see [Span filter](#span-filter) for the `RootAwareOpenInferenceProcessor` recipe that promotes `Agent workflow` to root.

## Resources

<CardGroup>
  <Card icon="book-open" href="https://openai.github.io/openai-agents-js/" title="OpenAI Agents JS Documentation" horizontal />

  <Card icon="terminal" href="https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-openai-agents" title="OpenInference OpenAI Agents Instrumentor (JS/TS)" horizontal />

  <Card icon="github" href="https://github.com/openai/openai-agents-js" title="OpenAI Agents JS GitHub" horizontal />
</CardGroup>
