> ## 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.

# PI AI Traces

> Trace PI AI model turns, streamed output, tool calls, token usage, costs, and agent identity through Catalyst.

Catalyst instruments `@mariozechner/pi-ai` in TypeScript. PI AI is a unified
LLM API and provider registry. Catalyst patches the registered PI AI providers
so each model turn through `stream`, `streamSimple`, `complete`, or
`completeSimple` emits an OpenInference LLM span.

Use this guide for Node applications that use PI AI for model calls, tool
calling, cross-provider handoffs, or agent workflows. PI AI is a TypeScript
package, so there is no Python equivalent for this integration.

## What Is Captured

* One LLM span per PI AI model turn, named like `pi-ai.<provider>.turn`
* `stream` and `streamSimple` calls, plus `complete` and `completeSimple`
  calls because PI AI completes by resolving the underlying stream
* System prompts, input messages, assistant output, model name, provider, and
  invocation parameters
* Tool call IDs, names, and JSON arguments from assistant messages
* Token usage, prompt cache read/write counts, and total cost when PI AI
  returns them
* Finish reason, errors, aborts, and exception details
* Active `agentSpan()` identity, including `agent.id`, `agent.name`,
  `agent.role`, and `session.id`

## Install

<Metadata text="integrations/traces/pi-ai-install" />

```bash TypeScript theme={"system"}
bun add @inference/tracing @mariozechner/pi-ai
```

## Configure Export

Set the Catalyst endpoint and token before your app starts. Generate a token at
[API Keys](https://inference.net/dashboard/api-keys).

<Metadata text="integrations/traces/pi-ai-env" />

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

export OPENAI_API_KEY="<your-openai-api-key>"
```

## Initialize Tracing

Initialize Catalyst before making PI AI calls. Import the PI AI namespace and
pass it to `setup()` so Catalyst patches the same provider registry your app
uses.

<Metadata text="integrations/traces/pi-ai-init" />

```typescript TypeScript theme={"system"}
import * as piAi from "@mariozechner/pi-ai";
import { setup } from "@inference/tracing";

const tracing = await setup({
  serviceName: process.env.CATALYST_SERVICE_NAME ?? "pi-ai-agent",
  modules: { piAi },
});
```

For explicit manual initialization, disable auto-instrumentation and use the PI
AI subpath helper.

<Metadata text="integrations/traces/pi-ai-manual-init" />

```typescript TypeScript theme={"system"}
import * as piAi from "@mariozechner/pi-ai";
import { setup } from "@inference/tracing";
import { instrumentPiAi } from "@inference/tracing/pi-ai";

const tracing = await setup({ autoInstrument: false });
instrumentPiAi(piAi, tracing);
```

## Complete Call

`completeSimple()` is the smallest path. PI AI resolves it through a provider
stream, so Catalyst emits the LLM span when the call finishes.

The examples below assume you initialized `tracing` with the setup block above.

<Metadata text="integrations/traces/pi-ai-complete" />

```typescript TypeScript theme={"system"}
import { completeSimple, getModel, type Context } from "@mariozechner/pi-ai";

const model = getModel("openai", "gpt-4o-mini");
const context: Context = {
  systemPrompt: "You answer in one concise sentence.",
  messages: [{ role: "user", content: "Why are trace trees useful?" }],
};

const response = await completeSimple(model, context);
console.log(response.content);
```

Expected span:

* `pi-ai.openai.turn`

Expected promoted fields include `llm.model_name`, `input.value`,
`output.value`, `llm.token_count.prompt`, `llm.token_count.completion`,
`llm.token_count.total`, and `llm.invocation_parameters` when PI AI returns the
corresponding data.

## Streaming

For streaming calls, consume the stream and call `await stream.result()` before
process shutdown. Catalyst finishes the span when PI AI emits a done/error event
or when `.result()` resolves.

<Metadata text="integrations/traces/pi-ai-stream" />

```typescript TypeScript theme={"system"}
import { getModel, streamSimple, type Context } from "@mariozechner/pi-ai";

const model = getModel("openai", "gpt-4o-mini");
const context: Context = {
  messages: [{ role: "user", content: "Stream a sentence about observability." }],
};

const stream = streamSimple(model, context);

for await (const event of stream) {
  if (event.type === "text_delta") {
    process.stdout.write(event.delta);
  }
}

const finalMessage = await stream.result();
console.log("\nFinished:", finalMessage.stopReason);
```

For short-lived scripts, call `await tracing.shutdown()` after the stream is
fully consumed.

## Tool Loop

PI AI returns tool calls in assistant message content. Catalyst records the tool
call name, ID, and arguments on the PI AI LLM span. Your local tool execution is
application code, so wrap it with `manualSpan()` if you also want a TOOL child
span for the work your app performs.

<Metadata text="integrations/traces/pi-ai-tool-loop" />

```typescript TypeScript theme={"system"}
import {
  Type,
  completeSimple,
  getModel,
  type Context,
  type Tool,
} from "@mariozechner/pi-ai";
import { manualSpan, SpanKindValues } from "@inference/tracing";

const model = getModel("openai", "gpt-4o-mini");

const tools: Tool[] = [
  {
    name: "lookup_order",
    description: "Look up an order by ID.",
    parameters: Type.Object({
      orderId: Type.String({ description: "Customer order ID." }),
    }),
  },
];

const context: Context = {
  systemPrompt: "Use tools when you need order data.",
  messages: [{ role: "user", content: "Where is order ABC-123?" }],
  tools,
};

const firstTurn = await completeSimple(model, context);
context.messages.push(firstTurn);

for (const block of firstTurn.content) {
  if (block.type !== "toolCall" || block.name !== "lookup_order") continue;
  const args = block.arguments as { orderId: string };

  const order = await manualSpan(
    {
      spanName: "lookup_order",
      spanKind: SpanKindValues.TOOL,
      toolName: block.name,
      toolCallId: block.id,
      input: args,
    },
    async (span) => {
      const result = {
        orderId: args.orderId,
        status: "shipped",
        eta: "Friday",
      };
      span.setOutput(result);
      return result;
    },
  );

  context.messages.push({
    role: "toolResult",
    toolCallId: block.id,
    toolName: block.name,
    content: [{ type: "text", text: JSON.stringify(order) }],
    isError: false,
    timestamp: Date.now(),
  });
}

const finalTurn = await completeSimple(model, context);
console.log(finalTurn.content);
```

Expected spans:

* `pi-ai.openai.turn` for the first model turn with the tool call
* `lookup_order` TOOL span when you wrap local execution with `manualSpan()`
* another `pi-ai.openai.turn` for the continuation after the tool result

## Stable Agent Identity

Wrap the full PI AI run in `agentSpan()` when one user request can produce
multiple model turns or tool executions. PI AI LLM spans inherit the active
agent identity and nest under the AGENT span.

<Metadata text="integrations/traces/pi-ai-agent-identity" />

```typescript TypeScript theme={"system"}
import { agentSpan } from "@inference/tracing";
import { completeSimple, getModel, type Context } from "@mariozechner/pi-ai";

const model = getModel("openai", "gpt-4o-mini");

await agentSpan(
  {
    agentId: "pi-ai-support-agent",
    agentName: "PI AI Support Agent",
    spanName: "pi-ai-support-agent.run",
    sessionId: "conversation-order-abc-123",
    role: "support",
    system: "pi-ai",
  },
  async (span) => {
    const input = "Summarize order ABC-123 for the customer.";
    const context: Context = {
      messages: [{ role: "user", content: input }],
    };

    span.setInput(input);
    const response = await completeSimple(model, context);
    span.setOutput(response.content);
  },
);
```

## Verify In Catalyst

Filter traces by your `service.name`, for example `pi-ai-agent`. A successful
run should show PI AI LLM spans named by provider, such as
`pi-ai.openai.turn`, with captured input/output, model metadata, usage, finish
reason, and tool call attributes. If you wrap the run in `agentSpan()`, the same
LLM spans should show the inherited `agent.id`, `agent.name`, and `agent.role`.

If no PI AI spans appear:

* Initialize Catalyst before making PI AI calls.
* Pass the PI AI namespace directly with `modules: { piAi }` or
  `instrumentPiAi(piAi, tracing)`.
* Consume streaming results or call `.result()` so PI AI can finish the turn.
* Call `await tracing.shutdown()` before process exit in short-lived scripts.
