第七章 一个接口连接多家模型
7.1 供应商、模型与协议不是同一层
假设你把助手从一家服务换到另一家:鉴权,也就是确认请求有资格使用服务的方式,可能变了,消息格式可能变了,流式响应里的字段也可能变了。如果每个界面都直接适配这些差异,换模型就会牵动许多代码。packages/ai 提供统一接口,把这些变化集中在供应商与协议适配层。它的包名是 pi-ai,是上层 Agent 使用模型的基础。
先定义三个名词。模型是一个具体可调用的条目,包含标识、上下文窗口、输入能力和价格等元数据。Provider 是供应商运行单元,负责模型列表、鉴权和请求行为。API 在这里主要表示通信协议:例如 OpenAI Responses 与 Anthropic Messages。多个供应商可能复用同一种协议;同一个网关供应商也可能承载多种协议。
Models 是保存 Provider 的集合,也是应用常用的请求入口。应用注册需要的供应商,查出模型,再调用集合的 streamSimple 等方法。集合根据模型的供应商标识寻找 Provider,规范化上下文并解析鉴权。Provider 随后把调用交给适合该模型 API 的实现。真正的协议字段转换与响应解析发生在 src/api/,不是由 Agent 循环逐家处理。
这个边界有一个具体好处:你可以只装配需要的供应商目录,不必在主入口立即加载全部模型和服务商 SDK,即软件开发工具包。内置工厂把模型目录、鉴权规则和延迟加载的 API 实现拼起来。若主动选用全部供应商入口,才会引入全部内置目录。至于最终打包多大,还与打包工具是否拆分代码有关,不能仅凭“延迟加载”四个字保证。
7.2 一次流式调用怎样返回
流式响应指服务器边生成边发送内容,客户端可以逐段处理,而非等待整篇完成。streamSimple 提供统一的常用选项,包括模型支持时使用的推理等级。底层的 stream 还允许传入特定 API 的完整选项。二者返回的都不是普通字符串,而是既能异步迭代事件、又能取得最终助手消息的事件流。
下面只用伪代码说明装配关系,不是可直接复制执行的 SDK 程序:先建立 Models 集合,注册目标 Provider,取得具体模型,准备用户消息,调用 streamSimple,逐条读取事件,最后读取流的结果。真实程序还需要正确导入供应商、处理模型不存在与鉴权,以及按类型构造上下文。
正常事件从 start 开始,文本块可以依次产生 text_start、若干 text_delta 和 text_end;推理块与工具调用块也各有对应事件。成功以 done 结束,失败以 error 结束。准备请求时就失败,可能只有一个错误事件。因此界面不能假设每次都先收到正文,也不能靠正文是否为空判断成功。
事件带有内容块序号,同一次回答中的文本、推理和工具参数可能交错到达。使用者需要按序号把更新放回对应位置。还有一个细节:事件的 partial 是持续变化的当前响应,并非每个历史时刻的独立快照。需要存档某一时刻时,应用必须自行保存合适的副本,不能保留一个引用就期待它永远不变。
完成后,助手消息保存内容、结束原因、模型身份与用量等信息。completeSimple 是等待这项最终结果的方便方法,底层仍沿用相应流式路径。应用可以据此做普通问答,也可以把结果交给第六章的循环,进一步处理工具调用。
7.3 声明工具与执行工具分开
tools 表示允许模型使用的工具声明:工具叫什么、做什么、参数是什么结构。toolCall 表示模型本次实际提出的一次调用,带调用标识、工具名和参数。菜单与订单的关系可以帮助理解:菜单上列着“读取文件”,订单才会写“读取哪个文件”。声明工具不会自行读取文件,收到调用也不代表操作已经发生。
在纯 pi-ai 层,应用负责执行工具并把结果追加为 toolResult 消息,再次请求模型。pi-agent-core 则代办这套重复流程。这个分工很实用:只要模型生成响应的服务可复用,业务可以选择自己的工具执行方式。工具的危险程度也不由名字决定,最终取决于宿主绑定的函数能访问哪些资源。
JSON 是用对象、数组与基本值表示数据的文本格式。模型生成工具参数时,JSON 可能分段到达。中途收到的对象可以缺字段、缺数组元素,甚至字符串只到一半,适合显示预览,不适合立即执行。完整的 toolcall_end 也不代表已通过参数模式校验。调用方仍要校验;Agent 循环提供了这一步。协议适配和业务校验解决的是不同层的问题。
上下文还可以带图片和系统消息。系统消息记录基础指令、命名片段更新以及工具增删。公共请求入口把便捷的系统提示和工具字段规范化为消息记录,下层 Provider 收到的是只有 messages 的规范化上下文。由此可以重放当前提示与工具集合;遇到不支持中途系统更新的模型,再由适配层转换为它接受的形式。
7.4 鉴权、目录、费用和限制
鉴权就是确认请求有资格使用服务,通常通过 API 密钥或 OAuth 凭证完成。OAuth 指用户授权后取得访问凭证的机制。Provider 声明怎样找凭证,Models 在请求前解析并合入选项。存储的凭证、环境变量和请求显式选项有既定优先关系;OAuth 凭证过期时可能需要刷新。库默认的凭证存储在内存中,长期保存应由宿主注入存储实现,不应假定单用 SDK 就会写入某个登录文件。
模型目录也分两类。静态目录随包提供,可同步读取;动态供应商可以在显式 refresh() 后发布更新。刷新的是目录描述,不是下载或更新模型权重。查询返回的是当前已知列表,不等于现场探测模型可用性。还应区分“目录里存在”与“鉴权已配置”,应用可通过可用模型接口过滤。即使通过过滤,网络故障、配额限制仍可能让实际请求失败。
token 是模型处理内容的计量单位,不等同于汉字、单词或字节。消息的 usage 记录输入、输出、缓存读写等用量,并按模型目录中的费率计算费用。推理 token 在提供方有报告时可单独记录,但属于输出 token 的一部分,不能重复相加。价格元数据和供应商实际账单可能存在时间或计费细则差异,应用不应把估算当最终财务凭证。
上下文窗口限制模型一次能处理多少输入与相关内容,最大输出限制一次能生成多少内容。视觉能力、推理等级、严格工具模式等也依赖具体模型与协议。统一接口减少了调用方式的差异,却没有让所有模型获得相同能力。选择模型时应查看元数据和支持等级,并读取实际结束原因;撞到长度限制的回答,可能只是一份尚未完成的输出。
当前 Models 还区分聊天、图像生成和分类模型。聊天模型走流式接口,图像模型走 generateImages,分类模型走 classify。同一个上游名字可能对应不同操作条目。不要拿图像模型去调用聊天流;“统一管理”指共用供应商和鉴权等基础设施,并不意味着所有操作的输入输出都完全一样。
7.5 小结与练习
pi-ai 把供应商、协议和应用连接起来。学习时应先看调用边界,再认识事件与消息,最后处理凭证、目录和限制。完成这些步骤,就能理解上层 Agent 为什么可以替换模型而保留工具循环。
练习一:两个不同供应商都支持同一种协议,它们必须共用一个 Provider 吗?参考答案:不必。Provider 管自己的目录和鉴权,内部可以复用相同 API 实现。
练习二:收到完整工具调用就说明文件已经修改了吗?参考答案:没有。那只是模型提出的调用,宿主要先校验并执行,实际结果另行返回。
练习三:为何切换模型后还要查看能力元数据?参考答案:统一接口没有抹去上下文、视觉、推理与协议限制,不同模型仍可能接受不同内容和选项。