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 streamandstreamSimplecalls, pluscompleteandcompleteSimplecalls 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, includingagent.id,agent.name,agent.role, andsession.id
Install
bun add @inference/tracing @mariozechner/pi-aiConfigure 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.
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.
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.
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.
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.
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.turnfor the first model turn with the tool calllookup_orderTOOL span when you wrap local execution withmanualSpan()- another
pi-ai.openai.turnfor 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.
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 }orinstrumentPiAi(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.