Agent 如何拆解任务:从经典范式到真实产品

Agent 如何拆解任务

一个反直觉的事实:当你问 Claude Code "重构这个支付模块"时,它不会一次性生成完整方案,而是边探索边决策、边执行边调整。这不是模型不够强,而是 Agent 任务拆解的本质——不是"一次性规划",而是"动态调度"

一、开篇:为什么任务拆解不是"一次性规划"

直觉上以为 Agent 接到用户请求后,会像项目经理一样先列个完整计划,然后一步步执行。实际观察 Claude Code、Codex CLI 的行为会发现:它们更像是"走一步看一步"——先判断当前状态,决定下一步行动,执行后观察结果,再决定下一步。

这不是工程能力的缺陷,而是 Agent 任务的特殊性决定的。Agent 的任务通常有强不确定性:搜索结果不可预测、工具调用可能失败、用户需求可能在执行中变化。一次性规划的致命问题是一旦现实偏离假设,整个计划就崩了。所以主流 Agent 的任务拆解,本质上是"在不确定性中做动态调度"——有规划,但不迷信规划;有执行,但随时能调整。

这种动态调度能力,来源于三种经典范式的组合:ReAct(边想边做)、Plan-and-Solve(先规划再执行)、Reflection(自我反思修正)。下面逐一拆解它们的机制、适用场景和工程要点。


二、ReAct:边想边做的步进式决策

ReAct(Reasoning + Acting)是最基础的 Agent 范式,由 Shunyu Yao 在 2022 年提出。核心思想是模仿人类解决问题的方式:把推理和行动显式结合,形成Thought → Action → Observation的循环。

工作流程

当用户提出请求"帮我重构这个支付模块,解决并发扣款问题"时,ReAct Agent 的执行过程是这样的:

第一步,Agent 进入 Thought 状态:"我需要先了解现有代码结构,找到支付模块的入口"。接着进入 Action 状态,调用工具:glob("src/payment/**/*.py")。工具返回 Observation:找到 15 个文件。Agent 回到 Thought 状态:"文件有点多,我先看核心的 service.py"。

第二步,Action:read_file("src/payment/service.py")。Observation:返回 2000 行代码。Thought:"代码量很大,我需要先看关键函数"。

第三步,Action:grep("def process_payment", "src/payment/service.py")。Observation:找到函数在第 45 行。Thought:"我先读这个函数"。

第四步,Action:read_file("src/payment/service.py", offset=45, limit=100)。Observation:返回函数代码。Thought:"我发现了问题:没有加锁,高并发下可能重复扣款。我需要设计分布式锁方案"。

循环继续,直到 Agent 在 Thought 中判断"问题已经分析清楚,可以开始修改",然后进入 Final Answer 阶段,给出重构方案。

最小实现

ReAct 的核心逻辑可以用不到 50 行 Python 代码实现。这是一个简化但完整的循环:

def react(task: str, model, tools: dict, max_steps: int = 10): """ ReAct 循环 - 完整实现 """ # 存储完整的交互历史,包括思考、行动、观察 history = [] # 每个元素: {role: "assistant" 或 "tool", content: str} observations = [] # 仅存储观察结果(用于构建 prompt) trace = [] # 初始化:把任务加入历史 history.append({"role": "user", "content": task}) for step in range(max_steps): # 1. 构建 prompt:包含完整历史 prompt = build_prompt_with_history(history, tools) # 2. LLM 思考并决策 decision = model.generate_json(prompt) # 记录思考过程 history.append({ "role": "assistant", "content": f"Thought: {decision.get('reason_summary', '')}\n" f"Action: {decision.get('tool', 'none')} " f"with args {decision.get('args', {})}" }) # 3. 判断是否结束 if decision["type"] == "final": return { "answer": decision["answer"], "trace": trace, "status": "completed", "steps": step + 1, "history": history } # 4. 执行工具 tool_name = decision.get("tool") if tool_name not in tools: obs = {"ok": False, "error": "unknown_tool", "tool": tool_name} else: args = decision.get("args", {}) try: obs = tools[tool_name](**args) obs["ok"] = True except Exception as e: obs = {"ok": False, "error": str(e), "tool": tool_name} # 5. 将观察结果加入历史(关键!) obs_summary = summarize(obs) history.append({ "role": "tool", "content": f"Observation: {obs_summary}" }) # 6. 更新记录 observations.append(obs) trace.append({ "step": step + 1, "tool": tool_name, "args": decision.get("args", {}), "reason_summary": decision.get("reason_summary", ""), "observation_summary": obs_summary }) # 7. 关键:上下文已经通过 history 传递,下一轮循环会自动使用 # 超时处理 return { "answer": None, "trace": trace, "status": "max_steps_exceeded", "steps": max_steps, "history": history }

关键的工程细节:

  1. 最大步数限制max_steps):硬护栏,防止死循环。生产级 Agent 通常设为 10-20 步,根据任务复杂度调整。

  2. 审计日志trace):每一步都记录工具调用和推理摘要,失败时可排查,成功时可复盘。

  3. 观察摘要summarize):工具返回的 Observation 可能很长(如文件内容),需要摘要后再存入上下文,避免上下文爆炸。(将观察结果写入历史中)

  4. JSON 格式输出:让模型返回结构化 JSON(而非自由文本),便于解析和验证。生产级 Agent 会加格式校验,防止模型输出非 JSON 导致解析失败。

核心优势

ReAct 的强项在于动态规划与纠错能力。与一次性生成完整计划不同,ReAct 是"走一步,看一步"——根据每一步的 Observation 动态调整后续 Thought 和 Action。如果搜索结果不理想,它可以在下一步修正搜索词、重新尝试;如果代码读不懂,它可以追加上下文、换角度分析。这种适应性让 ReAct 在开放式任务(如调试、搜索、代码理解)上表现优异。

另一个优势是工具协同能力。ReAct 把 LLM 的推理能力与外部工具的执行能力天然结合:LLM 负责运筹帷幄(规划与推理),工具负责解决具体问题(搜索、计算、读文件)。这种协同突破了单一 LLM 在知识时效性、计算准确性上的固有局限。

固有局限

ReAct 的第一个问题是对 LLM 能力的强依赖。整个流程的成功与否,高度依赖底层 LLM 的综合能力。如果 LLM 的逻辑推理能力、指令遵循能力或格式化输出能力不足,就很容易在 Thought 环节产生错误规划,或在 Action 环节生成不符合格式的指令,导致流程中断。

第二个问题是执行效率。完成任务通常需要多次调用 LLM,每次调用都伴随着网络延迟和计算成本。对于需要很多步骤的复杂任务,串行的"思考-行动"循环会导致较高的总耗时和费用。这也是为什么生产级 Agent 都会设置最大步数限制、超时限制,避免无限制循环。

第三个问题是可能陷入局部最优。步进式决策意味着缺乏全局、长远的规划。Agent 可能因为眼前的 Observation 选择一个看似正确但长远来看并非最优的路径,甚至在某些情况下陷入"原地打转"的循环。这就是为什么很多 Agent 会加入重复检测机制——如果发现最近几步的 Action 重复,就强制切换策略或请求用户干预。


三、Plan-and-Solve:先规划再执行的两阶段策略

如果说 ReAct 像侦探,根据现场线索一步步推理、随时调整方向;那么 Plan-and-Solve 就像建筑师,动工之前先绘制完整蓝图,然后严格按照蓝图施工。

对应到推理模式上,它把 ReAct 里混在一起的「规划推理」和「执行推理」给完全拆开了(行话理解,说白了就是两件事分开干,各管各的):专门用一个 LLM 负责「做规划」,把大目标拆成一步一步的执行清单;用另一个 LLM(或者模块)负责「按清单执行」,执行完了再统一汇总。React是一个LLm做所有


两阶段工作流

当用户提出请求"帮我写一份关于 Agent 上下文压缩的技术报告"时,Plan-and-Solve Agent 首先进入规划阶段。LLM 调用生成一个结构化的计划:

1. 搜索上下文压缩相关论文和博客,收集核心概念 2. 阅读主流产品(Claude Code、Codex CLI)的压缩策略文档 3. 整理压缩策略的分类体系(截断、摘要、offload 等) 4. 分析压缩的触发机制和阈值设计 5. 总结压缩的安全风险和工程实践 6. 撰写报告初稿

规划完成后,Agent 进入执行阶段,严格按照计划逐一执行。每一步的输入包含:原始问题、完整计划、前序步骤的执行结果。比如执行步骤 2 时,Agent 会知道"这是计划的第 2 步,前一步已经收集了核心概念,这一步要阅读具体产品文档"。

核心优势

Plan-and-Solve 的强项在于目标一致性强。因为计划在执行前已经制定完成,Agent 在整个执行过程中都有明确的"导航图",不会走到一半忘了要干嘛。这对于需要长远规划的复杂任务(如报告撰写、代码生成、数学推理)特别有效。

另一个优势是上下文结构清晰。ReAct 的上下文是累积式的(逐步追加 Thought/Action/Observation),容易变长、变乱;Plan-and-Solve 的上下文是结构式的(计划在前 + 执行记录在后),逻辑层次分明,对模型更友好。

固有局限

Plan-and-Solve 的第一个问题是规划依赖初始信息。规划阶段的 LLM 调用只有用户请求作为输入,缺乏执行过程中发现的新信息。如果执行过程中发现某些假设是错的(比如"找不到某份文档"),原来的计划可能不再适用,但 Agent 可能还在按旧计划执行——这就是"过度依赖规划"的风险。

第二个问题是灵活性不足。面对动态变化的任务(如调试、实时搜索),Plan-and-Solve 可能不如 ReAct 敏捷。这也是为什么很多产品会采用混合策略:对于结构性强的子任务用 Plan-and-Solve,对于探索性强的子任务用 ReAct。


四、Reflection:自我反思修正的第三层保障

ReAct 和 Plan-and-Solve 都是"向前走",Reflection 是"回头看":执行一轮后,让模型自我批判,找出不足,然后修正或重试。

Reflection 不是一套独立的完整流程,而是给 ReAct、Plan-and-Execute 加的「锦上添花的 buff」,它不改变原本的做事流程,只是在原本的基础上,加了一层「自我检查、自我修正」的环节。

还是用最熟悉的考试例子,你一下就能看懂三者的关系。ReAct 就像你一道题一道题挨着做,做一道过一道,不回头看。Plan-and-Execute 是你先把整张卷子的做题顺序、时间分配定好,再按计划做题。Reflection 则是你做完一道题(或者整张卷子),回头再检查一遍,看看有没有算错数、有没有看错题,发现错了马上改,改完再交卷。

工作流程

当 Agent 完成一轮执行(无论是 ReAct 循环还是 Plan-and-Solve 计划)后,Reflection 模块会启动。LLM 被要求对执行结果进行批判性评估:"这个答案完整吗?有逻辑漏洞吗?是否满足用户的所有约束?"

如果反思发现问题(如"预算计算有误差"、"没有考虑边界情况"),Agent 会进入修正阶段:调整下一步行动、修改部分结果、或重新执行某个子任务。修正后再次反思,直到满意为止。

核心价值

Reflection 的价值在于提升输出的可靠性。对于质量要求高的任务(如代码生成、数学推理、关键决策),单次执行往往有疏漏,Reflection 提供了"自我纠错"的机会,显著提高成功率。

另一个价值是适应性强。Reflection 不改变 Agent 的主流程(ReAct 或 Plan-and-Solve),而是作为一个可选的"后处理"模块叠加在最外层。这种松耦合设计让它可以灵活地嵌入各种 Agent 系统。

固有局限

Reflection 的第一个问题是增加延迟和成本。每轮反思都是一次额外的 LLM 调用,如果反思多轮,总耗时和费用会显著上升。这也是为什么生产级 Agent 通常只在关键节点触发反思,而非每一步都反思。

第二个问题是可能过度修正。如果反思模块过于严格,Agent 可能陷入"反复改、改不完"的循环,甚至把原本正确的答案改错。这也是为什么反思模块的设计需要平衡"挑剔"和"放手"。


进阶

进阶:动态 Replan 和 Reflexion

讲完了三个基础范式,再补充两个在实际项目中经常会遇到的进阶机制,面试时能说出来会很加分。

第一个是动态 Replan,它解决的是 Plan-and-Execute 的一个核心痛点:计划定死了,中途遇到意外怎么办?

比如你规划了五步来写竞品分析报告,执行到第三步发现某个竞品已经被收购了,原来的分析框架需要调整,但计划已经定好了,后面的步骤还是按老计划跑,输出的报告就会有问题。

动态 Replan 的做法是在每个步骤执行完之后,把当前结果和剩余计划一起交给规划模块,让它判断「原来的计划还合理吗,需不需要调整」。如果需要,就生成一份新的剩余步骤计划,替换掉原来的。

这样既保留了 Plan-and-Execute「先规划再执行」的结构优势,又不会因为计划太僵硬而在意外情况下翻车。代价是每步都多了一次「重新评估计划」的 LLM 调用,token 消耗会增加。

第二个是Reflexion,它把 Reflection 的「自我反思」推到了更深的层次。普通的 Reflection 是「做完了检查一遍、发现问题就重做」,有点像考试做完检查一遍。

Reflexion 在这个基础上多做了一件关键的事:它不仅检查输出对不对,还会把每次失败的原因总结成一段「经验教训」,存进记忆里,下次再遇到类似任务时,这段教训会作为上下文传给 LLM,让它避免重蹈覆辙。

这个机制有点像你做错了一道数学题,不只是改答案,还会在错题本上写「这类题容易漏掉符号变化,下次要特别注意」,下次遇到同类题时翻一下错题本再动笔。

Reflexion 的效果到底有多强?在 HumanEval 代码生成基准测试上,Reflexion 机制把 GPT-4 的 pass@1 准确率从 80% 提升到了 91%,提升幅度超过了 10 个百分点。这个数据非常能说明问题:同样的基座模型,仅仅加了「反思 + 记住教训」这个机制,代码一次写对的概率就大幅提高。

背后的原因也好理解,代码生成天然适合 Reflexion,因为代码可以运行、可以测试,执行结果就是最直接的反馈信号。Agent 写完代码跑一遍测试,没通过的话就分析是哪里出了问题,把「这个 API 的参数顺序搞反了」或者「边界条件没处理」这样的具体教训记下来,下次重试时带着这些教训去改,成功率自然就高了。这种「verbal reinforcement learning」(语言强化学习)的思路,让 Agent 不需要梯度更新就能从错误中学习,非常适合在推理阶段提升质量。

五、三种范式对比:从概念到工程

把三种范式放在一起看,差异会更清晰。

核心维度对比

维度

ReAct

Plan-and-Solve

Reflection

决策时机

每一步动态决策

一次性规划后执行

执行后回头看

停止条件

模型判断"够了"或硬编码上限

计划执行完毕

满意为止或达到上限

上下文压力

累积式(逐步追加 Thought/Action/Observation)

结构式(计划在前 + 执行记录在后)

反思追加

执行效率

多次串行 LLM 调用,延迟和成本较高

规划 1 次 + 执行 n 次,相对可控

额外反思调用,增加延迟

灵活性

高(根据 Observation 随时调整)

中(计划可调整但不够敏捷)

高(可反复修正)

目标一致性

弱(可能走到一半忘了目标)

强(有完整计划导航)

中(反思能纠偏)

适用场景

开放式探索(调试、搜索、代码理解)

结构化任务(报告撰写、代码生成)

高质量要求(关键决策、代码生成)

典型任务表现对比

从公开实验和工程实践来看,三种范式在不同任务类型上的表现有显著差异:

开放式搜索任务:ReAct 成功率最高,因为能根据搜索结果动态调整策略。Plan-and-Solve 容易因为初始假设错误而跑偏,成功率低 15-25%。

结构化推理任务:Plan-and-Solve 成功率最高,因为有完整计划导航,不会走到一半忘目标。ReAct 容易陷入局部最优,成功率低 10-15%。

代码生成任务:Plan-and-Solve + Reflection 组合表现最好——先规划架构,再逐块实现,最后反思检查边界情况。纯 ReAct 容易生成"能跑但不优雅"的代码。

调试任务:ReAct 表现最好,因为调试本质上就是"根据报错动态调整"。Plan-and-Solve 几乎不适用——你没法在开始前就知道会报什么错。

三种范式在 token 消耗上的差异是选型时必须考虑的现实因素,咱们用一个具体的例子来直观感受一下。

假设有一个需要 5 步工具调用的任务,每步产生的推理和工具结果平均占 2000 token。

用 ReAct 来跑,因为每次调 LLM 都要把完整历史带上,第一步输入 2000 token,第二步 4000,第三步 6000,依次递增,光输入就是 2000 + 4000 + 6000 + 8000 + 10000 = 30000 token,增长曲线是线性的,步骤越多越吃钱。

换成 Plan-and-Execute,消耗集中在规划阶段和汇总阶段这两个「大头」上。

规划阶段调一次 LLM,输入是任务描述加工具列表,大约 3000 token。执行阶段每步只需要带当前步骤的指令和前面步骤的结果摘要(不是完整的推理历史),每步大约 1500 token,5 步总共 7500 token。汇总阶段再调一次 LLM,把所有结果综合起来,大约 4000 token。加起来总消耗约 14500 token,比 ReAct 的 30000 token 低了一半多。如果再用上前面说的「强模型规划、弱模型执行」策略,虽然 token 数量差不多,但执行阶段用便宜模型跑,实际花费能再降 70% 以上。

加了 Reflection 之后,每个需要反思的节点至少多一次 LLM 调用(评估一次,不达标还要重做),token 消耗会在基础范式上再增加 30% 到 100%,取决于反思的轮次和严格程度。如果一个步骤反思了两轮才通过,那这一步的消耗就翻了三倍。所以 Reflection 不是越多越好,得有个上限控制,一般设置最多反思 2 到 3 轮就够了。

生产级 Agent 的组合策略

主流产品不会"只用一种范式",而是组合:

  • 入口判断:根据任务复杂度选择 ReAct 或 Plan-and-Solve

  • ReAct 主循环:大多数任务用 ReAct 式步进决策

  • Plan-and-Solve 子任务:结构性强的子任务(如"遍历这个目录找所有 .py 文件")用 Plan-and-Solve 派给 SubAgent

  • Reflection 关键节点:重要修改前触发反思,确认方向正确


六、真实产品的任务拆解机制

从公开资料来看,Claude Code 和 Codex CLI 的任务拆解是上述范式的工程化组合,而非单一范式的应用。

Claude Code:分层多 Agent + 动态 Steering

Claude Code 采用主 Agent(协调) + SubAgent(执行专项任务)的分层架构。主 Agent 接收用户请求后,判断是否需要派给 SubAgent:如果任务涉及代码搜索、文件遍历等探索性工作,就派给 Explore 类 SubAgent;如果任务涉及具体修改,就自己执行。

SubAgent 在独立上下文里执行子任务,只把结论返回给主 Agent。这种隔离设计避免了上下文污染——SubAgent 产生的大量中间观察不会塞满主 Agent 的上下文,只有高密度的结论被保留。

主 Agent 的决策方式是 ReAct 式的步进决策,但加入了实时 Steering机制:用户可以随时中断、调整方向,系统自动保存状态并无缝切换。这解决了传统 Agent "必须等完整执行结束才能调整"的痛点。

Codex CLI:Handoff 思维 + Session Memory 优先

Codex CLI 带来的一个观念转变是:压缩不是 Summary(总结发生了什么),而是Handoff(交接给下一个模型该做什么)。压缩 prompt 明确要求包含:"Current progress and key decisions made"、"What remains to be done (clear next steps)"——这是前瞻性的,而非回顾性的。

另一个关键是Session Memory 优先策略:Codex 会先检查结构化的 session memory(任务状态、文件编辑历史、关键决策)能否替代完整摘要(用简单的if判断,判断是否为空。或者Hash和diff计算文件完整性。或者逻辑检测,关键字段检测。必要字段检测)。大多数自动压缩走这条路径,根本不调 LLM——既省钱又快。只有 session memory 不够时才走服务端压缩。("基于当前结构化的任务状态,你觉得有没有信息缺失?只回答'是'或'否'。")


六、工程关键点:拉开差距的细节

任务拆解的范式只是骨架,真正拉开差距的是工具设计、停止条件、错误恢复、可观测性这些工程细节。

工具设计:找到 Goldilocks 区

工具不能太万能,也不能太碎。太万能的例子是"Terminal Tool 什么都管"——模型写管道命令rg | grep | sed,错误信息被管道吞掉,Agent 只看到一个空结果,无从判断是"真的没找到"还是"查找过程中出错了"。这就是"不可诊断"的致命问题。

太碎的例子是"每个功能点都拆成独立工具"——模型在第一步就卡住,不知道该用FindByNameFindByPattern还是Glob。选工具本身就消耗大量 attention。

正确的做法是找到Goldilocks 区:高频、强确定性的动作(如读文件、搜索、列表)拆成原子工具,每一步都有明确的输入、输出、状态;低频、弱确定性的动作(如复杂的 shell 操作)放到兜底层,明确禁止什么而非允许什么。

真实项目复盘:管道命令事故

我们团队在开发一个 Code Agent 时,曾踩过一个典型坑:给 Terminal Tool 开了绿灯,允许模型自由写管道命令。那天测试一个简单需求:"帮我搜一下process_data函数的定义"。

模型很快给出一条看起来挺专业的命令:

rg -n "def process_data" src/ | grep -v test | sed -n '1,50p'

但执行结果是空的。Agent 看到空结果后,开始各种补救:换搜索词、换目录、甚至怀疑是不是记错了函数名。三轮重试后,它放弃了,告诉我"我在仓库里没有找到这个函数"。

但我手动去仓库看了,那个函数明明就在src/utils/helpers.py第 42 行。排查后发现:启动 Agent 时的工作目录不是项目根目录,而是项目下的一个子目录。src/相对当前目录不存在,rg直接报错退出。但因为命令用了管道,错误信息被管道吞掉了——rg的错误输出没有传到 stdout,而是被导向了下一个命令的输入。Agent 只看到一个空字符串,根本不知道上游失败了。

这次事故消耗了27 步工具调用15 万 token,最后给出的是错误结论。修复方案是:禁止管道命令,把高频操作拆成原子工具(GlobGrepRead),每个工具都有明确的成功/失败状态码。修改后,同样任务只需要4 步调用2 万 token


停止条件:不能只靠模型判断

ReAct 循环不能无限跑下去。工程上必须硬编码:最大步数限制(如 20 步)、超时限制(如 5 分钟)、重复检测(如最近 3 步 Action 相同就强制切换策略)。这些"硬护栏"防止 Agent 陷入死循环或无限制消耗 token。

错误恢复:失败不是终点,是输入

工具调用失败不能让整个循环崩掉。正确做法是:把失败信息包装成 Observation,让 Agent 根据错误类型决定下一步(如换搜索词、换工具、请求用户澄清)。这把"失败"变成了"可处理的输入",而非"终止信号"。

可观测性:把黑盒变玻璃盒

Agent 的每一步都要有审计日志:调了什么工具、传了什么参数、返回什么结果、模型怎么推理的。没有这些日志,失败了排查不了,成功了也不知道为什么成功。这就是 Extra09 强调的"可观测性把黑盒变玻璃盒"。


七、总结:任务拆解的本质是动态调度

主流 Agent 的任务拆解,不是简单的"用户请求 → LLM 拆成子任务 → 执行",而是:

  • 入口判断:用户请求进来,先判断复杂度和任务类型

  • 简单请求:直接 ReAct 式步进决策,快速响应

  • 复杂请求:Plan-and-Solve 式规划,或派给 SubAgent 隔离执行

  • 执行中动态 Steering:用户可随时中断、调整方向

  • 关键节点 Reflection:质量要求高的环节加入反思修正

  • 上下文管理兜底:压缩、offload、子代理隔离,保证"工作记忆"不爆

真正拉开差距的不是"用哪种范式",而是工具设计(Goldilocks 区)、停止条件(硬护栏)、错误恢复(失败变输入)、可观测性(黑盒变玻璃盒)、上下文管理(工作记忆不爆)这些工程细节。

这也是为什么读"最佳实践"文章不够,必须看真实踩坑经验——前者告诉你"该怎么做",后者告诉你"做错了会怎样、怎么救回来"。


延伸阅读
本文的工程细节主要来自 hello-agents 的 Extra09《Agent应用开发实践踩坑与经验分享》,那里有管道命令事故、工具设计 Goldilocks 区、可观测性的真实案例。理论框架来自 hello-agents 第四章《智能体经典范式构建》,有 ReAct、Plan-and-Solve、Reflection 的完整实现代码。产品实践来自 AgentGuide 的《上下文工程:业界最佳实践精华》和 Claude Code 源码分析。