π Pi Agent Study
Chapter 02 · model interface

02 · 模型接口

Context 进 provider,事件流回 agent

这一章读 packages/ai:它把 agent 的内部状态压成统一 Context, 再把不同厂商的 stream 翻译成 pi 标准事件。provider 层负责翻译,agent loop 层负责执行。

这一章看什么

目标不是记住某个厂商 API,而是理解 pi 如何把不同模型统一到同一个 agent loop 里。

读类型

  • Context
  • Message
  • AssistantMessage
  • AssistantMessageEvent

读路由

  • streamSimple() 不直接调用 OpenAI。
  • 它通过 model.api 找 provider。
  • provider 才负责厂商适配。

读翻译

  • Context -> payload
  • response.* -> pi event
  • toolCall -> toolResult -> Context

普通请求 vs Agent 请求

普通 LLM 请求通常只问模型一次;agent 请求会把模型输出、工具执行和工具结果组成循环。

类型
请求里有什么
返回后发生什么
普通 LLM 请求
system prompt、用户消息、历史消息。
模型直接返回文本,调用方把文本展示给用户。
Agent 请求
system prompt、历史消息、可用工具 schema、已有 tool result。
模型可能返回文本,也可能返回 toolCall;agent 执行工具后把 toolResult 放回下一轮上下文。
本章先记住 Context 是给 provider 的统一请求包;Provider 是厂商适配器; AssistantMessageEvent 是模型流式输出的标准事件。三者先按这个粗粒度理解,不需要一开始背所有字段。

Context 是边界

agent 内部可以很复杂,但 provider 只接收统一的 Context

export interface Context {
  systemPrompt?: string;
  messages: Message[];
  tools?: Tool[];
}

export type Message =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage;

怎么理解

  • systemPrompt 是本轮系统提示词。
  • messages 是模型能看到的历史。
  • tools 是模型可调用工具定义。
  • provider 不知道 CLI、TUI、session 文件和队列。
边界判断 Context 之前是 agent 内部结构,之后是具体 provider 的 API payload。新手第一遍只要抓住三个字段: systemPromptmessagestools

Assistant 不是只有文本

assistant message 的内容可以是 text、thinking 和 toolCall。

结构化内容

export interface AssistantMessage {
  role: "assistant";
  content: (
    TextContent |
    ThinkingContent |
    ToolCall
  )[];
  usage: Usage;
  stopReason: StopReason;
}

标准事件

  • text_start / text_delta / text_end
  • thinking_start / thinking_delta / thinking_end
  • toolcall_start / toolcall_delta / toolcall_end
  • done / error
为什么要事件流 provider 一边返回,agent loop 一边更新 partial assistant message,同时 UI/JSON mode 可以实时看到变化。

start

创建一个还没完成的 assistant message,让 agent loop 和 UI 知道输出开始了。

delta

不断追加片段。文本、thinking 和工具参数都可能分多段返回。

end

当前内容块完成。对 toolCall 来说,参数到这里才适合解析和校验。

streamSimple 只是路由

它根据 model.api 找 provider,不关心厂商请求细节。

export function streamSimple(model, context, options) {
  const provider = resolveApiProvider(model.api);
  return provider.streamSimple(
    model,
    context,
    withEnvApiKey(model, options)
  );
}

关键点

  • model.api 决定进入哪个 provider。
  • API key 可以从 options 或环境变量补齐。
  • OpenAI、Anthropic、Google 的差异被压到 provider 内部。

OpenAI Responses 怎么翻译

OpenAI 返回自己的 response.* 事件,pi 在 shared adapter 里翻译成标准事件。

01Context统一输入。
02buildParams转成 Responses params。
03client.responses.create打开 OpenAI stream。
04output维护统一 AssistantMessage。
05processResponsesStream翻译 response.*。
06pi eventsthinking/text/toolcall。
07agent-loop消费事件。

thinking

  • response.output_item.added + reasoning
  • response.reasoning_text.delta
  • response.output_item.done + reasoning

text

  • response.output_item.added + message
  • response.output_text.delta
  • response.output_item.done + message

toolCall

  • response.output_item.added + function_call
  • response.function_call_arguments.delta
  • response.output_item.done + function_call
toolCall 三段式 toolcall_start 表示模型开始生成一次工具请求; toolcall_delta 是参数 JSON 的增量片段; toolcall_end 表示工具名和参数已经完整。工具不能在 delta 阶段执行。

ToolResult 必须回填

模型发出 toolCall 后还不知道工具结果,必须通过下一轮上下文告诉它。

assistant: toolCall(read README.md)
-> pi 本地执行 read
-> toolResult: README.md 内容
-> Context.messages
-> 下一次 streamAssistantResponse()
-> 模型继续回答

为什么

  • 本地执行结果只有 pi 知道。
  • 模型下一轮必须看到 ToolResultMessage
  • 否则模型只知道自己请求了工具,不知道结果。
  • 所以 toolResult 是上下文,不只是日志。
职责边界 provider 负责把厂商事件翻译成完整 toolCall;agent loop 负责执行工具,并把 toolResult 放回上下文。

常见误解 1

toolCall 不是工具已经执行了,只是 assistant message 里的一段结构化请求。

常见误解 2

toolResult 不是日志,而是下一轮模型请求必须携带的上下文。

常见误解 3

provider 不应该知道 CLI、TUI 或 session 文件,它只做请求和事件翻译。

02 完成度

这一章已经把模型层主线走完,后续可以进入更细的 agent loop 和工具执行。

已经完成

  • 理解 Context 是 agent 和 provider 的边界。
  • 理解 Message 的三种角色。
  • 理解 assistant content 的三种结构。
  • 理解 provider stream event。
  • 理解 toolcall_start/delta/end 的意义。
  • 理解 OpenAI Responses 事件翻译路径。
  • 理解 toolResult 的上下文作用。

下一章继续

  • 深入 runLoop()
  • 看工具并行/串行执行。
  • beforeToolCallafterToolCall
  • 看 steering/followUp 如何影响下一轮。