How to Configure Thinking and Reasoning Options in pi-ai
Configure thinking and reasoning in pi-ai by setting the reasoning property in SimpleStreamOptions to a ThinkingLevel value ("minimal" through "xhigh") while ensuring your selected model has reasoning: true in its definition.
The pi-ai library from the badlogic/pi-mono repository provides a unified interface for streaming LLM responses with configurable reasoning capabilities. Understanding how to configure thinking and reasoning options allows you to control the depth of chain-of-thought processing in supported models, balancing response quality against token usage and latency.
Understanding Thinking Levels and Budgets
The library defines reasoning configuration through two primary interfaces in packages/ai/src/types.ts.
ThinkingLevel controls the amount of chain-of-thought the model emits. Valid values are "minimal", "low", "medium", "high", and "xhigh".
ThinkingBudgets provides token limits for each level, useful for token-based providers like OpenAI or Anthropic. This is a partial map where you can specify budgets for specific levels (e.g., { high: 1200, xhigh: 2000 }).
Model Support is determined by the reasoning boolean flag on the Model type. Only models with reasoning: true (defined in packages/ai/src/models.generated.ts) will accept thinking options.
Configuring Reasoning in Stream Options
Basic Configuration with SimpleStreamOptions
To enable reasoning, pass the reasoning property when calling streamSimple or completeSimple from packages/ai/src/stream.ts.
import { streamSimple, models, type SimpleStreamOptions } from "@mariozechner/pi-ai";
const model = models.find(m => m.id === "anthropic-sonnet-3.5-thinking");
if (!model || !model.reasoning) throw new Error("Model not found or doesn't support reasoning");
const context = {
systemPrompt: "You are a helpful assistant.",
messages: [
{ role: "user", content: "Explain quantum entanglement.", timestamp: Date.now() }
],
};
const options: SimpleStreamOptions = {
reasoning: "high",
maxTokens: 1024,
temperature: 0.7,
};
const stream = streamSimple(model, context, options);
stream.on("thinking_start", e => console.log("🧠 Thinking started"));
stream.on("thinking_delta", e => process.stdout.write(e.delta));
stream.on("thinking_end", e => console.log("\n🧠 Thinking complete"));
stream.on("text_delta", e => process.stdout.write(e.delta));
stream.on("done", e => console.log("\n✅ Stream finished"));
Source: SimpleStreamOptions definition – packages/ai/src/types.ts
streamSimple implementation – packages/ai/src/stream.ts
Setting Custom Thinking Budgets
For token-based providers, constrain reasoning tokens by providing thinkingBudgets:
const options: SimpleStreamOptions = {
reasoning: "xhigh",
thinkingBudgets: {
high: 800,
xhigh: 1500,
},
maxTokens: 2048,
};
const stream = streamSimple(model, context, options);
Source: ThinkingBudgets type – packages/ai/src/types.ts
Selecting Compatible Models
Before configuring reasoning, verify the model supports it by checking the reasoning boolean in the model definition:
import { models } from "@mariozechner/pi-ai";
const reasoningModels = models.filter(m => m.reasoning === true);
console.log("Available reasoning models:", reasoningModels.map(m => m.id));
// Example: selecting a specific model
const model = models.find(m => m.id === "claude-sonnet-4-5-thinking");
if (model?.reasoning) {
// Safe to use reasoning options
}
Source: Model.reasoning flag – packages/ai/src/types.ts
Model definitions – packages/ai/src/models.generated.ts
Web UI Integration
Message Editor Component
The Web UI exposes reasoning controls through the <message-editor> component in packages/web-ui/src/components/MessageEditor.ts:
// In packages/web-ui/src/components/MessageEditor.ts
@customElement("message-editor")
export class MessageEditor extends LitElement {
@property() thinkingLevel: ThinkingLevel = "off";
private _modelSupportsThinking(model?: Model) {
return model?.reasoning === true;
}
private _onThinkingLevelChange(e: Event) {
this.thinkingLevel = (e.target as HTMLSelectElement).value as ThinkingLevel;
// Value passed to streamSimple via options.reasoning
}
}
Source: thinkingLevel property – packages/web-ui/src/components/MessageEditor.ts
Persistent Storage
User-selected thinking levels persist across sessions via the storage schema defined in packages/web-ui/src/storage/types.ts:
// packages/web-ui/src/storage/types.ts
interface SessionConfig {
thinkingLevel: ThinkingLevel; // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
// ... other config
}
Source: thinkingLevel field – packages/web-ui/src/storage/types.ts
Summary
- ThinkingLevel controls reasoning depth via
"minimal"to"xhigh"values inSimpleStreamOptions.reasoning. - ThinkingBudgets optionally caps token usage per level through
SimpleStreamOptions.thinkingBudgets. - Model Compatibility requires checking
model.reasoning === truebefore passing reasoning options. - Entry Points are
streamSimpleandcompleteSimpleinpackages/ai/src/stream.ts. - Web UI exposes these controls via the
thinkingLevelproperty inMessageEditorwith persistence in session storage.
Frequently Asked Questions
What happens if I configure reasoning on a model that does not support it?
If the model's reasoning property is false, the library silently ignores the reasoning option in SimpleStreamOptions. The stream will not emit <thinking> blocks, and the request proceeds as a standard completion without chain-of-thought processing.
How do token budgets interact with non-token-based providers?
Token budgets defined in thinkingBudgets are only respected by token-based providers such as OpenAI or Anthropic. Local or non-token providers ignore the budget map and rely solely on the qualitative thinkingLevel value to determine reasoning depth.
Can I change the thinking level dynamically during a conversation?
Yes. Since thinkingLevel is persisted in the session configuration (packages/web-ui/src/storage/types.ts) and passed fresh to each streamSimple call via SimpleStreamOptions, you can adjust the level between messages. The Web UI's MessageEditor component updates this value via the _onThinkingLevelChange handler before each new stream.
Where does the thinking output appear in the response stream?
Reasoning content emits as discrete events through the stream event listeners. Specifically, thinking_start, thinking_delta, and thinking_end events carry the chain-of-thought content, while text_delta carries the final response. This separation allows UI components to render thinking blocks distinctly from final answers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →