前一阵把一个内部运维 Agent 的工具调用逻辑推倒重来。旧版本是一个函数里几百行的 if-else:先看是否需要查用户 ID,如果需要再查订单,订单有异常再去拉日志。加第 8 个工具时,那个函数已经没人愿意碰,因为改一处顺序就等于重写整段分支判断。新版本把工具前置条件写成一个有向无环图,由执行器按拓扑序跑。
手写串行编排在哪里开始塌
串行工具调用的本质是数据依赖:后一个工具的入参来自前一个工具的输出。说常见动作对应关系的是 lookup_user_id 返回 user_id,然后拿这个 ID 去调 get_orders(user_id)。链长两步时手写毫无问题,但真实场景里很快会碰到几种情况。
一是分支指数增长。某些工具对 A 类查询需要、对 B 类不需要,两两组合后 if-else 里的路径数迅速失控。二是模型每轮都要重新推理依赖关系,等于把已经知道的顺序交给模型重新猜一遍,中间某轮它跳过查用户 ID 直接取订单,拿到参数缺失,循环里又多转一圈。三是并行机会白白浪费。互不依赖的两个查询被强行串行,端到端延迟就是调用耗时相加。
LangChain 的 Agent / AgentExecutor 这一层解决的是工具注册和单次选择:把 name、description、function 放进一个映射表,模型按描述挑一个调用。它不管你调用之后排第几个、什么时候可以并行。
图的节点是工具,边是数据依赖
改造的核心是把原来写在分支里的隐式约定改成显式数据:每个节点声明产出字段和使用字段。序列化后大致是这样:
nodes = [
{"id": "uid", "tool": "lookup_user_id", "produces": ["user_id"]},
{"id": "orders", "tool": "get_orders", "consumes": ["user_id"], "produces": ["orders"]},
{"id": "profile", "tool": "get_profile", "consumes": ["user_id"], "produces": ["profile"]},
{"id": "logs", "tool": "search_logs", "consumes": ["orders"]},
]
执行器做三件事:按 consumes 建边校验无环、算出入度为 0 的就绪集、分批执行并回填产出字段。上面这个例子里,uid 跑完后 orders 和 profile 一起成为就绪节点,可以并行下发,logs 继续等 orders 返回。user_id 从输入里一次性解析。
这和有向边推进的工作流框架并排看。LangGraph 的节点与边、Temporal 的显式建模都能表达同样的东西。区别在粒度:把依赖图塞进模型上下文时,图本身就是一份长文本。
图抽出来以后换来什么
变更局部化是最直接的收益。新增 search_logs 只需追加一个节点,不改旧边。它现在声明的 consumers(依赖节点 id 列表吗?不需要,修正为:consumes字段名单)写对就行。
模型少做一轮依赖推导。到达节点边界时它只调用图中给出、且依赖已满足的那个工具,不再每次重新算先调谁。
并行带来的成本可以提前计算。这里要考虑写入锁问题的另一面:orders 与 profile 两个写法并行会不会竞态?只看图不够,识别语义冲突。
静态校验的价值往往被低估。图在运行前就能发现环、发现某个 consumes 谁都不 produces、发现循环里所有节点等一个永远不会出现的字段。langgraph 这种报错比运行时模型多绕三轮便宜得多。
不建议全面拥抱图的理由
显式图的前提是人能写出依赖关系。任务探索性很强时,下一步取什么工具往往看上一份工具返回内容里面是什么,而开新链接分支并不是提前定好的边。候选集合本来就动态。
也承认 Anthropic 等不同团队在 Agent 编排上的侧重并不一致。工程上省上下文一个明确的收益点:把要分发的指令的引用名、ID 子集关系统后,逻辑简化比图的边更重要。工具数量上到几十个之后,schema 占用的上下文成本要先算清楚。
落地的几个判断
改完没有想象中的轻。最大痛点在工具描述被写在 prompt 里、又被写成图,图里是权威源以后,描述要和 consumes 同步,加一个产物字段就得改一次描述。
我们的做法是以从模型输出映射工具调用的动态边版本为主。工具集合在约十几个后,模型在多轮工具会话结果时逐一起可能收敛,并借助通用搜索工具兜底。
我把依赖图列为可选的优化路径,而不是默认架构。它确够把握时推荐改写一个过程码,任务开放时或许还不如一个 loop 里直接写成调用工具、把路径投到记录中心或日志里来得清晰。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-engineering/670.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
推理成本下降之后:模型路由、KV Cache 与端侧推理
上个月一个团队拿着账单来找我:模型单价降了,费用却涨了三倍。看完调用链就明白了,问题不在单价,在用法。单位 token 便宜,不等于每次任务便宜。
2026/10/10
-
2026 年 AI 应用评测:从单轮打分到多轮轨迹评估
一个 Agent 的评测分数从 72% 涨到 81%,团队很高兴,直到发现它学会了在失败时直接结束任务,把“没做完”算成了“无需回答”。评测设计错了,指标越好看越危险。
2026/10/19
-
动态拼批不是开个开关:线上拼批的取舍与门禁
在线服务里的动态拼批和离线批量推理是两码事。窗口时长、KV Cache 水位、批内参数对齐共同决定拼接能否成立。上线前该盯的是 p99 和显存峰值,而不只是吞吐。
2026/10/25
-
长度惩罚参数调优:常见参数名字容易混淆
长度惩罚在主流 API 里并不存在。真正管长度的是 max_tokens 和 stop sequences,frequency、presence、repetition 惩罚管的是用词重复和新词探索。把这些旋钮分开,能避免输出被截断、术语被替换成同义词两类故障。
2026/10/23
-
推理模型的流式输出:thinking 块怎么流
推理模型的流式输出不只是逐字渲染:思考块、正文、工具调用参数混杂在 SSE 流里,客户端要按字段分流、独立缓冲、支持中途中断。记录接通义千问与智谱过程中的判断和踩坑。
2026/10/24
-
推理服务排队延迟的分层观测:从请求数到Token账本
盯 QPS 和 P99 看不到排队延迟长在哪。把这件事拆成入口→上游准入队列→后端等待→batch 解码四层,用 vLLM 暴露的排队与 KV 缓存指标做层级归因,讨论分层之后仍然看不见的部分与取舍。
2026/10/27
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布