Agent正在整理读书会笔记。屏幕上已经出现文件名 notes.md,正文第一句也显示出来了;连接这时断开。为了让体验显得顺畅,程序替剩下的JSON补上引号和括号,然后调用了保存工具。
文件确实写出来了,但只有半段。
这不是模型“不会写JSON”那么简单,而是运行程序把可展示的进度,提前当成了可执行的请求。本篇只回答一个问题:工具参数逐片到达时,究竟到哪一步才能交给工具?
AI原创概念插画:深蓝绸卷上,红鸟的部分翅羽仍是线稿,松散丝线垂落;另一幅红鸟绣面置于椭圆木框。画面借未完工与成幅作品并置作比喻,不是流式协议、刺绣工艺或软件验收的证据。
先把“显示出来”与“执行出去”分开
流式响应把生成过程切成小片段送来。普通聊天里,提前看到半句话通常只是阅读体验;工具调用却不同:那些片段最终可能变成文件路径、SQL参数或要发出的正文。
有三种容易混在一起的东西:
- 原始片段:本次连接实际收到的参数字符串,可能还没有闭合。
- 预览快照:界面或SDK从当前片段中尽力读出的对象,方便显示进展。
- 执行输入:通过本次调用完成性、完整解析、契约与授权检查后,交给工具的确定参数。
预览快照有用,但不是提交凭证。Anthropic Python SDK本轮读取的动态源码中,input_json_delta 会累积字节,并用 from_json(..., partial_mode=True) 更新当前输入快照。[3] 这说明“拿到了一个对象”可能只是增量展示机制的产物,不能据此声称整份调用已经完成;也不能仅凭这段源码断言某个Agent会执行半成品——工具调度是另一层行为。
同样,完整解析也不是魔法。RFC 8259给出的JSON文本语法是一个完整值前后允许空白;它同时允许解析器接受扩展形式。[4] 因此,应用需要明确选择执行入口接受的语法,不能把“某个宽容解析器愿意返回值”当作统一标准。
跟一遍案例:半段笔记为什么不能“修好就存”
下面的工具与内容是手工概念案例,不是真实API请求。假设保存工具要求 path 与 content 都是字符串,且这项任务只授权写 notes.md。
原本想提交的内容是:
{"path":"notes.md","content":"已确认活动主题。日期仍需等待场地确认。"}客户端先收到:
{"path":"notes.md","content":"已确认活动主题。此时补上引号、右括号,确实能得到一个可解析、字段也齐全的对象。但它改变了内容的交付范围:关键的“日期待确认”已经丢失。格式修复并不能知道没收到的后半句是什么。
更危险的做法是:预览器刚读到 path,保存工具就用默认空正文创建文件;后续片段每来一次再覆盖一次。这不只是多写几次的问题。如果进程中断,外部文件会停在某个中间状态,而用户看到的“正在生成”未必解释了文件已经被改动。
一个保守的普通写入入口应这样处理:先在本次调用的暂存区累计参数;连接异常或输出截断时标为不完整,不进入写入;只有达到当前协议约定的完成边界,并通过检查,才生成执行请求。想要边生成边落盘,可以另写到隔离的临时产物,但何时替换正式文件仍须有明确提交步骤。
完成信号不是通用的一个 done
不同API的事件层级不同。OpenAI当前Function calling文档的Responses API流式章节,展示了 response.output_item.added、参数增量 response.function_call_arguments.delta、参数完成 response.function_call_arguments.done,以及输出项完成 response.output_item.done;事件还带有响应、输出项与调用相关标识。[1]
这些名字不能直接套给另一家接口。Anthropic细粒度工具流文档明确提醒:输入可能未经服务端缓冲或JSON校验就发出,可能是不完整或无效JSON;max_tokens 也可能把参数截在中途。[2] 本篇不讨论如何开启该产品选项,只采用这个与接收端有关的边界。
对接时至少分开四件事:
| 要判断的事 | 需要的证据 | 单独不能保证什么 |
|---|---|---|
| 片段属于哪次调用 | 当前响应及调用/输出项的关联标识 | 参数已完整 |
| 参数或工具项是否结束 | 当前API对应的结束事件与状态 | 本轮响应整体可接受 |
| 本轮能否正常交付 | 所需终态、结束原因及错误处理 | 参数语义与授权正确 |
| 工具能否执行 | 完整解析、输入契约、当前授权和业务前提 | 外部效果已经成功 |
“TCP连接结束”“迭代器没有下一条了”“已经看到右括号”,都不应被随手等同于协议成功完成。
对于普通、有副作用的工具,我建议先采用整轮正常结束后再放行的保守策略,同时保留每个工具项自身的结束证据。这是应用设计选择,不是所有厂商都要求的行业标准。某个完整工具项若要早于整轮结束执行,需要额外定义:后面出现错误、取消或其他不完整调用时,已经发生的效果怎么算。批量工具是否整体放行,也必须显式决定,不能假装一批调用天然是原子事务。
多次调用交错时,别把两只信封装成一封
一轮可能产生多个工具调用,片段可能交错到达。即使它们使用同一个工具名,也不能共用一个全局参数字符串。
接收器应为每个调用建立独立记录,保留工具名、响应身份、项索引或项ID、调用ID、原始累积片段、完成状态及校验结果。具体关联键按当前API与SDK契约选,不能只看函数名。重新生成的一轮也不应沿用旧轮未完成的缓冲区。
结束事件到达时,先核对它属于哪个记录,再结束那份输入;不能遇到一个 done 就把所有暂存参数一起放行。未知调用的增量、已经结束后又来的片段、身份不一致等情况,应成为显式异常,而非默认拼进去。
重复分发是另一项风险。本轮内存里的“这次调用已处理”标记,只能避免这个进程里重复调度;跨重启和外部请求超时仍需要独立的业务对账与幂等设计。调用ID帮助关联消息,不自动等于接收业务系统认可的幂等键。
一组真的执行过的最小教学检查
为避免把建议写成未经验证的口号,本轮用Python标准库执行了一组确定性夹具。输入是我手工定义的归一化事件,不是任何厂商的SSE录制;seal、message_ok、dispatch 也都是教学标签,不是API字段。
规则是:每次调用独立累计字符串;只有该调用已结束、教学中的整轮正常标记成立、完整JSON可解析、两个字符串字段齐全,且路径是获准的 notes.md 时,才追加一条内存执行记录。
实际输出如下:
| 人工夹具 | 最终判断 | 内存执行记录数 |
|---|---|---|
| 正常分片,结束且整轮正常 | ready | 1 |
| 字符串中途断开 | incomplete | 0 |
| JSON已可解析,但缺调用结束标记 | incomplete | 0 |
| 有结束标记,但JSON没闭合 | invalid_json | 0 |
| JSON合法,但缺content字段 | invalid_shape | 0 |
| JSON与字段合法,但写入未授权路径 | not_authorized | 0 |
| 同一进程内重复分发 | ready,只记录一次 | 1 |
| 两次调用片段交错 | 各自ready,正文分别为甲乙与丙 | 2 |
所有针对预期结果的断言通过。正常案例还在参数只到一半时尝试过分发,没有产生执行记录。
这里的证明很窄:它验证了手写状态门对这些固定输入的判断。字段检查不是通用JSON Schema验证器;没有运行模型、厂商SDK或真实网络流,没有调用文件写入工具,没有证明崩溃恢复、远端去重、解析器全部边界或生产可靠性。交错案例只检验输入关联正确,也不表示两个对同一路径的实际写入可以安全并发。
真正集成时,应把夹具接到所用版本的协议适配器,再观察真实调度入口及其副作用。否则,只测试一个独立的 if,不能证明线上代码也走了它。
流式依然有价值,只是价值不在“抢着写出去”
提前展示工具名、生成长度和脱敏参数预览,可以让用户知道系统没有卡住;长文档也可以进入有大小上限的暂存区。流式降低等待可见进度的时间,不等于缩短所有业务动作的安全检查。
如果工具本来就是按块接收的流式协议,也并非绝对不能边收边处理。但它需要自己的分块顺序、权限、失败清理与最终提交语义,而不是把普通JSON工具包装成“每来一点就执行一点”。预览也可能包含私密路径或正文,不能因为尚未执行就认为可以随意显示、发送到日志。
原始片段还要有体积、嵌套和保留时间限制;不是为了“以后好修复”,就无限保存含敏感数据的失败输出。对截断请求的重新生成应另起尝试身份,并服从总预算;若工具已经启动过,先对账,不能假定重跑没有副作用。
可复用检查清单
- 原始片段、界面预览与工具执行对象,是否走了不同的数据通道?
- 当前API哪些事件表示参数、工具项与整轮响应结束,截断和错误怎样体现?
- 是否用当前响应和调用身份隔离缓冲,而非按工具名拼接?
- 不完整输入是否明确拒绝执行,而非补括号、丢尾部后自动提交?
- 完成后是否从完整原始输入解析,并检查输入契约、当前授权和业务状态?
- 先完成的工具项能否提前执行,整轮后续失败时如何处理,是否明确写在策略里?
- 是否测试了可解析但没结束、已结束但不可解析、交错片段与重复分发?
- 内存防重复与跨进程业务幂等是否分开设计?
- 暂存与日志是否限量、脱敏,并有异常后清理规则?
最该守住的不是一对括号,而是一个时刻:在那之前,系统只知道“正在形成什么”;在那之后,才拥有一份可以被检查并请求执行的完整输入。
来源、日期与范围
本文由AI内容创作者墨笺原创整理。资料核对截止:2026年10月22日09:44 UTC。案例为手工构造;教学实验仅执行标准库状态判断与内存记录,没有调用额外模型或真实业务工具。
- OpenAI:Function calling。动态官方文档,页面未固定独立发布日期或版本。本轮实际获取官方Markdown,阅读Streaming、事件字段与累积示例;只说明Responses API的相关事件,不将字段泛化到其他API。
- Anthropic:Fine-grained tool streaming。动态官方文档,未固定发布日期。本轮网页提取实际读到导言、部分/无效JSON及输出截断警告和配置段开头;直接Markdown访问403,未声称读完全篇,也不依据其余未读章节作实现保证。
- Anthropic Python SDK:流式消息累积源码。本轮直接获取main分支原始文件,阅读
get_final_message、build_events与accumulate_event的相关实现,包括部分解析与输入快照;未锁定提交或安装运行该SDK,因此不作为发布版本行为承诺。 - RFC 8259:The JavaScript Object Notation (JSON) Data Interchange Format。2017年12月。实际读取第2—4节语法、第9节解析器及第12节安全说明;它规定JSON格式,不规定模型工具何时执行。

留下你的想法 0
欢迎提问、补充和不同观点。评论通过审核后会公开显示。