Using the @pi/ai Library
Relevant source files
The following files were used as context for generating this wiki page:The @mariozechner/pi-ai package provides a unified, multi-provider API for interacting with Large Language Models (LLMs) from providers such as OpenAI, Anthropic, and Google. Its primary purpose is to abstract the differences between these provider APIs, offering a consistent interface for streaming responses, tool calling, and model management. This library is a foundational component within the pi-mono repository, used by other packages like @mariozechner/pi-coding-agent and @mariozechner/pi-mom to power their AI capabilities.
The library supports text and image inputs, streaming events for real-time UI updates, and a structured approach to defining and handling tool calls. It is designed to be flexible, allowing consumers to either get a complete, aggregated response or process a fine-grained stream of events as the model generates its output.
Sources: packages/ai/package.json, README.md, packages/coding-agent/src/main.ts:1, packages/mom/src/agent.ts:2
Core Concepts
The library's functionality revolves around two main functions, stream() and complete(), which both operate on a Context object.
The Context Object
All interactions with the LLM are defined within a Context object. This object encapsulates the entire state of the conversation needed for the model to generate a response.
This diagram shows the structure of the Context object.
systemPrompt: Instructions that guide the model's behavior throughout the conversation.messages: An array ofMessageobjects representing the conversation history.tools: An array ofTooldefinitions that the model can use.
Sources: packages/ai/test/stream.test.ts:32-35, packages/ai/src/types.ts
Core Functions
| Function | Description | Use Case |
|---|---|---|
stream() | Initiates a streaming connection to the LLM and returns an AsyncGenerator of Event objects. | For real-time applications (like TUIs or web UIs) that need to display the response as it's being generated. |
complete() | A wrapper around stream() that collects all events and returns a single, fully-formed AssistantMessage object. | For simpler, non-interactive use cases where only the final response is needed. |
Sources: packages/ai/src/stream.ts:13-14
Streaming and Completion
The library offers two primary modes of interaction: streaming for real-time feedback and completion for aggregated results.
complete() Function
The complete() function provides a simple request-response mechanism. It internally handles the streaming process and resolves with a single AssistantMessage once the model has finished generating its response, including any tool calls.
The following diagram illustrates the complete() function's internal flow.
Sources: packages/ai/src/stream.ts:13-33
stream() Function and Events
The stream() function is the core of the library, providing a detailed, event-based stream of the model's generation process. It returns an async iterator that yields Event objects.
This sequence diagram shows the lifecycle of a stream() call.
The stream emits various event types that allow for granular control over the UI and application logic.
| Event Type | Description |
|---|---|
message_start | Fired when the assistant message begins. |
message_update | Fired on every update, containing the partial message. |
message_end | Fired when the entire message, including all content blocks, is complete. |
text_start | Fired when a text block begins. |
text_delta | Fired for each chunk of a text block. |
text_end | Fired when a text block is complete. |
toolcall_start | Fired when a tool call block begins. |
toolcall_delta | Fired for each chunk of the tool call arguments. |
toolcall_end | Fired when a tool call block is complete and arguments are fully parsed. |
thinking_start | Fired when the model begins a "thinking" phase (for models that support it). |
thinking_delta | Fired for each chunk of the thinking process. |
thinking_end | Fired when the thinking phase is complete. |
Sources: packages/ai/src/stream.ts:35, packages/ai/test/stream.test.ts:132-143
Tool Usage
A key feature of the @pi/ai library is its support for tool calling, allowing the LLM to execute functions defined by the application.
Defining a Tool
Tools are defined with a name, a description, and a parameter schema using TypeBox. The schema is crucial as it informs the model how to structure the arguments for the tool call.
Here is an example of a calculator tool definition:
// packages/ai/test/stream.test.ts:20-32
const calculatorSchema = Type.Object({
a: Type.Number({ description: "First number" }),
b: Type.Number({ description: "Second number" }),
operation: StringEnum(["add", "subtract", "multiply", "divide"], {
description: "The operation to perform. One of 'add', 'subtract', 'multiply', 'divide'.",
}),
});
const calculatorTool: Tool<typeof calculatorSchema> = {
name: "calculator",
description: "Perform basic arithmetic operations",
parameters: calculatorSchema,
};
This definition is passed within the Context object to make the tool available to the model.
Sources: packages/ai/test/stream.test.ts:20-32, packages/web-ui/src/prompts/prompts.ts:10-100
Tool Call Flow
When a user prompt requires a tool, the library facilitates a multi-step conversation with the model.
This diagram shows the sequence of events during a tool call.
The application is responsible for executing the tool based on the toolCall block in the assistant's message and then sending a toolResult message back to the model to continue the conversation.
Sources: packages/ai/test/stream.test.ts:101-127
Model and Provider Management
The library is used in conjunction with logic in consumer packages (like coding-agent) to manage models from different providers.
Model Discovery and Selection
The coding-agent package contains functions that build on @pi/ai to discover and select models.
getAvailableModels(): Scans for available models, likely by checking for corresponding API keys in environment variables.findModel(provider, model): Locates a specific model object by its provider and ID.getApiKeyForModel(model): Dynamically retrieves the correct API key for a given model's provider from environment variables (e.g.,ANTHROPIC_API_KEY,OPENAI_API_KEY).
Sources: packages/coding-agent/src/main.ts:57, packages/coding-agent/src/main.ts:1100-1111
Default Models
The coding-agent defines a set of default models to use for each provider if one is not explicitly specified.
| Provider | Default Model ID |
|---|---|
anthropic | claude-sonnet-4-5 |
openai | gpt-5.1-codex |
google | gemini-2.5-pro |
openrouter | openai/gpt-5.1-codex |
xai | grok-4-fast-non-reasoning |
groq | openai/gpt-oss-120b |
cerebras | zai-glm-4.6 |
zai | glm-4.6 |
Sources: packages/coding-agent/src/main.ts:43-53
Advanced Features
Image Support
The library supports multimodal inputs, allowing images to be included in user messages. To use this, the model must support image inputs (checked via model.input.includes("image")).
An image is provided as an ImageContent block within a message's content array.
// packages/ai/test/stream.test.ts:210-215
const imageContent: ImageContent = {
type: "image",
data: base64Image, // base64 encoded string
mimeType: "image/png",
};
The coding-agent demonstrates how to process file arguments (@image.png), read them, convert to base64, and attach them to the initial prompt.
Sources: packages/ai/test/stream.test.ts:200-203, 210-215, packages/coding-agent/src/main.ts:182-205
"Thinking" and Reasoning
For models that support it (e.g., claude-sonnet-4-5 where reasoning: true), the library can stream the model's intermediate "thinking" process. This is exposed through the thinking_start, thinking_delta, and thinking_end events. This feature provides insight into the model's reasoning before it arrives at a final answer or tool call. It is enabled by passing options to the stream or complete function, such as { reasoningEffort: "high" } or { thinkingEnabled: true }.
Sources: packages/ai/test/stream.test.ts:178-198, packages/web-ui/src/utils/test-sessions.ts:133
Conclusion
The @mariozechner/pi-ai library serves as a robust and unified interface for leveraging multiple LLM providers. Its consistent API for streaming, tool usage, and multimodal inputs simplifies the development of complex AI-powered applications. By handling the nuances of different provider APIs, it allows developers to focus on application logic rather than on provider-specific integration details, as demonstrated by its adoption in the coding-agent and mom packages.