跳转至

ReWOO:先排工具调用,再批量执行

ReAct 每调完一个工具就问模型:接下来干什么?

灵活,但慢。旅游助手需要查天气、查汇率、查开放时间、查地铁路线——这四个调用互相不依赖,ReAct 却要串行跑四轮模型调用。

ReWOO 的做法:先让模型把所有工具调用排出来,Python 批量执行,然后模型读完所有结果一次回答。

一句话说清楚

ReWOO 把"观察一次、决定一次"改成"先排好所有工具调用、批量执行、再一次回答",减少不必要的模型轮次。

解决什么问题

问题 表面看起来 实际风险
ReAct 每次工具调用后都问模型 灵活 独立调用白白串行,延迟翻倍
独立工具调用串行跑 简单 延迟随工具数线性增长
每次观察后没有明确目的 模型能回答 不知道每次工具调用到底为了什么

旅游助手要查 4 样东西,ReAct 要跑 4 轮模型调用(每轮决定下一步)+ 4 次工具调用 = 8 次。ReWOO 只要 1 次模型调用(排计划)+ 4 次工具调用 + 1 次模型调用(综合)= 6 次,而且工具调用可以并行。

引入什么复杂度

  • 计划是一次性排的——如果第一个工具的结果会影响后续工具的参数,ReWOO 处理不了。
  • 工具调用失败时,没有中途补救机制。
  • Solver 一次看完所有 observation,上下文可能膨胀。

和其他模式的关系

模式 谁决定下一步 什么时候用
ReAct 每次观察后模型选动作 调用之间有强依赖
ReWOO 提前排好所有调用 调用大部分独立
LLM Compiler 模型建依赖图 有依赖也有并行
Workflow Python 写死步骤 步骤完全已知

ReWOO 比 ReAct 省轮次,但比 LLM Compiler 简单——不处理依赖图,只处理"一批独立调用"。

这个模式改变了什么

负责什么
Planner 模型 输出工具调用列表(含 tool、args、purpose)
Python 批量执行工具、处理失败、收集结果
Solver 模型 读所有 observation,写最终答案
  • 谁决定下一步:没有"下一步"。计划一次排完,执行一次做完。
  • 谁拥有状态:Python 持有调用列表和结果列表。
  • 什么时候停止:所有工具执行完 + Solver 给出答案。

走一遍真实轨迹

用户请求:"杭州三天,查一下天气、西湖门票、从上海到杭州的高铁时间。"

阶段 内容 输出
排计划 模型列出 3 个工具调用 [weather("杭州"), ticket("西湖"), train("上海","杭州")]
批量执行 Python 并行跑 3 个工具 天气:"Day1 晴...";门票:"免费";高铁:"50 分钟"
综合 Solver 读完 3 个结果 "杭州三天天气晴好,西湖免费,上海出发高铁 50 分钟..."

关键:3 个工具调用之间没有依赖,ReWOO 一批搞定。如果"门票价格取决于天气(雨天免费)",那就不能用 ReWOO,要用 ReAct 或 LLM Compiler。

流程图

flowchart TD
  T["用户任务"] --> P["模型排工具调用列表"]
  P --> E["Python 批量执行工具"]
  E --> O["收集所有 observation"]
  O --> S["Solver 模型写最终答案"]

完整实现

from __future__ import annotations
from pathlib import Path
from agent_patterns_lab.patterns.rewoo import rewoo
from agent_patterns_lab.runtime import MockLLM, Tool, ToolRegistry, Tracer


def main() -> None:
    tracer = Tracer()

    # 模拟工具
    def weather(args: dict) -> str:
        return f"{args['city']}未来三天:Day1 晴 28°C,Day2 多云 26°C,Day3 晴 29°C。"

    def ticket(args: dict) -> str:
        return f"{args['attraction']}门票:免费(需预约)。"

    def train(args: dict) -> str:
        return f"{args['from']}{args['to']}高铁:最快 50 分钟,二等座 73 元。"

    tools = ToolRegistry(
        [
            Tool(name="weather", description="查询城市天气", handler=weather),
            Tool(name="ticket", description="查询景点门票", handler=ticket),
            Tool(name="train", description="查询高铁信息", handler=train),
        ]
    )

    model = MockLLM(
        [
            # 第一次调用:排工具计划
            '{"tool_calls":['
            '{"tool":"weather","args":{"city":"杭州"},"purpose":"查天气"},'
            '{"tool":"ticket","args":{"attraction":"西湖"},"purpose":"查门票"},'
            '{"tool":"train","args":{"from":"上海","to":"杭州"},"purpose":"查高铁"}'
            "]}",
            # 第二次调用:综合答案
            (
                "杭州未来三天天气好(晴/多云),适合出行。"
                "西湖免费但需预约。"
                "上海到杭州高铁 50 分钟,二等座 73 元。"
                "建议提前预约西湖,选早班高铁避开人流。"
            ),
        ]
    )

    result = rewoo(model, task="杭州三天,查天气、西湖门票、上海到杭州高铁。", tools=tools, tracer=tracer)
    print(result.answer)

    print("\n--- 工具调用记录 ---")
    for i, (call, res) in enumerate(zip(result.tool_calls, result.tool_results)):
        print(f"  {i+1}. {call.tool}({call.args}) -> {res}")

    trace_path = tracer.export_jsonl(Path(".traces") / "52_rewoo_zh.jsonl")
    print(f"\n[trace] {trace_path}")


if __name__ == "__main__":
    main()

运行:

PYTHONPATH=src python examples/52_rewoo.py

完整运行记录

杭州未来三天天气好(晴/多云),适合出行。西湖免费但需预约。上海到杭州高铁 50 分钟,二等座 73 元。建议提前预约西湖,选早班高铁避开人流。

--- 工具调用记录 ---
  1. weather({'city': '杭州'}) -> 杭州未来三天:Day1 晴 28°C,Day2 多云 26°C,Day3 晴 29°C。
  2. ticket({'attraction': '西湖'}) -> 西湖门票:免费(需预约)。
  3. train({'from': '上海', 'to': '杭州'}) -> 上海→杭州高铁:最快 50 分钟,二等座 73 元。

[trace] .traces/52_rewoo_zh.jsonl

trace 中的事件:

rewoo.plan  tool_calls=3
tool.call  name=weather
tool.call  name=ticket
tool.call  name=train

踩坑与诊断

现象 trace 信号 修法
排了不相关的工具 调用了和任务无关的工具 tool.call 里有任务未提及的工具名 缩短计划、允许第二批补调
无法中途调整 第一个工具返回"闭馆",后续工具还在查门票 结果里包含矛盾信息 回退到 ReAct
工具失败污染 一个失败导致 Solver 乱写 tool.call 有 error 字段 逐工具重试 + 把失败标记传给 Solver
observation 膨胀 Solver prompt 太长 Solver 调用 token 数异常高 按 purpose 摘要 observation

关键诊断技巧:如果 Solver 的回答遗漏了某个工具的结果,检查 _synthesis_prompt 里是不是所有 (call, result) 对都传进去了。

工程备忘

成本公式

模型调用 = 2(计划 + 综合)
工具调用 = N
总延迟 ≈ 2 × 模型延迟 + max(工具延迟)(如果工具并行)

对比 ReAct:模型调用 = N + 1总延迟 = (N+1) × 模型延迟 + sum(工具延迟)

工具越多、越独立,ReWOO 的优势越明显。3 个独立工具时 ReWOO 省约 50% 模型调用。

生产注意事项

  • max_tool_calls 建议 3-6。超过 6 个的计划容易出错。
  • 工具调用可以真正并行执行(asyncio.gather),但当前实现是串行的。生产环境建议改成并行。
  • 如果超过 30% 的任务需要"看到第一个结果才能决定下一步",ReWOO 不适合,用 ReAct。

读完以后

ReWOO 适合"多个独立工具调用"的场景。

  • 如果调用之间有依赖关系,读 LLM Compiler
  • 如果每一步都需要看观察再决定,读 ReAct

参考资料