1. 先理解问题:Agent 核心为什么不应该绑定 Provider
一个 Agent 通常负责:
- 保存对话状态;
- 把用户消息加入上下文;
- 请求模型生成回复;
- 解析增量文本;
- 识别工具调用;
- 执行工具;
- 把工具结果追加回上下文;
- 继续下一轮模型请求。
其中第 3 步才真正涉及模型供应商:
Agent Loop │ ├─ 准备上下文 ├─ 调用模型生成回复 ← Provider 相关 ├─ 处理文本增量 ├─ 执行 Tool └─ 继续循环如果 Agent 核心直接写:
import{streamAnthropic}from"...";import{streamOpenAI}from"...";那么核心就会依赖具体 Provider,带来几个问题:
- Agent 包必须安装所有 Provider SDK;
- 增加新 Provider 时需要修改 Agent 核心;
- 测试必须真正模拟或连接具体模型;
- 用户无法方便地注入代理、缓存、重试、路由或本地模型;
- Agent 核心与模型鉴权、HTTP、API 格式耦合。
Pi 的做法是把“请求模型并获得事件流”抽象成一个函数类型:StreamFn。
2.StreamFn是什么
定义位于:
packages/agent/src/types.ts:33核心类型是:
exporttypeStreamFn=(model:Model<Api>,context:TranscriptContext,options?:SimpleStreamOptions,)=>AssistantMessageEventStream|Promise<AssistantMessageEventStream>;可以把它理解为:
给我: - 一个模型描述 - 一份已经整理好的对话上下文 - 一些请求选项 返回: - 一个 AssistantMessageEventStream它的职责非常明确:
StreamFn = 模型调用适配器Agent 不关心这个函数内部是:
- OpenAI Chat Completions;
- Anthropic Messages;
- Google Generative AI;
- Azure;
- Bedrock;
- 本地 Ollama;
- 远程代理;
- 测试用 Mock;
- 自己实现的 RPC 服务。
Agent 只依赖统一的返回类型:
AssistantMessageEventStream3. Agent 核心如何注入streamFn
AgentOptions要求传入streamFn:
// packages/agent/src/agent.ts:113exportinterfaceAgentOptions{initialState?:AgentInitialState;convertToLlm?:(messages:AgentMessage[])=>Message[]|Promise<Message[]>;transformContext?:(...);streamFn:StreamFn;...}构造 Agent 时:
constagent=newAgent({initialState:{model,},streamFn,});Agent 会保存这个函数:
// packages/agent/src/agent.ts:194publicstreamFunction:StreamFn;构造函数中完成注入:
// packages/agent/src/agent.ts:230this.streamFunction=runtimeOptions.streamFn??getDefaultStreamFn(