
一个工具已经成功返回,Agent 却又发起一轮调用,这不一定是重复执行。模型可能还要根据结果继续工作。反过来,模型给出空文本,也不代表程序卡住了。循环要看的是当前回合是否结束,以及结束原因是什么;文本有没有内容只是辅助信息。
这里把常见的控制逻辑缩成一个本地桩程序:收到工具调用就执行工具,再把结果送回模型;收到终止信号才退出。模型本身没有参与,输出只验证循环分支,不代表任何特定 API 的字段格式或模型行为。
Q:工具回了结果,为什么不能直接结束?
工具的结果是模型下一轮的输入,并不是用户最终拿到的答案。比如检索工具返回了几条记录,模型还需要筛选并组织回应。若工具执行结束就退出,用户看到的可能只是原始结果;若把工具结果直接当成最终答案,后续的判断和解释也被跳过。
循环的边界通常落在一轮模型响应上。若响应包含待执行的工具调用,程序执行调用并把结果追加回对话,再请求下一轮。若响应明确表示回合结束,程序才把最终内容交给调用方。这里的「明确」依赖所用接口定义,不能把某一家 SDK 的状态名硬套到所有服务。
Q:空文本能作为结束条件吗?
不稳。工具调用回合可能没有面向用户的文本,只有调用信息;文本为空不等于没有工作。反过来,一段看起来完整的自然语言也未必能证明底层回合已经结束,尤其是响应结构仍带有需要执行的调用时。
更稳妥的顺序是先读取回合状态,再按状态处理内容。调用请求进入执行分支,结束状态进入返回分支,未知状态进入错误处理。不要只检查文本长度,也不要以「有没有工具调用」作为唯一的成功判断。调用字段可能损坏或缺失,校验失败应中止并报告,而不是悄悄把坏响应当成正常终点。
Q:步数上限和模型的结束信号有什么区别?
结束信号描述模型这轮表达的意图;步数上限保护程序免于无休止地继续。两者不能互相替代。达到上限时,任务可能尚未完成,结果应标记为受限或失败,不能伪装成正常完成。若只依赖模型说「结束」,异常响应或循环调用就可能一直占用资源。
本地桩程序固定了三种轨迹:一次工具调用后收到停止信号;没有工具调用但内容为空,回合仍按停止信号退出;连续请求工具则被独立的步数上限截断。日志原样如下。
tool call returned: calls=2 reason=model_stop
empty text but no tool call: calls=1 reason=model_stop
tool keeps requesting work: calls=2 reason=step_cap
这个结果只说明控制分支各自有效。实际项目还要处理工具执行失败或超时;这些不在桩程序的验证范围内。我倾向于把「正常完成」和「因为达到上限而停止」记成两种结果,否则监控和重试策略会把未完成任务误算成成功。
Q:到达上限后应该返回什么?
别把最后一条工具结果包成答案。可以返回明确的中止状态,保留已完成的步骤供诊断,并由上层决定是否重试或交给人工处理。对于有副作用的工具,重试之前还得判断调用是否已经执行成功,避免同一动作重复提交。
结束条件写得清楚,循环才不会把「停止运行」和「完成任务」混为一谈。步数上限是保险丝,不是完成证明。上限值应结合单步成本设定,日志里也要留下触发原因。