调用循环
连接执行一次,调用可能走多轮。本页走一遍这条循环:模型在其中出场三次,宿主管执行与终止,失败也须按规则回传给模型。
1连接:握手与发现,只执行一次
连接是一次性动作:握手确认双方可用同一套协议,发现取回工具清单。产物是一份工具清单。
| 步 | 做什么 | 失败时 |
| 1 | 取出这台服务器的连接信息(地址与令牌) | 不在可用范围内:按不存在处理 |
| 2 | 拼接端点地址 | — |
| 3 | initialize 握手,带上令牌 | 停在未连接,提示检查地址与令牌 |
| 4 | tools/list 取回工具清单 | 同上:停在未连接 |
| 5 | 用本次的清单整体替换旧的,标记为已连接 | 写入失败同样停在未连接 |
第 5 步是替换而非追加:先清除旧清单,再写入本次的新清单。因此本地清单始终等于「最近一次成功的发现结果」,远端已删除的工具不会留下残留条目。
五步里只有 3、4 两步真的走出本机;它们失败时清单与连接状态都不更新——不记住一次失败的发现结果。
同一件事的两个视角:上面看宿主内部的五步,这张看跨进程的往返——只有 ③④ 真的离开本机。
2调用循环:模型出场三次
在通用做法中,调用不是一个固定步骤,而是夹在模型两轮生成之间的一个循环。
① 提问 + 工具声明一起发出
宿主把候选集内的工具转换为工具声明,连同本次提问一起发给模型。声明中的三项都来自发现结果。
② 模型回结构化的调用请求
模型并非输出一句非结构化的调用意图,而是输出一段结构化内容:工具名加参数对象。它可能一次提出多个调用。
③ 宿主校验后执行
宿主按那份输入结构校验参数,通过后才真正发报文、带令牌、等结果。模型始终不接触地址与令牌——它只是提议者。
④ 结果作为工具消息回传,再生成
结果以「工具消息」这一角色追加进消息序列(messages),再次请求模型。模型可能还需要一个工具,于是回到 ②;不再需要,则输出回答。
调用不是一个固定步骤,而是一个循环:只要模型还要工具,中间两步就会再来一轮;模型决定「要不要」,宿主决定「能不能」。
模型出场三次:收到工具声明、提出调用、取得结果后再组织回答。它决定「要不要调」,但从不自己发请求。
同一份输入结构被使用两次:模型据此填写,宿主据此校验。这正是「工具声明必须来自发现结果」的原因——手写一份,模型与校验器就会各对一套不同的结构,错误会推迟到调用时才暴露。
3循环如何终止
循环必须有明确的终止条件,否则一次提问可能无限往返下去。
| 终止条件 | 什么时候触发 | 触发之后 |
| 模型不再请求工具 | 它认为已有信息足够作答 | 正常输出回答 |
| 达到轮次上限 | 往返次数用完 | 停止调用,让模型用已有信息作答 |
| 达到调用次数或时间上限 | 单次会话内的累计用量用完 | 同上;一次提问里的并发调用也要计入 |
| 工具返回让它自行收敛 | 结果已经足够,或反复失败 | 模型自行决定不再调用 |
上限不是优化项,而是必需品:模型可能陷入「调了不满意、再调一次」的循环,也可能被一段诱导文本反复带动。超限后的处理是优雅降级(graceful degradation)而非报错——停止调用,让模型基于已有信息作答,用户得到的是一段回答而不是一次失败。
轮次与累计次数是两个不同的闸门:轮次管的是「一条链能走多深」,累计次数管的是「一次会话总共能做多少动作」。前者防死循环,后者防成本失控,两者都要有。
4失败是局部失败:错误也须回传给模型
工具未调通,不等于本次对话无法继续。关键在于把失败如实告知模型,由它自行决定后续策略。
工具自报错误、超时、连接失败——这些都应当作为一条工具消息回传给模型,并带上「失败了、为什么」。模型据此可以换个参数重试、换一个工具,或者如实告知用户「这一步未查到」。反过来,若将失败静默丢弃,模型会误以为工具返回了空结果,于是自行编造一段内容填上。
无论成败,工具失败都不应中断整段对话:检索与生成照常进行。工具是增益,不是主路径。
| 结果 | 什么情况 | 是否记录 | 记录的内容 |
| 成功 | 服务器可达,工具执行成功 | 留 | 返回内容的一段摘要 |
| 工具出错 | 服务器可达但工具报错、超时、返回异常 | 留 | 错误原因 |
| 宿主拒绝 | 服务器不在可用范围,或未连接 | 视能否归属而定 | 「未连接」一类固定说明 |
两种「拒绝」,一条记录、一条不记录,差别在于能否归属到某台服务器。
记录的前提是「本次调用能归属到某台服务器」:服务器已不存在时,这次失败只体现在本次应答的调用清单中,落不进一条独立记录。因此「有记录」不等于「记下了一切」——它记录的是可被归属的动作。