Skip to content

Chapter 11 · Thinking and Reasoning Modes ​

Chapter goals: Master pi-ai's unified interface for thinking/reasoning, learn to enable reasoning based on model capabilities, receive thinking content in streaming, and control token budgets.

11.1 What Is Thinking/Reasoning ​

Many models support a "thinking" capability — generating an internal reasoning process before giving the final answer (e.g. Claude's extended thinking, DeepSeek-R1's chain-of-thought). pi-ai unifies these wildly different provider parameters into a single interface.

Key points:

  • You can判断 whether a model supports reasoning through the model's reasoning property;
  • Passing reasoning options to a model that does not support reasoning is silently ignored — no error is thrown;
  • Thinking content is returned as an独立 block type (thinking), separated from body text (text).
typescript
import { createModels } from '@earendil-works/pi-ai';
import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';

const models = createModels();
models.setProvider(anthropicProvider());

// Check whether the model supports reasoning
const model = models.getModel('anthropic', 'claude-sonnet-4-5')!;
if (model.reasoning) {
  console.log('Model supports reasoning/thinking');
}

11.2 Unified Interface: streamSimple / completeSimple ​

streamSimple and completeSimple are pi-ai's recommended high-level interfaces. They接受unified reasoning options (such as reasoning: 'medium'), and the library automatically translates them into each provider's native parameters:

typescript
// Unified reasoning levels: off | minimal | low | medium | high | xhigh | max
const response = await models.completeSimple(model, context, {
  reasoning: 'medium', // Automatically mapped to the provider-specific parameter
});

// The streaming version supports the same
const stream = models.streamSimple(model, context, { reasoning: 'low' });
for await (const event of stream) {
  if (event.type === 'message_update') {
    console.log(event.assistantMessageEvent.type);
  }
}

Silent degradation

If you pass reasoning: 'high' to a model that does not support reasoning, pi-ai will not throw — the option is silently ignored. This lets the same code switch freely between different model tiers.

11.3 Provider-Specific Options: stream / complete ​

The lower-level interfaces stream / complete expose provider-specific native options for fine-grained control:

typescript
// Lower-level interface: pass provider-native options directly
models.stream(m, context, {
  thinkingEnabled: true,        // Enable thinking (Anthropic extended thinking)
  thinkingBudgetTokens: 2048,   // Thinking token budget upper limit
});

Trade-offs between the two interfaces:

InterfaceReasoning ControlSuitable Scenario
streamSimple / completeSimpleUnified level stringsCross-provider generic code
stream / completeProvider-native parametersNeed precise budget details

11.4 Receiving Thinking Content in Streaming ​

Thinking content streams out as独立 events. Note: events for different content blocks are not guaranteed to be continuous — the provider may interleave text, thinking, and toolcall deltas within a single upstream data chunk; you must use contentIndex to associate events with their blocks:

typescript
for await (const event of models.stream(model, context)) {
  switch (event.type) {
    case 'thinking_start':
      // Thinking block begins; contentIndex marks its position in the content array
      console.log('[Model is thinking...]');
      break;
    case 'thinking_delta':
      // Receive a thinking fragment increment
      process.stdout.write(event.delta);
      break;
    case 'thinking_end':
      // Thinking block complete; event.content is the full thinking text
      console.log('\n[Thinking complete]');
      break;
  }
}

Event reference:

EventMeaningKey Fields
thinking_startThinking block beginscontentIndex
thinking_deltaThinking fragment incrementdelta, contentIndex
thinking_endThinking block completescontent (full thinking), contentIndex

11.5 Using Thinking Levels in Agents ​

The Agent class controls thinking depth through initialState.thinkingLevel or runtime state modification:

typescript
import { Agent } from '@earendil-works/pi-agent-core';

const agent = new Agent({
  initialState: {
    systemPrompt: 'You are a rigorous algorithm assistant.',
    model,
    thinkingLevel: 'medium', // off/minimal/low/medium/high/xhigh/max
    tools: [],
    messages: [],
  },
  streamFn: models.streamSimple.bind(models),
});

// Dynamically adjust difficulty at runtime: turn off thinking for simple questions to save tokens
agent.state.thinkingLevel = 'off';
await agent.prompt('What is 1+1?');

agent.state.thinkingLevel = 'high'; // Turn on high-tier reasoning for complex questions
await agent.prompt('Prove: Why has the conjecture that every even number greater than 2 can be written as the sum of two primes not been proven?');

For providers that bill thinking by token, you can also customize the budget for each level:

typescript
// Customize budgets per level for token-based thinking providers
agent.thinkingBudgets = {
  minimal: 128,
  low: 512,
  medium: 1024,
  high: 2048,
};

Additionally, some models expose unique ultra-high reasoning tiers (such as xhigh, max). Use getSupportedThinkingLevels(model) to query which levels a specific model actually supports, avoiding blind setting.

Chapter Summary ​

  • Use model.reasoning to judge whether a model supports reasoning; unsupported models silently ignore reasoning options;
  • streamSimple/completeSimple provide unified level strings; stream/complete expose native parameters;
  • Thinking events have three types: start/delta/end; you must use contentIndex to associate blocks — do not assume continuity;
  • Agents control thinking depth and budget through thinkingLevel and thinkingBudgets;
  • getSupportedThinkingLevels() queries the set of levels a specific model supports.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What happens when you pass reasoning: "high" to a model that does not support thinking?

2. When handling streaming thinking events, why must you use contentIndex?

3. To use unified reasoning control across providers with the same code, which interface should you use?

4. How do you query which thinking levels a specific model actually supports?

🛠️ Hands-on Practice ​

  1. Send requests to the same math problem with reasoning: 'off', 'low', and 'high' respectively, and compare response quality and latency.
  2. Write a terminal program that renders the thinking process in gray text and body text in white.
  3. Add an "adaptive thinking" strategy to your Agent: automatically select thinkingLevel based on the length of user input.

Next we let the Agent "see" images — Chapter 12 · Image Input and Image Generation.