9.3.4 工具调用策略

- 理解工具调用策略为什么是 Agent 成败关键之一
- 分清“不调用 / 单次调用 / 多步调用 / 回退策略”几种常见模式
- 学会设计基本的路由、重试和验证逻辑
- 看懂一个更完整的工具策略示例
为什么“有工具”不等于“会用工具”?
Section titled “为什么“有工具”不等于“会用工具”?”一个常见误解
Section titled “一个常见误解”很多人做 Agent 的第一步是:
- 接上搜索工具
- 接上计算器
- 接上数据库
然后就觉得系统会变强。
但现实里经常出现:
- 不该调工具时乱调
- 该调工具时反而不调
- 调错工具
- 一件事连着调 5 次还停不下来
所以真正的问题不是:
系统有没有工具
而是:
系统有没有“使用工具的策略”。
一个生活类比
Section titled “一个生活类比”你家厨房有刀、锅、烤箱、微波炉,不代表你就会做菜。 关键在于:
- 什么时候切
- 什么时候煮
- 什么时候烤
- 哪一步出错了怎么补救
Agent 的工具调用策略也是一样。
先把几种常见策略分清楚
Section titled “先把几种常见策略分清楚”适合:
- 常识性解释
- 简单改写
- 文风转换
例如:
“把这段话改得更正式一点”
这种任务通常不需要外部工具。
适合:
- 查天气
- 算数学式
- 查一条知识库记录
这是最简单也最稳定的调用方式。
适合:
- 先查订单,再查退款规则,再给结论
- 先搜资料,再总结,再生成输出
这时策略不再只是“调用哪个工具”,而是“下一步还要不要继续调”。
如果:
- 主工具失败
- 结果不可信
- 参数校验不过
系统就要决定:
- 重试
- 换工具
- 让用户补充信息
- 直接承认无法完成
这也是工具策略的重要部分。
工具调用前要先判断什么?
Section titled “工具调用前要先判断什么?”这件事真的需要工具吗?
Section titled “这件事真的需要工具吗?”并不是所有问题都值得走工具链。 每次调用工具都会增加:
- 延迟
- 成本
- 失败路径
所以第一步常常是:
先判断需不需要调用工具。
如果需要工具,该选哪个?
Section titled “如果需要工具,该选哪个?”例如用户问:
“我这个订单还能退款吗?”
可能需要:
- 查订单状态
- 查退款政策
所以工具选择不总是“单选题”,有时是“有顺序的组合题”。
参数是否足够?
Section titled “参数是否足够?”有些问题即使知道该调哪个工具,也可能参数还不够。
例如:
“帮我查天气”
缺城市名。 这时最合理的策略不是乱猜,而是:
先向用户追问。
工具调用后还要判断什么?
Section titled “工具调用后还要判断什么?”结果是否可信?
Section titled “结果是否可信?”工具返回了,不代表就可以直接用。
比如:
- 接口超时后返回空值
- 搜索结果相关性不高
- 数据库查不到记录
是否需要继续下一步?
Section titled “是否需要继续下一步?”有些任务一次调用拿不到最终答案。
例如:
- 先查知识库
- 再做计算
- 再汇总成用户能读懂的话
所以工具策略本质上经常是:
调用 -> 观察 -> 再决定下一步
一个最小但有教学意义的策略示例
Section titled “一个最小但有教学意义的策略示例”下面这个例子会区分三种情况:
- 不调工具
- 调单个工具
- 参数不足时先追问
def route_query(query): if "总结" in query or "改写" in query: return {"action": "no_tool", "reason": "纯文本任务"}
if "天气" in query: if "北京" in query: return {"action": "tool", "tool": "weather", "arguments": {"city": "北京"}} return {"action": "ask_user", "question": "你想查哪个城市的天气?"}
if "计算" in query: expression = query.replace("计算", "").strip() return {"action": "tool", "tool": "calculator", "arguments": {"expression": expression}}
return {"action": "fallback", "reason": "当前没有合适策略"}
queries = [ "把这段话总结一下", "北京天气怎么样", "帮我查天气", "计算 12 * 7"]
for q in queries: print(q, "->", route_query(q))预期输出:
把这段话总结一下 -> {'action': 'no_tool', 'reason': '纯文本任务'}北京天气怎么样 -> {'action': 'tool', 'tool': 'weather', 'arguments': {'city': '北京'}}帮我查天气 -> {'action': 'ask_user', 'question': '你想查哪个城市的天气?'}计算 12 * 7 -> {'action': 'tool', 'tool': 'calculator', 'arguments': {'expression': '12 * 7'}}这个例子虽然简单,但已经体现出“策略”这个层次了:
- 不是所有输入都交给工具
- 不是一缺参数就硬猜
- 不知道怎么办时有 fallback
一个更完整的策略闭环
Section titled “一个更完整的策略闭环”定义几个工具
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"))
def get_weather(city): return {"city": city, "temperature": 22, "condition": "sunny"}
def calculate(expression): return {"result": safe_calculate(expression)}调度 + 校验 + 执行
Section titled “调度 + 校验 + 执行”def execute_strategy(query): decision = route_query(query)
if decision["action"] == "no_tool": return {"type": "answer", "content": "这类任务更适合直接由模型生成文本结果。"}
if decision["action"] == "ask_user": return {"type": "question", "content": decision["question"]}
if decision["action"] == "tool": if decision["tool"] == "weather": result = get_weather(**decision["arguments"]) return {"type": "tool_result", "content": result} if decision["tool"] == "calculator": result = calculate(**decision["arguments"]) return {"type": "tool_result", "content": result}
return {"type": "fallback", "content": "当前无法稳定处理这个请求。"}
for q in ["北京天气怎么样", "帮我查天气", "计算 9 + 8"]: print(q, "->", execute_strategy(q))预期输出:
北京天气怎么样 -> {'type': 'tool_result', 'content': {'city': '北京', 'temperature': 22, 'condition': 'sunny'}}帮我查天气 -> {'type': 'question', 'content': '你想查哪个城市的天气?'}计算 9 + 8 -> {'type': 'tool_result', 'content': {'result': 17}}
这段代码真正教的是:
工具调用策略不是一行
if,而是“判断 + 分流 + 执行 + 兜底”的链路设计。
真实系统里最常见的几种策略模式
Section titled “真实系统里最常见的几种策略模式”先判断问题属于哪个工具或哪个子系统。
适合:
- 工具很多
- 任务边界明确
工具调用后,不马上信结果,而是再做检查。
适合:
- 外部数据不稳定
- 工具失败率较高
重试 / 兜底模式(Retry / Fallback)
Section titled “重试 / 兜底模式(Retry / Fallback)”先重试,再降级,再兜底。
适合:
- 外部 API 波动
- 线上服务不稳定
先规划再选工具模式(Plan-then-tool)
Section titled “先规划再选工具模式(Plan-then-tool)”先规划,再决定工具顺序。
适合:
- 多步任务
- 多工具依赖
什么时候该“少调工具”?
Section titled “什么时候该“少调工具”?”这其实也是很重要的策略能力。
少调工具的典型场景
Section titled “少调工具的典型场景”- 纯总结
- 纯改写
- 风格转换
- 已有上下文足够
为什么少调有时更好?
Section titled “为什么少调有时更好?”因为每增加一次工具调用,就增加一次:
- 时延
- 失败可能
- 状态管理成本
所以一个成熟系统不是“能调就调”,而是:
该省的时候就省。
初学者最常踩的坑
Section titled “初学者最常踩的坑”把工具调用策略理解成“路由规则”
Section titled “把工具调用策略理解成“路由规则””路由只是其中一部分。 真正的策略还包括:
- 是否调用
- 是否追问
- 是否继续下一步
- 是否回退
调用失败后没有下一步
Section titled “调用失败后没有下一步”没有重试、没有 fallback、没有补问,这种系统线上会很脆。
每次都默认模型自己决定一切
Section titled “每次都默认模型自己决定一切”实际工程里,很多策略应该由程序框架明确约束,而不是完全放给模型自由发挥。
学完这一页,至少保留这张证据卡:
- 工具契约
- 名称、描述、输入 schema、输出 schema
- 权限
- 工具允许读取或修改的内容
- 调用轨迹
- 参数、结果、错误、重试或回退
- 失败检查
- 错误的工具、参数不当、不安全操作,或缺少观察结果
- 安全动作
- 验证、确认、沙箱、限流,或回滚
这一节最重要的不是知道“可以调哪些工具”,而是理解:
工具调用策略决定了 Agent 会不会在正确的时机、以正确的顺序、用正确的方式调用工具。
这往往比“多接几个工具”更影响系统质量。
- 给本节示例再加一个
search_docs(keyword)工具,并扩展路由逻辑。 - 增加一个“如果工具执行报错,则 fallback 到人工确认”的分支。
- 想一想:如果用户问“帮我查天气并计算穿衣指数”,策略层应该怎样拆分这件事?
- 用自己的话解释:为什么说工具调用策略是 Agent 质量的分水岭之一?
参考实现与讲解
search_docs(keyword)应该增加一个知识检索分支,并返回证据片段,而不只是自由文本段落。- 工具执行错误要先分类。只有安全的临时错误才重试;其他情况应转人工确认或返回受控失败。
- “查天气并计算穿衣指数”应拆成天气查询、天气条件解释、再做一个小计算或规则推荐。
- tool strategy 是 Agent 质量分界线之一,因为它决定工具顺序、状态传递、错误恢复和何时停止。