Check out the newest way to compare different models for a task/agent harness: AutoEvals
Agent Frameworks

Traces

PI AI Traces

Trace PI AI model turns, streamed output, tool calls, token usage, costs, and agent identity through the Inference platform.

Using current @earendil-works/pi-ai or @earendil-works/pi-agent-core? Use the Pi Agent integration.

Inference platform instruments @mariozechner/pi-ai in TypeScript. PI AI is a unified LLM API and provider registry. Inference platform 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.

This page covers the legacy @mariozechner package family. Inference platform continues to support it for existing applications.

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

TypeScript
bun add @inference/tracing @mariozechner/pi-ai

Configure Export

Set the Inference Tracing endpoint and token before your app starts. Generate a token at API Keys.

export INFERENCE_OTLP_ENDPOINT="https://telemetry.inference.net"
export INFERENCE_API_KEY="<your-token>"
export INFERENCE_SERVICE_NAME="pi-ai-agent"

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

Initialize Tracing

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

TypeScript
import * as piAi from "@mariozechner/pi-ai";
import { setup } from "@inference/tracing";

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

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

TypeScript
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 Inference platform emits the LLM span when the call finishes.

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

TypeScript
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. Inference platform finishes the span when PI AI emits a done/error event or when .result() resolves.

TypeScript
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. Inference platform 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.

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

TypeScript
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 the Inference platform

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 Inference platform 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.

On this page