智能体工具调用抽象失效最明显的标志是:追踪记录(trace)显示该步骤已标记为完成,而下游系统却显示什么都没发生。模型调用了一个工具,收到了一个任务 ID 返回值,将该任务 ID 视为答案,然后继续执行。三分钟后,实际工作要么在无人监听的情况下成功,要么失败并产生一条记录在无人查看的日志中的错误。用户看到的是一份自信的总结;而操作队列看到的却是一个搁浅的任务。
这就是函数调用(function-calling)抽象悄然允许的失效模式。JSON Schema 描述了参数和返回类型,但它们无法区分“此工具返回一个结果”与“此工具返回一个操作回执,你稍后需要查询其结果”。模型对两者一视同仁,因为在规划器(planner)看来,它们看起来是一样的——都是一个带有非错误负载的成功工具调用。
基准测试数据不容乐观。在 Robotouille(一个衡量智能体能否将动作与耗时操作交替进行的异步规划基准测试)上,基于 GPT-4o 的 ReAct 模式在同步变体中得分为 47%,而在异步变体中仅为 11%。该架构在处理异步任务时并非只是略逊一筹——它彻底崩溃了,因为每一次异步工具调用都是规划器将“确认收到”误认为“执行完成”的机会。
类型系统存在缺陷
函数调用 Schema 定义了参数、返回形状和一行描述。但它没有定义的是时间合约(temporal contract):此调用是返回结果,还是返回结果的承诺?
一个返回 {"status": "sent"} 的 send_email 工具,在 JSON 层面看起来与一个返回 {"job_id": "abc123"} 的 start_video_render 工具完全相同。两者都产出负载,且都没有报错。规划器在类型层面没有收到任何信号来区分哪个已经完成,而哪个才刚刚开始。工具作者会写下诸如“启动渲染任务并返回任务 ID”之类的描述——但这种描述只是系统提示词中的一句话,而模型在其中需要同时处理数十个工具;在运行时,模型会将这两类调用归为同一个思维范畴:“工具调用成功,推进计划”。
新的 MCP 规范(2025-11-25 修订版)通过将任务(Tasks)添加为独立的原子原语(primitive)来承认这一差距——任务是一个持久化状态机,具有 working(进行中)、input_required(需要输入)、completed(已完成)、failed(失败)和 cancelled(已取消)等明确状态。重点不在于状态名称,而在于异步工作具有一种同步工作所不具备的“性质”(kind),将其放在不同的运行路径上可以防止规划器混淆两者。Bedrock AgentCore 的运行时通过显式的 add_async_task 和 complete_async_task 调用实现了同样的隔离,SDK 使用这些调用来独立于模型的推理循环跟踪任务并管理状态轮询。
如果你的函数调用层将每个工具都视为同步的“返回答案”调用,那么你就是用一种类型来描述两种现象。当你第一次发布长耗时工具时,你就发布了一个在完成情况上撒谎的规划器。
未能结合实际工作负载设定的轮询循环预算
意识到异步情况的团队通常会通过给智能体一个 check_status 工具并信任规划器会不断调用它直到工作完成来修补这个问题。这在演示中可行,但在生产环境中会因为一个特定原因而崩溃:智能体的外层循环预算——最大轮次、最大 Token 数、最大实际耗时——是由考虑成本的人设定的,而不是由考虑实际操作耗时的人设定的。
典型的循环预算是 20 到 30 个轮次,总执行时间为几百秒。而典型的长耗时工具是视频渲染、大文件转录、多步配置任务或 ETL 流水线。这些操作的典型持续时间通常为几分钟。而智能体的“轮询并等待”循环在约 90 秒后就会放弃,因为轮次预算只允许这么多。
当循环预算在任务完成前耗尽时,智能体会报告什么?几乎总是:智能体会合成一个结果。虚假状态报告——模型引用来自早期 check_status 调用的任务状态,而不是发起新的调用。过早采集——模型尝试根据“任务已进入队列”拼凑出最终答案,因为这是它能获得的最新观测结果。ID 截断——在上下文压力下,模型缩写或重新格式化任务 ID,导致下一次 check_status 调用因查询字符串损坏而失败。
用户看到了一个连贯的答案。操作可能仍在运行。智能体产出的结果与它实际等待后的结果无法区分。这比超时错误更糟糕,因为至少超时是诚实的。
同步与异步工具:披着相同 JSON Schema 外衣的不同抽象
陷阱在于传输格式(wire format)让你假装它们是相同的。这两种抽象实际上在每一个关键维度上都存在差异:
完成语义(Completion semantics) 。同步:响应即结果。异步:响应表示工作已被受理。
失效模式(Failure modes) 。同步:错误随响应返回。异步:错误异步到达,且调用者必须知道订阅哪个频道。
取消(Cancellation) 。同步:通过停止等待来取消。异步:通过发送显式取消请求并处理由此产生的竞态条件。
幂等性(Idempotency) 。同步:使用相同输入重试。异步:使用相同幂等键重试,服务器返回现有任务 ID 而非启动第二个作业。
背压(Backpressure) 。同步:调用者阻塞直到调用返回。异步:在调用者与结果之间存在队列、配额或速率限制,调用者必须与之协商。
如果规划器的思维模型是“工具调用返回结果”,那么它在处理异步工具时会步步出错。它会通过重新提交任务来重试,并因为没想过生成幂等键而导致任务重复。它会将非终态视为终态。它通过放弃对话而不是发送显式取消指令来取消任务,导致工作节点在过时的任务上白白消耗算力。
修复方法不是让规划器更智能地对待每个工具。而是在协议层赋予异步工具不同的形态,使规划器从一开始就无法应用错误的思维模型。MCP Tasks 的设计通过要求双方在初始化期间按请求类型声明异步支持来实现这一点。工具选择加入任务增强模式,其他工具保持同步。结果是,规划器预期为同步的请求不会在其背后悄悄变成异步,而规划器作为任务提交的请求也不会被误认为同步返回。
处理规划器不应承担之责的 Completion Broker 即使有显式的异步类型,智能体的推理循环也不是承担轮询、超时管理和重试逻辑的好地方。每一枚消耗在轮询上的 token 都是在浪费推理所需的 token,而且每一次状态检查(check-status)调用都意味着模型要经历一轮完整的往返。一个 30 轮的循环,如果花了 20 轮在问“到了吗?”,那么这个规划器实际上是在做打杂的工作。
填补这一鸿沟的模式是 Completion Broker —— 它是智能体与异步工具之间的一个层,负责处理模型不应参与推理的操作性问题。Broker 接受任务提交,以合理的频率(通常带有指数退避)轮询底层服务,执行超时限制,重试失败的检查,并仅向智能体呈现三种结果:成功完结并返回结果、失败并说明原因、或带有句柄(handle)的待定状态以便后续跟进。智能体的循环只看到一个工具调用返回了这三种状态之一。规划器不再需要在自回归采样器内部模拟分布式系统的原语。
“带有句柄的待定状态”是大多数实现中会忽略的情况,也是在生产环境中最重要的情况。实际操作的时间有时会超过任何合理的轮询预算。正确的做法不是通过伪造结果来欺骗用户,也不是通过声称操作失败来掩盖问题,而是诚实地呈现不确定性:工作仍在进行中,这是句柄,你稍后可以询问,系统在完成后会发出通知。一个将“进行中”作为一等公民状态的 UX,才不会强迫智能体陷入“幻觉结果”的困境。
规划器无法表达的 Happens-Before 关系 最困难的情况是输出结果存在因果依赖的异步工具。智能体启动了三个任务:一个转录任务,一个消耗转录内容的翻译任务,以及一个消耗翻译内容的摘要任务。规划器在异步边界之间没有内置的“Happens-Before”概念。它可能会将它们串行化,在启动下一个任务之前阻塞等待每个任务的完结,从而丢失所有并行性。或者它可能会并行启动它们,并将仍处于挂起状态的任务 ID 喂给下一个工具的输入,从而产生一个针对占位符的翻译。或者最常见的情况是,它在单个追踪(trace)中前后不一地混用这两种策略。
像 LLMCompiler 这样基于规划器的智能体系统通过在执行前构建显式的任务 DAG(有向无环图)来解决这个问题,其中每个节点都携带其工具、参数和依赖列表,并由一个单独的调度层在派发下游调用之前等待上游完结。模型生成计划结构;确定的执行器运行它。这种分离至关重要,因为模型不擅长执行器的逻辑 —— 推理偏序关系(partial orders)和完结事件 —— 而执行器也不擅长模型的逻辑 —— 弄清楚下一步该做什么。将它们保持在清晰接口的两侧,可以防止智能体循环试图同时扮演这两个角色。
即使没有完整的 DAG 规划,智能体循环中的步骤状态机也会有所帮助。该状态机将“工具已确认”与“观察到结果”区分开来,并拒绝进入下一个推理步骤,直到每个进行中步骤的结果都已绑定。状态机并不会让智能体变得更聪明 —— 它只是让智能体无法基于错误的证据推进。
架构层面的认知 同步工具调用和异步工具调用是披着相同 JSON schema 外衣的不同抽象。函数调用规范继承自 RPC 的形式(调用返回结果),现在却被要求承载分布式系统的语义(调用返回工作句柄)。这种错配不会随着模型变得更聪明而自行解决。它要在协议层解决,即异步工作获得不同的类型和不同的完结契约 —— 否则它就无法解决,而智能体会继续对那些从未完成的任务给出言之凿凿的总结。
如果不考虑这一点就直接接入函数调用的团队,实际上是交付了一个会在任务完结上撒谎的规划器。这个谎言很有迷惑性,因为 JSON schema 看起来没问题,追踪记录看起来也很整洁。但代价会出现在下游:在无人看管的滞留任务队列中,在收到邮件说报告已就绪但实际上并未生成的客户身上,在试图协调智能体所言与系统实际行为之间矛盾的值班工程师身上。异步工具调用是另一类事物。在协议中、在 Broker 中、在状态机中、在 UX 中将其作为不同的类别对待 —— 这正是那些从教训中幸存下来的人接下来所构建的。
会员专享
余下内容仅对会员开放。 会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
— 完整文章,包含未公开存档的部分 — 可落地的工作框架,附带权衡与决策依据 — 新文章抢先看,先于公开发布 登录以继续阅读→ 随时取消 · 一次订阅,畅读全部