Agent 响应延迟过高?从工具治理角度提供两条思路

大模型的一次响应往往不止一个工具调用,而工具的参数又是分片、逐步抵达的。系统如何一边把生成过程展示给用户,一边在参数就绪时就尽早执行工具,同时保证结果不乱、对话还能继续?这背后是一套兼顾"展示"与"执行"两条通道的工程实现。

下面用一个购物场景来类比。你在一家 AI 导购里问"帮我把两件商品加入购物车,再算一下总价"。模型在"说话"的过程中,其实已经在生成几个动作的参数:加入 A、加入 B、计算总价。理想的做法是:参数一凑齐就立刻去执行,而不是等模型把整句话说完再统一动手------这样用户能更快看到结果。但提前动手又带来风险:动作是否执行重复、结果顺序是否错乱、对话记录还能不能接下去。

01 先区分三个时间点

一个工具调用从"出现"到"完成",至少经历三个时间点。

  1. 开始生成:流里出现 tool_use 块,此时只知道工具名和调用 ID,参数还不完整。

  2. 参数生成完毕:收到该块的结束标记,这时才能把缓冲区当作完整 JSON 解析并执行。

  3. 工具执行完毕:得到结果回传给模型,模型才能据此继续下一轮推理。

这套实现的入口在 StreamedRound.relay(),执行任务由 EagerDispatcher 管理。核心思路是:在第 2 个时间点就启动工具,而不是等整个模型响应生成完。

02 从模型请求到工具参数

编排器先构造带工具定义的请求,再调用流式接口。购物端与商家端的编排器使用同一套处理方式。

模型返回的不是一次性完整 JSON,而是一连串事件。解析器按内容块的 index 记录状态,把参数一点点拼起来:

  • 块开始:记录工具名、调用 ID,创建参数缓冲区。

  • 增量:把 partial_json 追加到对应工具的缓冲区。

  • 块结束:参数写完,尝试解析完整 JSON。

这里有个容易混淆的点:解析器是逐个处理流事件的,但工具任务可以并发运行。所谓"并行",主要是异步任务在时间上的重叠,而不是开多个线程同时解析同一条模型流。

项目还区分了两种 JSON 解析,各自用途不能混用:

  • 执行解析:用 json.loads() 读取完整缓冲区,要求结果是对象。只有它成功,工具才可能执行。

  • 展示解析:对支持渐进式卡片的工具,可临时补齐未闭合的括号、略过未写完的字符串,用于生成界面预览。

两者绝不能混用:容错解析出来的半成品只能展示,不能触发有副作用的操作。最终卡片也会重新校验参数、补充服务端数据,再发出正式的 UI 事件。这好比菜还没炒熟,可以先让顾客看一眼"锅里在冒热气",但绝不能把半生不熟的菜端上桌。

03 为什么能提前执行多个工具

收到某个工具的块结束信号后,relay() 调用 dispatcher.dispatch()。后者通过 asyncio.ensure_future() 创建执行任务,并以调用 ID 为键保存。

假设模型一轮生成了 A、B 两个工具调用,时间线大致是这样:

  • 模型生成 A 的参数 → A 的块关闭 → 启动 A。

  • 模型继续生成 B 的参数 → B 的块关闭 → 启动 B。

  • 模型响应结束 → 等待 A、B 全部完成 → 回填结果。

于是两段耗时可以被重叠起来:工具 A 的执行与模型生成后续内容重叠;工具 A、B 的执行彼此重叠。就像流水线上提前开工:上一件还在加工,下一件的图纸已经送进来。

流结束后,编排器以模型最终消息里的 tool_use 列表为准。EagerDispatcher.collect() 对已经启动的调用复用其任务,对还没启动的调用补充执行,再用 asyncio.gather() 等待全部结果。

这里用调用 ID 去重,解决了"流中提前执行一次、读最终消息后又执行一次"的重复风险。gather() 的返回列表与传入列表同序,所以结果虽然可能乱序完成,但发出的结果事件和写入对话的结果仍按模型调用顺序排列。

04 结果怎样回到模型和前端

工具统一由 BaseToolExecutor.execute() 分派。它负责选择普通业务 handler、展示工具或委托工具,并把参数错误和执行异常转换成统一的 ToolOutcome。

一个 ToolOutcome 有两类产物:

  • result_text:作为 tool_result 写入对话,供下一轮模型调用读取。

  • events:例如正式卡片 UI、购物车更新,供前端消费。

编排器在一轮工具全部结束后发出结果事件,再把所有 tool_result 作为一条 user 消息追加到模型对话。若还需要模型回答,就进入下一轮模型调用。

前端接收的是 SSE:服务端把 AgentEvent 编码成 event: 与 data: 帧,浏览器逐帧解析。渐进式预览和最终 UI 使用调用 ID 关联,使最终卡片能接替预览卡片。

要注意一个时序细节:tool_call 可以在模型仍在生成时发给前端,但这个项目的常规 tool_result 是统一 join 后再按顺序发出的。不能把"工具提前完成"直接理解成"完成瞬间就向前端发结果"------展示通道可以渐进,结果交付仍然是有序的。

05 并发带来的正确性问题

提前并发只是调度方式,业务约束仍必须在执行层保证。还是购物场景:两个修改购物车的调用可能同轮执行,如果"读取购物车、检查限制、写入购物车"这三步交错进行,检查结果就可能被破坏------比如两个动作各自都以为限额还够,同时写入后总额超限。项目用按 session 划分的 asyncio.Lock 包住这三步,确保同一会话内的修改不会交错。

还要处理不完整输入和中断:

  • 参数始终不是有效 JSON:保留已生成的对话内容,给该调用一个错误 tool_result,提示模型重试,但不运行工具。

  • 流报错、连接中断:取消已启动但未结束的任务;若对话里留下没有结果的 tool_use,补一个中断结果,保证下一次请求的消息结构有效。

  • 服务端工具(如模型侧的网络搜索):记录其块,但不通过本地执行器运行。

06 提炼成设计原则与边界

这套实现可以概括为:逐块确定参数、按调用 ID 提前启动、轮末统一 join、按原顺序回填、由业务层保护共享状态。流式预览是独立的展示通道,执行只接受完整参数。这样既缩短了模型生成与工具执行之间的空等时间,也保住了工具只执行一次、结果有序、对话可继续这三项正确性要求。

最后要说明范围:上述逐块解析和提前调度是仓库的 Messages API 运行时实现。仓库另有 Agent SDK 路径,它把工具注册给 SDK,并收集完整响应。不能把这里的流程说成所有接入方式都自行实现了同一套解析。

相关推荐
CappuccinoRose1 小时前
FormData数据处理
开发语言·前端·javascript·表单数据
飘尘2 小时前
SVG和Canvas,前端里的两支“画笔”,用的时候怎么选择?
前端·javascript·面试
马六六i2 小时前
市面上正规的IP驱动产业新场景新工具有哪些
大数据·网络·python·tcp/ip
计算机魔术师3 小时前
DeepSeek 据报道接近完成至少 800 亿元融资,腾讯与宁德时代参与
前端
可乐ea3 小时前
多模型编排的三层:框架、模型路由与提供商路由
前端·网络·人工智能·ai智能体·多智能体协作·多模型编排·大模型路由
万联WANFLOW3 小时前
全球广告网络直通美国,TikTok抢占旺季全域流量新高地
网络·业界资讯
HackTwoHub3 小时前
Clown SRC skill第二代2.x最新版企业安全研究 Skill|证据优先的 SRC 与白盒审计工作流正式更新
网络·安全·web安全·网络安全·系统安全·密码学·安全架构
2601_962177304 小时前
2026年AI API Gateway怎么选?我整理了6种方案的费用、稳定性和适用场景
网络·人工智能·深度学习·gateway
白帽攻防录4 小时前
SRC 挖洞:Certighost AD CS 域控制器伪造深度复盘,CVE-2026-54121 一个普通域用户怎么拿走整个企业域
网络·安全·web安全·网络安全·域安全