9.3.9 实战:多工具协作 Agent

- 理解多工具 Agent 和单工具 Agent 的主要差别
- 看懂一个完整的“发现 -> 选择 -> 执行 -> 整合 -> 输出”闭环
- 理解多工具协作里状态管理为什么是关键
- 学会用最小项目方式展示一个多工具 Agent
多工具协作难在哪里?
Section titled “多工具协作难在哪里?”难点不只是“工具更多了”
Section titled “难点不只是“工具更多了””真正困难的地方通常有三层:
- 先后顺序
- 中间状态传递
- 错误后的处理
例如退款场景里:
- 不知道订单状态,政策判断就可能错
- 不知道订单金额,计算退款额就没法做
- 工具失败后,最终答复也必须改变
一个类比:像接力赛而不是单人跑
Section titled “一个类比:像接力赛而不是单人跑”单工具任务像一个人直接完成动作。 多工具任务像接力赛:
- 前一棒的结果要交给下一棒
- 某一棒掉棒,后面都受影响
所以多工具系统最怕“状态散掉”
Section titled “所以多工具系统最怕“状态散掉””如果每一轮都不清楚当前已经知道什么, 系统就很容易:
- 重复调用
- 漏关键信息
- 最后整合错
这个实战例子要解决什么问题?
Section titled “这个实战例子要解决什么问题?”我们做一个最小但完整的退款工单助手。 用户问题是:
- 我的订单还能退款吗?
- 预计退多少钱?
- 多久到账?
这个任务至少要用到三类工具:
get_order_statussearch_refund_policycalculator
而且它们之间有明显顺序:
- 先看订单状态
- 再匹配政策
- 再算金额
先跑一个完整闭环示例
Section titled “先跑一个完整闭环示例”下面这段代码会完整展示:
- 工具注册
- 状态跟踪
- 决策策略
- 多轮执行
- 最终回答
import astimport operator
OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv,}
def safe_calculate(expression): def visit(node): if isinstance(node, ast.Expression): return visit(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](visit(node.left), visit(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -visit(node.operand) raise ValueError("unsupported_expression")
return visit(ast.parse(expression, mode="eval"))
TOOLS = { "get_order_status": lambda order_id: { "order_id": order_id, "status": "未发货", "amount": 299, "shipping_fee": 15, }, "search_refund_policy": lambda keyword: { "policy_text": "未发货订单可直接申请退款,款项原路返回,通常 3 到 7 个工作日到账。" }, "calculator": lambda expression: { "result": safe_calculate(expression) },}
def decide_next_action(state): if "order_info" not in state: return {"tool": "get_order_status", "arguments": {"order_id": state["order_id"]}}
if "policy" not in state: return {"tool": "search_refund_policy", "arguments": {"keyword": "退款"}}
if "refund_amount" not in state: order = state["order_info"] expression = f"{order['amount']} + {order['shipping_fee']}" return {"tool": "calculator", "arguments": {"expression": expression}}
return None
def apply_observation(state, tool_name, observation): if tool_name == "get_order_status": state["order_info"] = observation elif tool_name == "search_refund_policy": state["policy"] = observation["policy_text"] elif tool_name == "calculator": state["refund_amount"] = observation["result"]
def build_final_answer(state): order = state["order_info"] if order["status"] != "未发货": return "该订单当前不满足直接退款条件,请联系人工客服进一步处理。"
return ( f"订单 {state['order_id']} 当前状态为{order['status']}。" f"{state['policy']} " f"预计退款金额为 {state['refund_amount']} 元。" )
def run_agent(order_id, max_steps=5): state = {"order_id": order_id, "trace": []}
for _ in range(max_steps): decision = decide_next_action(state) if decision is None: return state["trace"], build_final_answer(state)
tool_name = decision["tool"] observation = TOOLS[tool_name](**decision["arguments"])
state["trace"].append( { "tool": tool_name, "arguments": decision["arguments"], "observation": observation, } )
apply_observation(state, tool_name, observation)
return state["trace"], "达到最大步数,任务未完成。"
trace, answer = run_agent("ORD-1001")print("trace:")for item in trace: print(item)print("\nanswer:")print(answer)预期输出:
trace:{'tool': 'get_order_status', 'arguments': {'order_id': 'ORD-1001'}, 'observation': {'order_id': 'ORD-1001', 'status': '未发货', 'amount': 299, 'shipping_fee': 15}}{'tool': 'search_refund_policy', 'arguments': {'keyword': '退款'}, 'observation': {'policy_text': '未发货订单可直接申请退款,款项原路返回,通常 3 到 7 个工作日到账。'}}{'tool': 'calculator', 'arguments': {'expression': '299 + 15'}, 'observation': {'result': 314}}
answer:订单 ORD-1001 当前状态为未发货。未发货订单可直接申请退款,款项原路返回,通常 3 到 7 个工作日到账。 预计退款金额为 314 元。
这段代码和前面分散示例最大的差别是什么?
Section titled “这段代码和前面分散示例最大的差别是什么?”它已经不再是:
- 单一工具演示
而是完整表现出:
- 决策顺序
- 状态累积
- 多工具配合
- 最终整合
也就是说,它已经接近一个真正的多工具 Agent 骨架。
为什么 state 这么关键?
Section titled “为什么 state 这么关键?”因为每次工具调用后,系统都要知道:
- 现在已经知道了什么
- 还缺什么
- 下一步该补哪块信息
如果没有统一状态, 多工具协作几乎一定会乱。
为什么最终回答不是直接拿最后一个 observation?
Section titled “为什么最终回答不是直接拿最后一个 observation?”因为多工具系统的目标,通常不是原样转述某次工具输出。 它真正要做的是:
- 把多个 observation 整合成用户可理解的结论
这正是 Agent 层的价值。
这类系统最容易失败在哪?
Section titled “这类系统最容易失败在哪?”例如还没查订单状态, 就先查退款金额或直接给结论。
中间状态没保存
Section titled “中间状态没保存”会导致:
- 重复查同一工具
- 结果覆盖错
- 后面步骤用不到前面结果
某个工具失败后,系统还假装继续成功
Section titled “某个工具失败后,系统还假装继续成功”这是多工具系统里很危险的一类 bug。 例如:
- 政策没查到
- 但系统还是编了一个退款规则
所以失败路径也必须是设计的一部分。
怎样把这个演示进一步做成作品?
Section titled “怎样把这个演示进一步做成作品?”第一步:让工具更真实
Section titled “第一步:让工具更真实”例如把:
- mock 订单状态
- mock 政策文档
换成:
- 数据库查询
- 文档检索
第二步:加失败处理
Section titled “第二步:加失败处理”例如:
- 工具超时
- 订单不存在
- 政策未命中
系统都应有明确退路。
第三步:加入评估集
Section titled “第三步:加入评估集”你可以准备:
- 可退款订单
- 不可退款订单
- 金额边界样例
- 工具失败样例
这样系统就不只是“能跑”, 而是“能测”。
第四步:把 追踪 可视化
Section titled “第四步:把 追踪 可视化”如果你把工具调用轨迹展示出来, 这个项目会非常适合做作品集演示。
误区一:多工具就是把多个函数按顺序连起来
Section titled “误区一:多工具就是把多个函数按顺序连起来”不够。 真正难的是:
- 顺序判断
- 状态传递
- 失败恢复
误区二:工具越多,Agent 就越强
Section titled “误区二:工具越多,Agent 就越强”工具变多只会让:
- 选择难度
- 状态管理复杂度
一起上升。
误区三:最终答得像人就说明系统好
Section titled “误区三:最终答得像人就说明系统好”多工具系统更该看:
- 追踪 是否合理
- 工具是否必要
- observation 是否被正确整合
学完这一页,至少保留这张证据卡:
- 工具契约
- 名称、描述、输入 schema、输出 schema
- 权限
- 工具允许读取或修改的内容
- 调用轨迹
- 参数、结果、错误、重试或回退
- 失败检查
- 错误的工具、参数不当、不安全操作,或缺少观察结果
- 安全动作
- 验证、确认、沙箱、限流,或回滚
这节最重要的,不是做出一个“会连续调三个函数”的演示, 而是建立一个多工具 Agent 的核心认识:
多工具协作的本质,是围绕共享状态把多个外部能力按正确顺序组织起来,并在失败和不确定时保持系统可控。
只要这层理解稳了, 你后面做更复杂的:
- 企业助手
- 研究 Agent
- 代码 Agent
都会知道问题真正难在哪。
- 给示例再加一个
notify_user工具,只有在退款条件成立时才发送通知。 - 为什么说多工具 Agent 的核心不是“工具多”,而是“状态管理稳”?
- 如果
search_refund_policy返回空结果,你会怎么改这套流程? - 想一想:这个演示里哪些部分最适合拿去做作品集展示?
项目交付参考与讲解
notify_user应该只在已有政策证据、已有资格判断,并且最终 state 表示满足退款条件后执行。- 核心是 state management,因为 Agent 必须记住政策证据、用户输入、工具输出、决策,以及某个 side effect 是否已经发生。
- 如果
search_refund_policy返回空,应进入 no-evidence state,请用户澄清,尝试批准过的 fallback 来源,或转人工,而不是猜。 - 作品集中最值得展示 trace、state transition、tool contract、失败处理,以及加 guardrails 前后的行为对比。