Agent 怎么做规划?这道面试题淘汰了 80% 的候选人
最近团队招人,面了不少做 Agent 开发的候选人,几乎每轮我都会问一个看似基础的问题:“Agent 是怎么做规划的?”就这道题,真实淘汰率在 80% 以上。不是大家不会说,而是很多人停留在“让模型自己想一想”“用了 ReAct 框架”这种非常表面的答案上,对规划机制本身没有系统的理解。
这篇文章就把我对 Agent 规划的全套认知写出来。会覆盖规划的常见模式、不同模式的适用场景和代码结构、面试和实际工程里真正会被追问的高频细节,以及我在多个实际项目里踩过和填过的坑。无论你是准备面试、带队做 Agent 项目,还是纯粹对框架内部感兴趣,这篇都值得认真读完。
1. 面试官真正想听什么:规划的本质拆解
1.1 大多数候选人死在哪一层
先还原一下面试现场最常见的回答,很多候选人会这样说:
“Agent 规划就是给大模型设定目标,然后让它自动拆解任务、调用工具,最后完成任务,一般用 ReAct 框架来做。ReAct 就是让模型思考然后行动,再根据结果继续思考。”
这段话说得对不对?对,但没有任何区分度,因为这是文档里直接能看到的定义。面试官想听的是更底层的逻辑——规划的本质是决策问题,即“在某个状态下,下一步到底应该做什么”。说得更直白一点:Agent 规划不是一个固定的流程,而是从状态到行动的映射规则,而不同 Agent 类型的差异,恰恰在于最新网络热词这套映射规则的设计方式不同。
另外还有一个高频误区是把任务分解当成规划的全部。任务分解确实很重要,但规划包含四个完整的环节:
| 环节 | 要解决的问题 | 常见误区 |
|---|---|---|
| 目标拆解 | 总目标如何被切分成一系列可执行的子目标 | 把拆解等同于规划 |
| 路径搜索 | 子目标之间是什么顺序、哪些可以并行 | 忽略依赖分析 |
| 资源与工具分配 | 每个子目标由哪个模型、哪个工具负责 | 不做工具能力画像 |
| 状态反馈 | 执行结果如何影响后续规划 | 只执行,不重规划 |
所以我会把规划拆解成这么一句话:根据当前环境和已有的观察结果,生成一个有序且可调整的行动列表,并在执行过程中不断修正这个列表。真正理解的候选人,会主动提到“反馈驱动规划”这个概念——规划不是一次性动作,而是每一步决策后都要更新状态、重新评估、迭代展开的闭环过程。
1.2 从人类规划类比理解 Agent 规划
大家天天都在做规划,人类规划本来就是一个动态过程。周末请朋友吃饭,你不可能提前把每一分钟都排满,而是先定一个时间框架,到菜市场看看有什么食材,再决定做哪几道菜。如果临时下雨不方便出门,你又得换方案。Agent 规划也是同样的逻辑:先有一个整体设想,执行过程中根据观测到的环境反馈不断调整,而不是一条道走到黑。
我经常用这个类比去解释规划和执行的边界。规划负责构建从“当前状态”到“目标状态”的路径图,执行负责沿路径图前进,反馈负责修正路径图。三者缺一不可。很多 Agent 之所以表现“又蠢又慢”,往往不是因为模型能力不行,而是规划层和执行层没有配合好:要么规划太粗导致执行过程频繁中断,要么规划太细导致一旦某个小步骤失败整个链路就崩了。
顺着这个思路往下走,就能自然推导出 Agent 规划的三大核心关注点:有效性(能否完成任务)、效率(在多少步、多少 token 内完成)、鲁棒性(遇到意外情况能否重新规划)。面试时你把这三个词说出来,就比干巴巴地背 ReAct 定义高出好几个层次。
2. 四种主流规划模式的原理、代码与适用边界
先明确一点,没有任何一种规划模式是万能的。不同的任务类型、工具环境、稳定性要求,直接决定了你该选哪种方案。下面逐一说清楚 ReAct、Plan-and-Execute、自反思规划和基于模型的显式规划器这四种最常见的选择。
2.1 ReAct:最经典,也是绝大多数 Agent 的起点
ReAct 的设计思想一句话就能说清:交替执行推理与行动,每一步先思考当前状态,再决定调用什么工具,然后根据工具返回结果继续思考。这个循环不断进行,直到问题被解决。它是一种准动态的贪婪规划器——动态体现在每一步都在重新决策,贪婪体现在它没有全局最优的概念。
用伪代码表达如下:
while not task_finished: thought = llm.generate_thought(state, tools) action = parse_action(thought) observation = execute_tool(action, tool_params) state = update_state(state, observation)核心优势是灵活,几乎不需要提前设计子任务流程,模型自己走一步看一步,什么任务都能上手。核心缺陷有三个:
第一,每一步都要做一次完整的大模型调用,长链路任务 token 消耗非常快,成本高企,而且延迟可能会到几十秒到分钟级。
第二,每一步决策只基于当前观察,容易陷入局部最优。比如流程走到中间发现某条路不太顺,它常常只会继续在当前语境里尝试变体,而不会跳回上一层重新审视整体路径。
第三,没有天然的全局路径视图,很难保证执行顺序的稳定性和依赖关系的准确性。
实际落地时,ReAct 适合“工具调用密集、每一步结果决定下一步方向”的任务,比如多轮信息检索、需要动态试探的环境。但一旦任务链条固定、步骤明确,用 ReAct 就是杀鸡用牛刀——又慢又贵还容易飘。
2.2 Plan-and-Execute:先谋后动,把规划和执行切开
Plan-and-Execute 把“想”和“做”分成两个独立的模块。规划器先把总目标拆成一条有序的子任务列表,执行器再逐步执行。执行过程中如果遇到错误,执行器会把情况反馈给规划器,规划器再重新生成新的计划。
这个结构的核心价值是:规划模型可以集中精力做全局分解,不受中间过程干扰;执行阶段的每次小决策也不像 ReAct 那样消耗完整推理链路。
伪代码可以这样理解:
plan = planner.generate_plan(task, available_tools) while plan is not None and not task_finished: step = plan.get_current_step() result = executor.execute(step) if result.success: plan = verify_progress(plan, result.critical_info) else: plan = planner.regenerate_plan(task, last_error=result.error, previous_plan=plan) plan.mark_done(step)实际项目中,我喜欢在规划器里同时输出多层信息来实现更好的控制节奏,不止每天返回以下内容:
{ "steps": [ { "step_id": 1, "description": "收集用户输入需求并做关键词抽取", "tool": "info_parser", "depends_on": [], "success_criteria": "关键词列表完整覆盖所有需求" }, { "step_id": 2, "description": "搜索相关数据并进行初筛", "tool": "search_api", "depends_on": [1], "success_criteria": "获得五条以上相关结果" } ] }这个 JSON 结构里我特别强调两个字段:depends_on用来标识任务的依赖顺序,success_criteria用来判断是否需要重规划,这两个字段是 Plan-and-Execute 方案能落地的基础,缺了它们就只剩一张空计划表。
当然缺点也很明显:如果任务本身不确定性太强,第一条计划生成的质量就不高,后续反复重新规划反而比直接用 ReAct 更慢。所以这种方式最适合“结构清晰、工具固定、执行路径相对明确”的任务。比如文档批处理,需要按固定流程做格式转换、内容摘要、关键词提取,Plan-and-Execute 就是稳定高效的选择。
2.3 反思与自我修正:从失败中动态迭代
这种模式可以理解为 ReAct 的升级版,核心区别是引入了独立的反思器(Reflector)。执行器每完成一轮行动后,反思器专门负责审查当前执行结果和目标之间是否存在偏差、已经走过的路径里有没有不合适的选择,以及下一步应该怎么调整。
从规划视角看,反思器扮演了一种“显式验证器 + 策略修改器”的角色,它不直接产生动作,而是修正状态评估和路径决策。
实现上通常是一个独立 Prompt 加上额外的模型调用,它专门分析任务的进行情况,确认当前方向是否偏离目标原意,给出质量分数。这个分数会被拼接到 ReAct 循环的状态消息里,作为下一轮决策的输入,同时也会触发重新规划机制:
execution_history = [] status_feedback = "针对当前状态,需要继续执行或调整计划" for step in range(max_steps): reflection = llm.reflect(execution_history, objective, current_plan) if reflection.is_complete: break if reflection.needs_replan: current_plan = llm.generate_plan(objective, reflection.new_direction) action = llm.choose_next_action(reflection, status_feedback) observation = execute(action) execution_history.append((action, observation))这个模式在那些“失败率较高且失败信息有价值”的场景里表现出色。比如自动化测试、数据处理管线这类任务,第一次执行往往会产生大量异常日志,反思器能基于这些日志重新调整测试策略或清洗策略,效果比单靠一两次 ReAct 循环好很多。
它的主要成本是多一次大模型调用,而且如果反思器设计得过于频繁,很容易把正常的执行过程打断,带来额外的延迟和不稳定。
2.4 基于模型的显式规划器:全链路可控的工业级方案
工业级 Agent 系统里,最常用的其实是基于显式模型的规划器。所谓显式规划器,就是提前为特定任务手写一套完整的行动编排逻辑,让大模型只负责局部的理解和决策,而关键流程节点完全由代码控制,不允许模型自行发挥。
举例来说,在自动化报表生成系统里,任务总是那么几步:收集数据源,清洗数据,生成图表,输出报告。我可能写一个明确的工作流调度器,周期性地执行这些步骤。每个步骤里模型能力被限制在“读取总结某个列的数据”“判断图表类型”这类局部小任务里,而整体流程只有代码能指挥。
这种方式的优势是极致稳定、可控、易排查。而它的问题在于一旦规则之外的新情况出现(比如数据格式变了、数据结构变了),模型很难自行跳脱规则,需要人工介入调整流程。所以它是一种针对特定领域的高效选择。
面试中如果能区分这四种模式,并清楚说出各自的使用边界和取舍标准,就已经超越了绝大多数候选人了。好,这是在方法层面的理解。接下来我要把维度拉高,重点讲面试里更常问的深度问题:所有任务都适合用完整规划吗?规划粒度怎么定?这是区分初级和资深开发者的分水岭。
3. 高级视角:粒度控制、递归规划与成本约束
3.1 所有任务都规划,是最大的幻觉
初学者最容易犯的毛病,就是拿到一个 Agent 需求就自动切到“规划模式”,不管任务简单还是复杂,都要先生成一个多步骤计划。我见过一个同事做知识库问答 Agent,用户问“今天天气怎么样”,Agent 也走一遍目标拆解 + 工具搜索 + 结果验证的完整流程,结果是延迟高、token 浪费严重,而且回答质量并没有更好。
根本原因在于:规划本身有成本,越精细的规划,成本越高。一个任务如果本身步骤少、确定性高、工具单一,直接一步推理 + 一步执行就好。只有复杂任务、涉及不确定推理的任务、需要多轮工具协作、需要交叉验证的任务,才值得显式规划。这就是“按需规划”的核心思想。
我在实际项目里对规划做过分级:
| 复杂度级别 | 规划策略 | 适合场景 |
|---|---|---|
| 低 | 单轮推理 + 直接执行 | 简单问答、固定格式提取 |
| 中 | ReAct 动态推进 | 信息检索、轻度工具调用 |
| 高 | Plan-and-Execute | 流程明确的多步任务 |
| 极高 | 分层/递归规划 + 反思 | 复杂现场、跨工具、需反馈调整的长任务 |
面试时如果能说出这层认知,意味着你不是只会套框架,而是真正在工程实践里思考过成本收益。
3.2 递归规划:处理复杂任务的关键思路
更高阶的思路是递归规划。把大任务递归拆解成子任务,直到某个子任务能被某个单一工具或模型直接解决为止。这个方法之所以强,是因为它天然适配复杂任务的层级结构,也能让规划器在每一层都只需要做当前层级的小决策。
举个例子,做一个年度数据审计项目。顶层规划器把任务拆成“数据接入、数据清洗、指标计算、异常检测、报告生成”五个子任务。进入“数据清洗”子任务后,下层规划器又继续拆成“格式统一、缺失值处理、类型校验、重复项剔除”这些更小的操作。每一层的规划都只面对有限的选择,这让大模型的单次决策质量显著提高。
代码层面可以用树结构来组织:
class PlanNode: def __init__(self, task_desc, subtask_nodes=None): self.task_desc = task_desc self.subtask_nodes = subtask_nodes or [] self.result = None def recursive_execute(agent, node, context): if not node.subtask_nodes: return agent.execute_leaf(node, context) context = planner.prepare(node, context) for subtask in node.subtask_nodes: subtask.result = recursive_execute(agent, subtask, context) return agent.merge_results(node, node.subtask_nodes)这种结构的核心是:每一层都在做“匹配、决策、聚合”,与传统的单层线性规划相比,大大提升了处理复杂逻辑的效率和稳定性。我在做一个跨平台数据同步 Agent 时就用了这套递归框架,最终执行步数从原来的二十八步压缩到十二步左右,而成功率反而提高了不少。
不过它的代价也很明显,需要提前界定好任务的层级边界,否则容易造成规划过度细化、子任务之间的数据传递混乱。这个问题我后面在实操章节会仔细说。
3.3 规划成本控制:如何在质量和效率之间做权衡
任何系统设计里都有一个铁律:延迟和成本是用户能感知的硬指标。Agent 规划讲究的是把有限的推理预算花在最有价值的地方。
我在实际项目里常用三种控制手段:
第一,设定最大规划深度。比如普通任务限制在两层递归内,超过就自动折回。这里建议用步数和递归深度两个维度同时限制,有效避免无限递归的风险。
第二,设定反思和规划的触发条件。不是任何一步出错都触发整体重规划,而是区分局部修复与全局重规划。局部错误优先让模型重试当前步骤,只有连续三次局部错误才启动重规划。通过在错误处理环节中加入计数器和轻微的状态调整,能显著降低无谓的规划开销。
第三,对工具的调用顺序做预排序。通过提前感知工具依赖关系,避免模型在多个无依赖工具之间反复横跳,容易出错也容易跑偏。
还有一个小技巧是流式输出计划。规划器不是一次性输出全部内容,而是按步骤逐个输出,同时执行线程已经开始处理前面的步骤。这种生产者—消费者模式能把端到端延迟降到最低。
聊完这些高级视角,下面进入更贴近实战的部分:真正动手做 Agent 规划模块时,有哪些看得见摸得着的设计选择和代码细节。
4. 实战:把规划模块真正集成进 Agent 系统
4.1 状态表示:规划器的“操作台”
状态表示决定了规划器能不能“看懂”当前进度。很多失败的 Agent 项目,问题根源不是模型不够聪明,而是状态表示太乱——模型根本不知道现在已经走到哪一步了。
我推荐一个结构化的存储方案,用一个 Python dataclass 建立一个全局状态对象:
from dataclasses import dataclass, field from typing import List @dataclass class AgentState: user_query: str plan: List[dict] = field(default_factory=list) current_step_index: int = 0 execution_history: List[dict] = field(default_factory=list) global_context: dict = field(default_factory=dict) tool_results: List[str] = field(default_factory=list) def get_current_step(self): return self.plan[self.current_step_index]execution_history是模型决策和工具结果的完整轨迹,global_context存放跨步骤的关键信息,tool_results是用来验证状态的关键数据。每一步执行后更新状态,让整个生命周期清晰可控。
4.2 工具注册与能力画像:规划器必须知道“手上有哪些牌”
规划器光知道目标没用,它必须知道有哪些工具、每个工具能做什么、有什么输入输出限制。没有工具画像的规划器,就像让一个大厨凭空设计菜单,却不知道厨房里有什么食材。
我在工程里会建立一个工具注册表,把所有可执行函数包装成统一的接口:
tool_registry = {} def register_tool(tool_name, desc, input_schema, output_schema, fn): tool_registry[tool_name] = { "name": tool_name, "desc": desc, "schema": input_schema, "output_schema": output_schema, "fn": fn } def available_tools_prompt(): lines = [] for name, spec in tool_registry.items(): lines.append(f"- {name}: {spec['desc']},输入:{spec['input_schema']},输出:{spec['output_schema']}") return "\n".join(lines)input_schema这里不是简单要求一个 JSON 类型,而是要告诉规划器具体的字段和含义。比如搜索工具不要只写“输入为字符串”,而要写明“输入为关键词列表,每个关键词之间用空格分隔”。这些细节直接决定规划器能不能生成合法的工具调用参数。工具能力画像是经常被忽略但实际价值极高的细节,它直接决定 Agent 能不能正确选对工具。
4.3 反馈回路:让规划器知道自己做得好不好
一个完整的规划器必须拥有“感知执行结果并自我修正”的能力,也就是前面提到的 verify_progress 模块。它的本质是动态评估当前状态和目标之间的差距。
通常我在每一步执行完成后调用 verify_progress 进行验证,把执行结果和预期的成功标准进行比较,发现偏差就决定是否触发重规划。如果成功标准模糊,这个反馈回路就等于空转。所以成功标准的设计需要足够具体,比如“结果必须是 JSON 数组且长度大于 5”,而不该是“看起来差不多”。
如果验证发现关键信息缺失,验证器会决定缺失的部分是否可以重新获取。若依赖项足以支撑,就只更新 context 并继续推进主线;如果不满足依赖,就触发重新规划当前阶段。这个设计比“遇错就重来”稳健得多,也是工程落地和实验室 Demo 的分水岭。
4.4 可观测性:给规划器装上“仪表盘”
Agent 在开发阶段最痛苦的事是出了问题不知道在哪一环出了错。所以可观测性是必做的工程化配置,而不是可有可无的选项。我会记录整个系统的完整轨迹,用结构化日志把规划决策、动作内容、工具结果、反思结果都存起来:
{ "step": 3, "thought": "用户需要分析销售数据,先提取必要字段", "action": "extract_columns", "action_input": "data/sales_monthly.csv, columns=[revenue, cost, month]", "observation": "成功提取三个字段,共 1200 行", "success": true, "next_plan": "继续计算毛利率" }日志是后期定位问题的核心资料,同时也能用来做回归测试,分析 Agent 在哪些步骤最容易出错,反思器的质量如何,然后再针对性优化。这些信息比“凭感觉调 Prompt”要有用得多。
5. 高频失误与排查实录
5.1 三个最常见的设计错误
结合我接触过的项目和团队反馈,Agent 规划模块最常见的问题集中在三个方面:
第一个错误是重新规划频率毫无限制。执行器一遇到失败就触发重规划,结果整个 Agent 在无限循环里空转。差的设计就是模型输出 JSON 里出现一个小字段错误就触发全局重规划,步数无限累加,最终导致成本指数级上升。好的设计会先做轻量修复,或对失败做升级处理,连续多次失败才进入重规划流程。
第二个错误是重规划没有版本意识。新计划生成后,旧计划里的有用信息和已完成的成果被直接丢弃,导致重复劳动。我习惯在切换计划前把旧计划执行到的中间结果和分析结论保存到 global_context 装置中,保证新计划直接接管这些成果。
第三个错误是工具参数缺少系统性校验。你必须提前判断模型可能输出的非法参数,比如不存在的字段名、不支持的格式。我的做法是写一个轻量级 pydantic 校验层,在工具调用前统一做字段校验,不符合规则就直接返回结构化错误信息给规划器,让规划器修正参数,而不是把一串堆栈报错丢给模型。
5.2 定位问题的排查思路
Agent 规划模块出问题时的定位方法与普通后端开发完全不同。普通研发看日志找异常,Agent 问题往往没有异常可看,只有“行为和预期不符”。
我的排查工序是分层的。先确认哪个环节失败。Model 决策是否正确,Tool 执行是否报错,还是规划器自身逻辑有误。然后针对失败环节定位。如果是模型决策错,调整 Prompt;如果思考对但工具调用参数错,检查工具 schema 描述是否清晰;如果工具返回结果之后规划器不会解读,则检查观察结果格式化模块。
实际案例说一个。某次我在做批量数据处理 Agent,用户给的原始文件结构不统一。第一版 Agent 规划器循环调用解析工具,但每次都会重新解析同一个文件,反复报错,最后进入死循环。排查后发现,问题根本不在大模型,而在于我这里的一个状态更新 bug——文件解析成功后我没有把这个状态写回 execution_history,导致规划器永远觉得“没有解析过”。修掉状态同步问题后,整个流程就顺了。
这个案例说明一个重要的排查原则:Agent 出问题不要一开始就去调 Prompt,先检查状态管理、工具返回、日志记录这些工程基础。
5.3 面试里必须避开的三个表达误区
第一个误区,把“给模型一大段指令”叫规划。指令只是输入,规划是模型对目标的决策过程,两者不是一回事。
第二个误区,把“任务拆解成列表”叫规划。列表只是输出形式,真正的规划在于拆解的策略、顺序控制和反馈机制。
第三个误区,把“规则引擎”或“工作流”直接当 Agent 规划。规则引擎是预定义的静态流程,Agent 规划是动态决策过程,核心区别在于是否有根据中间结果修改行动路径的能力。
面试时表达的重点,永远要落到“动态规划”“反馈调整”“成本约束”这三个概念上。哪怕你只是做过一个 Demo,也要能够让面试官感知到你对这套系统背后的计算逻辑有真正的理解。
6. 从面试到落地,完整的技术栈选型与参考建议
6.1 框架选型:项目导向还是抽象底层
在实际做 Agent 规划系统时,你首先面临的选型问题是:是用抽象框架(比如 LangGraph、LlamaIndex 这类现成的 Agent 编排框架),还是直接手写自己的规划核心?
我的建议很明确:如果你做的是业务原型验证,那么直接用成熟框架没问题,可以帮你快速搭建流程。但如果你做的是需要反复打磨、长线运营的系统,那核心规划模块必须由自己掌控。原因在于框架封装了太多“你不知道它在干什么”的逻辑,一旦出了问题,你只能看见高层报错,却难以定位内部状态。
手写规划核心并没有想象中那么困难。只要把状态管理、工具注册、规划器、验证器、反思器这几个模块划分清楚,一个几千行代码的主干就能支撑基本生产需求。同时这种结构灵活性极高,你可以随时替换模型、替换工具、调整策略。
6.2 模型选择对规划能力的影响
规划质量不仅取决于架构,也直接取决于你选择什么模型来承担规划器角色。
目前主流方案是用推理能力较强的模型做规划器,比如大参数旗舰模型,而执行器可以用更轻量、便宜的模型。因为规划器是“智力集中点”,属于高价值决策,选强模型值得;执行器是“体力劳动点”,动作简单重复,选轻量模型也不会影响输出质量,还能节省成本。
反思器则建议选一个输出可靠、稳定性高的模型。反思逻辑本身相对固定,不需要很强的创造力,而是需要持续稳定地输出有效判断。
一个工程里全部环节都用同一模型,固然简单,但成本和延迟都偏高。多个模型分场景组合使用,是规划系统迈向工程化的关键优化,也是资深开发者体现架构能力的关键细节。
6.3 落地阶段最好先做的一组最小验证用例
无论面试还是做项目,建议先在桌面构建一个最小验证系统。用一个查询分析规划器,接收用户的原始输入,输出一个结构化的执行计划。我建议的第一个用例是时间敏感的信息搜索场景,比如“帮我查一下某行业过去三个月的趋势数据”,这个用例能验证规划器在什么时候用搜索工具、什么时候直接推理、什么时候交叉验证信息,在评估规划能力时非常有用。
具体流程可以设定为三步:大模型判断意图,检索实时数据,对比与分析。只要这套简单逻辑跑通,然后再逐渐加入复杂的任务类型、更多工具和反思机制,你的规划系统就会像滚雪球一样越来越完整。我个人在实际项目里体会最深的一条——不要在第一次就追求完美规划,而是先把一个简单任务走顺,再逐步加复杂度。任何“高级规划框架”都改变不了这一点:Agent 的可靠性和工程成熟度,完全靠迭代堆出来,想一步到位只会给自己挖坑。
最后再分享一个亲测好用的细节:给规划器的 Prompt 里加上“为了确认自己是否走在正确的道路上,先列出三个验证条件,每一步完成后检查是否满足”,这个简单的思路,比加任何复杂机制都能更快地提升 Agent 的稳定表现。类似的工程细节,往往就是在一次次调试中慢慢沉淀下来的。祝各位面试顺利、项目少踩坑。