10|模型输出怎样变成真实工具调用:协议与执行边界
本章目录
本章预备知识
网络不是一次性送来完整答案。字节、文本片段、结构化事件、工具调用对象是不同边界;仅看到一个大括号闭合,不等于一次合法工具调用已经完成。
本章各题列出所需基础。可先完成相应基础课,也可通过自检确认掌握程度。
进入深读前的小检查:把一个包含中文和JSON的消息切成不完整字节片段,想清楚应该在哪里缓冲、在哪里解析、在哪里才允许执行。
术语带虚线时可点击速查。公式中的字母先看定义;不要用猜缩写替代推导。
本章的JSON是简化教学对象;产品API的具体字段以其文档为准。重点是从字节、事件、调用项到业务副作用的不同完成语义。
沿着对象与状态理解本章
先区分收到一段网络字节、拼成一个SSE事件、得到完整JSON以及确认工具调用完成。只有最后符合协议的结构和权限检查通过后才考虑执行。四层若混在一起,就可能把半段参数当成真实命令。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
字符完整、事件完整、工具参数完整是三道关
为什么看到半段JSON时绝对不能执行?
网络像分批送到的纸条,分割点不遵守汉字或JSON边界。接收端先重组字节和字符,再按流协议重组事件,最后解析结构并确认该输出是否表示完整行动。花括号配平不够,因为字符串内部也可能有花括号。
输入与适用条件:真实编码了一个小SSE文本事件,故意把“爱”的三个UTF-8字节拆成两段。示例并不使用厂商特定的事件命名。
1 / 4 · 收到第一段字节
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
网络片段可以停在一个汉字的UTF-8编码中间。现在只有“爱”的第一个字节,不能把每个网络片段独立按完整字符串解码。
字节缓冲默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
让网络分片方式不影响语义与执行次数。
怎样实现
增量解码→组装事件→解析JSON→检查完成状态→按类型分派。
可能的代价与误解
提前执行、重复事件未去重、调用ID错配,都可能把一次动作变成多次副作用。
如何判断是否做对
把同一字节串在每个切分点拆开,解析后的事件都应一致。
Q037|Function Calling到底是谁调用函数?模型怎样知道工具参数?
核心回答
工具定义将名称、功能说明和参数结构暴露给模型;模型生成调用意图与参数,应用或受托执行环境才真正调用工具。模型不是直接持有你的Python函数指针。工具结果再次进入上下文,才闭合信息反馈。[TOOLS]
逐层转换一次read_file
开发者注册read_file(path,start_line,end_line),为path注明只接受仓库内相对路径,为行号注明一基、包含端点。Harness把支持的schema和描述送到模型,模型可能返回:
{"type":"function_call","name":"read_file","call_id":"c7",
"arguments":"{\"path\":\"src/filter.py\",\"start_line\":40,\"end_line\":80}"}
执行器先确认调用项完整,再解析arguments字符串;验证字段类型、范围、工具存在性;解析实际路径并检查授权;调用文件读取实现;把结果封装为与c7关联的工具输出。返回至少要让模型区分“40—80行内容”“文件不存在”“路径被拒绝”“结果被截断”。
工具名称和描述会影响选择,但工具能力由实现决定。描述写着“可读取任何文件”不应扩大进程权限;相反,运行时授权允许一个目录也不代表模型自然知道哪一个工具可用。发现、选择、执行是三个环节。
模型输出一个函数名,到底谁执行了它
输入与条件。工具说明为 read_file(path)。模型响应中包含函数名read_file、参数 {"path":"config.json"} 和call_id=c1。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1接收输出 | 宿主得到一条结构化调用项目 | JSON本身不会打开文件。 |
| 2校验 | 检查工具已注册、参数为路径字符串、路径在许可范围内 | 任何一项不满足都不执行。 |
| 3分派 | 宿主在注册表查到read_file实现 | 真正调用操作系统读文件。 |
| 4返回观察 | 构造call_id=c1对应的结果 | 保证后续模型能把结果归到原请求。 |
| 5继续生成 | 把工具结果放入新一轮输入 | 模型再根据内容回答,不是工具执行结果自动变成模型结论。 |
把过程连起来。模型负责提出动作,宿主负责实施、权限与反馈;两者之间需要可核对的边界。
边界与反例。工具调用字段与角色随API而异,c1是教学ID。不要把示意JSON直接当作所有产品的完整请求体。
依据与适用范围:[TOOLS]。示例输入和场景由本书明确设定。
追问:为什么工具schema本身也消耗上下文?
模型要理解名称、字段和说明,它们必须以某种模型可用表示进入请求。工具多且描述冗长会增加输入负担和选择难度。按需发现能减少默认工具集,但也引入发现失误;应测必需工具召回与不必要加载,而不是无条件把所有工具塞进去。
追问:工具结果只返回一个字符串够不够?
简单情况可以,但必须有清晰边界。把错误和成功都写成自由文本,模型容易误读;结构化字段可表达status、path、version、truncated、next_range等,再配合可读内容。结构化不是越复杂越好,字段应服务实际后续决策。
追问:Toolformer等训练研究与API Function Calling有什么关系?
前者研究模型怎样学习在文本处理中使用工具等能力;后者是模型与应用交换调用意图的工程接口。支持接口不等于某个模型用了特定论文方法训练,也不保证它在你的工具集合中选择准确。训练来源未公开时不能据接口反推训练配方。
Q038|流式输出为什么不能收到半段JSON就执行?SSE具体怎么解析?
核心回答
网络chunk、UTF-8字符、SSE事件、JSON对象和模型调用项边界不同。应逐层增量解析,累积同一调用的参数,收到明确完成状态且通过验证后再执行。连接结束不一定意味着生成正常完成。[STREAM][SSE]
一段中文工具参数怎样被拆开
假设参数包含"path":"资料/说明.md"。中文字符由多个UTF-8字节组成,网络可能在字符中间切块。若每块独立decode,可能报错或插入替换字符。应使用增量UTF-8解码器保留未完成字节。
字节转成文本后,还要按SSE规则累计行。多个data:行组成同一个事件数据,空行才分派事件;注释心跳不等于业务数据。事件内JSON可能描述“某个调用参数新增片段”,而不是完整调用。按item标识、调用索引或接口约定把delta附加到正确缓冲,不能把多个并发工具的片段混在一起。
假设事件依次到达:
item_created: c7 / read_file
arguments_delta(c7): {"path":"src/
arguments_delta(c7): filter.py","start_line":40,
arguments_delta(c7): "end_line":80}
arguments_done(c7)
response_completed
直到参数完成,才有可验证对象。arguments_done、输出item完成和整个response完成是不同事件;是否可提前执行完整工具项要看产品协议及执行策略。最保守教学实现等待该调用及必要响应状态完成,不对猜测补全后的JSON产生副作用。
中文字符被拆成三个网络片段,为什么不能逐片decode
输入与条件。字符“我”的UTF-8字节为E6 88 91。网络先收到E6,再收到88,最后收到91;随后才收到SSE事件末尾空行。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1错误做法 | 每片独立按UTF-8解码 | 单个E6不是完整字符,会报错或插入替代字符。 |
| 2增量解码 | 解码器缓存未完整字节 | 第三片到达后输出“我”。 |
| 3事件缓存 | 把已解码文本继续积累到空行 | 网络分片结束不等于SSE事件结束。 |
| 4参数缓存 | 一个工具参数对象可能分成多个delta | 完整收齐并验证后才能交给执行器。 |
| 5关闭反例 | 连接断开时参数仍为 {"path": | 不能为了“尽快执行”猜一个文件名补齐。 |
把过程连起来。字节流、文本行、SSE事件、JSON对象和工具调用是不同层次的完成边界。
边界与反例。随书解析程序只覆盖声明的LF教学子集;完整EventSource还要处理CRLF、字段规则、重连等情况。
追问:如果完整JSON已经出现,但后面还没结束呢?
语法上闭合不等于语义最终确认。模型或协议可能继续发其他字段、撤销/失败信息,或者多调用还未齐全。执行时机要依事件契约,而不是用括号计数猜。对于只读预取可设计可撤销优化,但写操作不应投机执行。
追问:断线后拼接新的生成结果能恢复吗?
不一定。真正的事件续传需要稳定响应身份和游标/序列等协议支持;重新发起生成可能得到不同参数。不能把旧半段JSON和新答案直接拼接。应明确恢复旧响应、重发幂等请求还是新开生成,并分别处理执行去重。
追问:怎么测试流解析器不是只在正常chunk上有效?
把同一字节流在每个可能位置切分,尤其切在UTF-8、转义符、换行和JSON边界;注入心跳、重复事件、交错工具项、截断与错误结束;检查最终对象一致、未完成调用从不执行。测试目标是协议不变量,而不是几个固定样例字符串。
Q039|并行工具结果乱序返回,如何配对?哪些错误可以交给模型修?
核心回答
调用与结果应按稳定调用身份配对,不按返回顺序或工具名称。可恢复的参数/业务错误应提供精确反馈;权限拒绝、未知副作用和协议损坏不能简单交给模型无限重试。
看同名工具的两次调用
模型同时调用read_file(a.py)和read_file(b.py),ID分别c1、c2。b.py先返回。若按“第一个结果对应第一个调用”,模型会把B文件当A文件,后续补丁可能误改。正确方式保存call_id→name,args,status,result,按协议组装输出;同一工具名不能唯一标识调用。
状态可以区分created、validated、running、succeeded、failed、unknown。unknown表示客户端无法确认副作用是否发生,不是失败的别名。例如远程创建工单超时,不能立刻用新ID再建一个。需要查询或幂等恢复。
错误返回应保留可采取的动作:FILE_NOT_FOUND可重新定位路径,INVALID_RANGE可调整范围,PERMISSION_DENIED要求改变授权或选择允许路径,VERSION_CONFLICT需要重读新版本。把所有错误包装为“try again”会让模型在错误空间盲目搜索。
两个工具并行完成,后到的结果怎么归属
输入与条件。同轮发出c1=读取A,c2=读取B。B耗时20ms,A耗时100ms。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1登记 | 保存c1→A、c2→B | 不要只按数组位置猜对应关系。 |
| 2先到B | 结果标call_id=c2 | 即使它第一个返回,也归到B。 |
| 3后到A | 结果标call_id=c1 | 合并时顺序可以调整,身份不能错。 |
| 4局部失败 | A超时、B成功 | 分别记录状态,不把整批都标成功或都丢弃。 |
| 5依赖反例 | 第二个工具必须使用第一个新写入的版本 | 这不是独立并行,应先等待依赖完成。 |
把过程连起来。并行节省墙钟时间的前提是操作独立;结果关联依靠身份而不是完成顺序。
边界与反例。20/100ms是构造值。取消或超时不一定意味着外部动作未发生,有副作用时还需核查。
依据与适用范围:[TOOLS]。示例输入和场景由本书明确设定。
追问:多个工具调用是否天然可并行?
不。读取两个独立文件通常可以;先创建分支再切换、先写文件再测试有依赖;两个写操作可能争用同一资源。工具是否只读只能提供部分信息,具体参数和共享状态决定冲突。Harness要有并发策略,不能把模型一次输出多调用当作安全证明。
追问:取消后工具返回成功,该相信哪一个?
取消请求只是要求停止,工具可能已完成或无法中断。需要以实际副作用和协议状态为准,记录cancel_requested与最终结果,而不是把两者合并成一个布尔值。尤其远程发布/付款不能因本地停止等待就视为回滚。
追问:向模型返回完整堆栈是否越详细越好?
不一定。完整堆栈可能泄露密钥、内部路径,或淹没关键错误。保留错误类别、相关上下文、是否可重试、证据引用;原始日志安全落地,需要时再按范围取回。裁剪应保留错误与结尾摘要,不能只保留日志前几百字。
Q040|Structured Outputs怎样约束生成?为什么符合Schema仍不等于可执行?
核心回答
结构化输出机制限制生成满足受支持schema的格式与类型,常通过受约束解码等方式实现。它减少解析失败,但不证明字段事实真实、业务条件满足或执行已授权。还要处理拒绝、截断、未完成及接口支持范围。[STRUCTURED]
把四层验证拆开
第一层语法:JSON能否解析。第二层结构:是否存在必需字段、类型和枚举符合schema。第三层业务:金额是否合理、日期是否满足规则、文件版本是否一致。第四层授权:当前身份是否允许对这个具体资源执行这个动作。
例如{"action":"delete","path":"/data/important"}完全符合action enum[read,delete],path string,但仍可能越权。即便schema只允许“仓库相对路径”,../、符号链接和执行时路径变化仍需真实路径校验。模型输出中的approved:true不能作为审批依据。
受约束解码的直觉是:根据当前输出前缀和语法状态,屏蔽不允许的下一Token,再在允许集合里选择。真实schema支持、编译方式及错误行为由实现决定,不应编造所有厂商内部使用相同语法引擎。对于数值与现实关系,单靠语法状态无法查询数据库核验。
JSON格式完全正确,为什么仍然不能执行
输入与条件。输出符合schema:{"path":"../secrets.txt","timeout":-1},两个字段类型分别为字符串和整数。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1结构层 | 字段存在、类型正确 | 结构约束通过。 |
| 2业务层 | timeout要求1—60 | -1不满足业务域,不能接受。 |
| 3权限层 | 规范化后的路径越过允许目录 | 即使字段是字符串也不能读取。 |
| 4版本层 | 操作针对v7,但当前已v8 | 需要重读或拒绝过期修改。 |
| 5反馈层 | 返回具体可修复错误 | 让模型修改提议,而不是执行后才解释风险。 |
把过程连起来。结构正确、业务正确、授权正确和版本正确是四项独立要求。
边界与反例。支持minimum/maximum的schema可提前拦数值范围,但任意路径权限、当前版本和外部事实仍需宿主判断。
依据与适用范围:[TOOLS]。示例输入和场景由本书明确设定。
追问:为什么JSON Mode与Schema约束不是同一层保证?
“生成有效JSON”只限定语法;schema还可限定字段、类型、枚举等。一个有效JSON数组可能根本不是所需对象。即便用schema,应用也应明确处理兼容版本和不支持约束,而不是假设所有JSON Schema关键词都受支持。
追问:可以删除应用层验证来省代码吗?
不应删除权限和业务验证。模型服务是一个输入来源,可能发生错误、版本变化或调用被绕过。确定性边界通常可以保持简洁,比如一次类型解析、路径范围检查、版本条件和授权检查,不必因此构建复杂框架。
追问:结构化答案为什么有时更容易“编满字段”?
若schema强制所有字段必填,却没有unknown/null/证据不足状态,模型可能被迫填值。设计时应允许诚实表达缺失,并明确哪些字段可空、需要何种来源。格式完整度与知识完整度不能混为一谈。