从阶段二的模型配置实践进入 Pi 源码,理解 provider abstraction、streaming、tool calling、thinking events、model handoff 和 OpenAI-compatible provider。
Pi 的 LLM Provider 层不是调用某个模型 API 的薄封装,而是一层把不同模型供应商统一成同一种 assistant event stream 的适配层。
为什么 agent loop 不应该直接知道 OpenAI、Anthropic、本地模型或内部 gateway 的请求差异?
- ModelRegistry 先解析模型和认证。
- Agent loop 只提交统一的消息、工具和推理配置。
- Provider adapter 把供应商响应转成统一事件。
- 上层只消费 text、thinking、tool call、done 和 error。
源码推演(省略版)
Section titled “源码推演(省略版)”下面不是完整源码,而是把本课主线压缩成可以在文档里直接阅读的关键形状。读者即使不打开本地源码,也应该能看出运行时如何组织职责。
async function* streamAssistant(request: LlmRequest) { const raw = provider.stream(request);
for await (const chunk of raw) { if (chunk.kind === "text") yield { type: "text_delta", text: chunk.text }; if (chunk.kind === "reasoning") yield { type: "thinking_delta", text: chunk.text }; if (chunk.kind === "tool") yield { type: "toolcall_delta", call: normalizeToolCall(chunk) }; }
yield { type: "done" };}- 省略版源码展示了 provider 层的核心价值:归一化。
- 不同供应商的 chunk 格式可以不同,但进入 agent loop 前要变成统一事件。
- 接入新 gateway 时,优先判断能否通过兼容配置解决,不能时才新增 adapter。
设计一个 provider event union,覆盖文本、thinking、工具调用、完成和错误。
- 能说明 provider adapter 和 agent loop 的分工。
- 能解释 streaming 事件为什么要归一化。
- 能判断新增 provider 与使用兼容配置的取舍。
- 实践题:基于本课的省略版源码,补出一个最小实现草图,要求写清输入、输出、副作用和错误处理。
- 思考题:本课机制如果只靠 prompt 约束,而不放进 harness 或 runtime,会出现什么工程风险?