HARNESS / 代理工程下载 EPUB ↓
10 / 32

第九章:消息与流式协议,先完整再执行

9.1 三种数据不能混成一段文字

用户输入描述目标,助手文字解释过程,工具调用请求宿主执行结构化动作。即使它们最终都能显示成文本,也应在协议里保留不同类型。否则宿主可能需要猜测“这段文字是不是命令”,界面也难以可靠区分建议与已执行动作。

工具调用还需要稳定 call ID。工具结果靠这个 ID 关联请求,而不是靠“第二个读取工具”这样的显示顺序。模型一轮请求多个工具、某个工具很慢、恢复时补结果,都需要这项身份信息。

9.2 流的片段不是完整消息

网络可能把工具参数拆成多段:第一段是 {"path":,第二段是 "hello.txt"}。收到第一段时,程序可以显示进度,但不能执行读取。即使某一时刻拼出的 JSON 能解析,也还需要完整结束标记与正常停止原因。

Pi 在响应被长度限制截断时,不执行该消息中的工具调用,而是生成失败结果。它为“参数可解析”与“响应完整可执行”之间的区别提供了具体证据。Pi · agent-loop.ts L266–280

随书 decode_stream() 只接受文字增量、工具参数增量和正常结束标记。缺少结束、异常终止、未知事件、工具名中途改变或 JSON 解析失败都会报错。执行器只能消费最终 Reply,不能订阅参数片段后提前产生副作用。

9.3 定义失败怎样到达调用方

有些接口通过异常表示失败,有些把失败编码成流中的结束消息。Pi 的 StreamFn 定义前注释描述了其特定契约:请求或运行时失败应在返回流中表达,而不是任意抛出。调用者必须按实际接口理解,不能把它与本书实验的异常协议混用。Pi · types.ts L31–45

一个有用的协议说明至少覆盖:流从哪里开始、怎样结束、哪些事件允许重复、断开后的最后片段是否有效,以及错误时是否存在可用于后续历史的助手消息。没有这些定义,重试就可能产生重复文本、重复调用或无法配对的结果。

9.4 协议演进要有接纳规则

新增事件可以对旧客户端无害,也可能改变执行含义。若只是可忽略的显示元数据,可以明确兼容策略;若改变工具参数、权限或结果状态,就应升级契约并验证。静默忽略所有未知事件看似宽容,却可能掩盖关键控制信息。

本书实验采用严格失败,以便学习者看清边界。生产系统可以设计更细的兼容层,但要明确每一类事件能否忽略。Codex 的协议枚举与前后端事件传递提醒我们:UI 不应仅靠解析终端显示文字理解核心状态。Codex · protocol.rs L1467–1481

练习:模型连续发出两段同 ID 的工具参数,是否代表调用了两次?不一定,它可能是一个调用的片段。另一个轮次重用已完成调用的 ID,则属于不同问题,需要单独的重复身份检查。

非官方学习读物 · 固定源码快照 · 原始代码保留各自许可。GitHub · 源码依据 · 实验代码