loading

Loading

探索与发现

首页 AI 工程化

用依赖图替掉手写 if-else:Agent 工具串行的工程做法

分类:AI 工程化
字数: (2009)
阅读: (0)
0
摘要:把手写 if-else 的工具调用换成有向无环图:节点声明产出与消费字段,执行器按拓扑序推进。解释了改动带来的局部化变更、少一轮依赖推导和并行机会,也说了写不清依赖时这套做法会失效。

前一阵把一个内部运维 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

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图