news 2026/10/20 7:56:48

从Planner到Reflection:AI Agent架构设计与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从Planner到Reflection:AI Agent架构设计与工程实践

1. 为什么单靠一个LLM调用撑不起真正的Agent

很多人第一次接触AI Agent,脑子里浮现的画面是这样的:用户输入一句话,模型思考一下,然后调用工具,返回结果。听起来很顺,但真把它跑起来就会发现,一个裸的LLM调用和真正的Agent之间,差着一整套架构。

我刚开始做Agent相关项目的时候,也踩过这个坑。当时觉得只要给模型挂上几个工具函数,它就能自己规划、自己执行、自己纠错。结果实际跑起来,模型要么在第一步就选错工具,要么执行到一半发现参数不对却不知道怎么回头,要么反复调用同一个接口陷入死循环。问题不在于模型不够聪明,而在于我们只给了它"一次思考"的机会,却没有给它"规划、执行、观察、反思"的完整闭环。

这就是Planner、Tool Use、Memory、Reflection这几个模块存在的意义。它们不是花哨的概念堆砌,而是为了解决LLM在实际任务中暴露出来的几个硬伤:没有全局规划能力、没有外部世界交互能力、没有跨轮次记忆能力、没有自我纠错能力。把这四件事补齐,一个Agent才算真正能干活。

这篇文章我会把Agent架构从Planner到Reflection的完整链路拆开讲,包括每个模块解决什么问题、内部怎么设计、模块之间怎么衔接、实际落地时哪些地方最容易翻车。适合已经写过基础LLM应用、想往Agent方向深入的人,也适合正在设计Agent系统、需要一份架构参考的开发者。

2. Planner模块:Agent的"总指挥"到底怎么设计

2.1 Planner的本质是把模糊目标翻译成可执行步骤

Planner是整个Agent的入口。用户给的目标通常是模糊的,比如"帮我分析一下这份销售数据并生成报告"。这句话里没有说用什么工具、分几步、每步输出什么。Planner要做的就是把这个模糊目标拆解成一系列有序的、可执行的子任务。

我见过两种常见的Planner实现思路。一种是一次性规划,让模型在开始执行前就把所有步骤列出来,形成一个完整的任务列表。另一种是增量规划,模型只规划下一步做什么,执行完根据结果再决定下一步。两种方式各有适用场景。

一次性规划适合步骤明确、依赖关系清晰的任务,比如"查询数据→清洗数据→计算指标→生成图表→写报告"这种线性流程。它的好处是全局视角清晰,模型能看到完整路径,不容易跑偏。坏处是一旦中间某步的实际结果和预期不符,整个计划可能就废了,需要重新规划。

增量规划适合探索性任务,比如"帮我找一下这个bug的原因"。你不可能一开始就知道要查哪些日志、看哪些代码。这种场景下,模型需要根据每一步的观察结果动态调整方向。坏处是容易陷入局部最优,走着走着忘了最初的目标。

实际项目中,我通常采用混合策略:先做一次粗粒度的高层规划,把任务分成几个大阶段,然后在每个阶段内部做增量规划。这样既有全局方向,又有局部灵活性。

2.2 任务分解的粒度控制是个技术活

Planner最容易出问题的地方是分解粒度。分得太粗,每个子任务还是太模糊,执行模块不知道怎么做。分得太细,步骤数量爆炸,模型在长上下文里容易丢失关键信息,而且每步之间的衔接成本很高。

我的经验是,单个子任务的复杂度应该控制在"一次工具调用或一次LLM推理能完成"的范围内。比如"查询上个月的销售数据"是一个合适的粒度,"分析销售趋势"就太粗了,应该拆成"查询数据→按周聚合→计算环比→识别异常点"。

还有一个实用技巧:让Planner在输出每个子任务时,同时输出这个子任务的预期输出格式和完成条件。这样执行模块知道要产出什么,Reflection模块也有了一个判断"这步做完了没有、做对了没有"的依据。

# Planner输出的子任务结构示例 { "step_id": 1, "description": "查询2024年1月的销售数据", "tool": "database_query", "expected_output": "包含日期、销售额、订单数的结构化数据", "completion_criteria": "返回至少一条记录,且字段完整" }

这个结构看起来简单,但它让后续所有模块都有了明确的对接点。没有这个结构,执行模块只能靠猜,Reflection模块也无从判断对错。

2.3 规划失败时怎么让Agent自己找台阶下

Planner不是万能的。有时候用户给的目标本身就矛盾,有时候任务需要的工具根本不存在,有时候规划出来的步骤在逻辑上就是死路。这时候如果Agent直接报错退出,体验就很差。

我的做法是在Planner后面加一个可行性校验层。这个校验层做三件事:检查每个子任务需要的工具是否可用、检查子任务之间的依赖关系是否有环、检查是否存在明显无法满足的前置条件。如果校验不通过,不是直接失败,而是把校验结果反馈给Planner,让它重新规划。

这个"规划→校验→重新规划"的循环最多跑两到三轮。如果还是不行,Agent就应该诚实地告诉用户"这个任务我目前完成不了,原因是XXX",而不是硬着头皮往下走然后产出一堆垃圾结果。

3. Tool Use:Agent的手脚,也是最容易翻车的地方

3.1 工具描述的质量直接决定调用准确率

Tool Use模块的核心是让模型知道"有哪些工具可用、每个工具干什么、怎么调用"。听起来简单,但工具描述写得好不好,直接决定了模型选工具的准确率。

我见过很多项目,工具描述就写一句"查询数据库",然后模型就懵了:查哪个库?传什么参数?返回什么格式?最后模型只能瞎猜,调用失败率极高。

一个好的工具描述应该包含这几个要素:功能说明、参数列表(含类型和是否必填)、返回值格式、使用示例、以及什么时候不该用这个工具。最后一条特别重要,因为模型经常会在不合适的场景下调用某个工具,如果你明确告诉它"这个工具只适用于查询历史数据,不适用于实时数据",它就会少犯很多错。

tools = [ { "name": "query_sales_data", "description": "查询指定时间范围内的销售数据。仅适用于已入库的历史数据,实时数据请使用get_realtime_sales。", "parameters": { "start_date": {"type": "string", "required": True, "description": "开始日期,格式YYYY-MM-DD"}, "end_date": {"type": "string", "required": True, "description": "结束日期,格式YYYY-MM-DD"}, "granularity": {"type": "string", "required": False, "enum": ["day", "week", "month"], "default": "day"} }, "returns": "包含date, sales_amount, order_count字段的数组" } ]

3.2 参数校验和错误处理不能全甩给模型

模型生成工具调用参数的时候,经常会出现格式错误、类型错误、必填项缺失这些问题。如果你直接把模型生成的参数扔给工具函数,大概率会抛异常。

我的做法是在模型和实际工具之间加一层参数校验与修复层。这层做几件事:检查必填参数是否齐全、检查参数类型是否匹配、对常见格式错误做自动修复(比如日期格式从"2024/1/1"修成"2024-01-01")、如果修复不了就把具体错误信息返回给模型让它重新生成。

这层看起来是额外工作量,但它能把工具调用的成功率从百分之六七十提升到百分之九十以上。而且它把错误处理逻辑从模型的黑盒里拿出来,变成了可调试、可监控的代码。

3.3 工具调用的超时、重试和熔断策略

实际生产环境里,工具调用失败是常态。网络抖动、下游服务限流、数据库慢查询,这些都会导致工具调用超时或失败。如果Agent没有处理这些情况的能力,整个流程就会卡死。

我通常会给每个工具配置三个参数:超时时间、最大重试次数、熔断阈值。超时时间根据工具的实际响应时间分布来定,一般设成P99响应时间的两倍。重试次数设2到3次,采用指数退避策略。熔断阈值是指连续失败多少次后暂时禁用这个工具,避免Agent在一个已经挂掉的服务上反复撞墙。

这些策略要写在工具调用的封装层里,而不是让模型去决定要不要重试。模型没有实时监控能力,它不知道下游服务是不是挂了,这些判断必须由代码来做。

4. Memory:让Agent不再"金鱼记忆"

4.1 短期记忆和长期记忆要分开设计

Agent的Memory分两种:短期记忆和长期记忆。短期记忆是当前任务执行过程中的上下文,包括已经执行的步骤、每步的结果、当前的中间状态。长期记忆是跨任务、跨会话积累的知识和经验。

短期记忆的实现相对直接,就是把执行历史按顺序存下来,每次调用LLM时把相关的历史片段塞进上下文。但这里有个坑:上下文长度是有限的,任务步骤一多,历史记录就会超出窗口。我的做法是对短期记忆做分层压缩:最近的几步保留完整信息,较早的步骤只保留摘要和关键结果,更早的步骤只保留结论。

长期记忆就复杂一些。它需要解决"存什么、怎么存、怎么取"三个问题。存什么:我通常存三类东西——成功的任务执行路径、失败的原因和修复方法、用户偏好和领域知识。怎么存:用向量数据库做语义索引,同时保留结构化的元数据(任务类型、时间、成功率等)。怎么取:根据当前任务的目标和上下文,检索最相关的历史记忆。

4.2 记忆检索的相关性判断比存储更难

存储记忆不难,难的是在需要的时候找到对的记忆。我试过纯向量相似度检索,效果不稳定。有时候检索出来的记忆表面上和当前任务很像,但实际上场景完全不同,硬套过来反而误导模型。

后来我改成多路召回加精排的策略。第一路用向量相似度召回一批候选,第二路用关键词匹配召回一批,第三路用任务类型和工具组合做结构化过滤召回一批。三路结果合并后,再用一个小的LLM做精排,判断每条记忆对当前任务是否真的有参考价值。

这个精排步骤很关键。它相当于一个"记忆过滤器",把那些看起来相关但实际上没用的记忆挡在外面。实测下来,加了精排之后,记忆对任务成功率的提升从百分之十几涨到了百分之三十左右。

4.3 记忆的更新和遗忘机制

记忆不是只进不出的。如果只存不删,记忆库会越来越大,检索效率越来越低,而且过时的信息会干扰判断。所以需要一套更新和遗忘机制。

我的做法是给每条记忆打一个时效分和置信分。时效分随时间衰减,越老的记忆分数越低。置信分根据这条记忆被引用后的实际效果来调整——如果一条记忆被检索出来用了,任务成功了,置信分就调高;如果用了之后任务失败了,置信分就调低。检索的时候,综合分数低于阈值的记忆就不参与召回。

另外,对于同一类任务的多条相似记忆,我会做合并压缩,把共性提取出来形成一条更通用的记忆,而不是保留一堆重复的碎片。这样既节省存储,又提高检索质量。

5. Reflection:Agent的自我纠错能力从哪来

5.1 Reflection不是简单地把结果再喂给模型看一遍

很多人对Reflection的理解是:任务执行完了,把结果扔给模型,问它"做得对不对"。这太浅了。真正的Reflection应该是一个结构化的自我评估和修正流程。

我设计的Reflection模块包含四个环节:结果校验、过程复盘、错误归因、策略调整。结果校验是看最终产出是否满足任务目标。过程复盘是回看每一步的执行轨迹,找出哪一步偏离了预期。错误归因是分析偏离的原因——是规划错了、工具选错了、参数传错了、还是外部环境变了。策略调整是根据归因结果,决定是重试、换方案、还是放弃。

这四个环节里,错误归因是最有价值的。因为只有知道了"为什么错",才能避免下次犯同样的错。如果只是知道"错了"但不知道为什么,那Reflection就退化成了简单的重试,价值有限。

5.2 用"预期-实际"对比来驱动反思

Reflection要有效,必须有一个对比的基准。这个基准就是Planner在规划阶段输出的预期输出和完成条件。执行完之后,把实际结果和预期做对比,差异在哪里,差异有多大,差异是否可接受,这些就是Reflection的输入。

我在实现的时候,会让Reflection模块输出一个结构化的反思报告:

{ "task_id": "task_001", "overall_status": "partial_success", "step_reflections": [ { "step_id": 1, "expected": "返回2024年1月的销售数据", "actual": "返回了2024年1月的数据,但缺少订单数字段", "deviation": "字段缺失", "root_cause": "数据库查询语句中漏选了order_count字段", "suggested_fix": "修改查询语句,补充order_count字段", "severity": "medium" } ], "overall_lesson": "数据库查询类工具调用后,需要校验返回字段是否完整" }

这个报告不只是给当前任务用的,它还会被写入长期记忆,作为以后类似任务的参考。这样Agent就真的能从错误中学习了。

5.3 反思的时机:事后反思和过程反思要结合

Reflection不一定要等到整个任务结束才做。我在实际项目里会把Reflection分成两种:过程反思和事后反思。

过程反思是在每个关键步骤执行完后立即做的。比如工具调用返回了异常结果,或者某一步的输出明显不符合预期,这时候就触发一次轻量级的反思,判断是继续、重试还是调整计划。这种反思要快,不能拖慢整体流程。

事后反思是在整个任务结束后做的,更全面、更深入。它会回顾整个执行链路,分析成功和失败的模式,提取可复用的经验。这种反思可以慢一点,因为它不影响当前任务的执行,但它的产出会影响未来的任务。

两种反思结合,Agent既有实时纠错能力,又有持续进化能力。

6. 四个模块怎么串成一个闭环

6.1 完整执行链路的分步拆解

把Planner、Tool Use、Memory、Reflection串起来,一个完整的Agent执行链路是这样的:

  1. 接收任务:用户输入目标,Agent初始化任务上下文。
  2. 记忆召回:从长期记忆中检索与当前任务相关的历史经验和知识。
  3. 规划:Planner结合任务目标和召回的记忆,生成执行计划。
  4. 可行性校验:检查计划中的工具是否可用、依赖是否满足。
  5. 逐步执行:按计划执行每一步,调用工具,获取结果。
  6. 过程反思:每步执行后做轻量级校验,异常时触发调整。
  7. 记忆更新:把执行过程中的关键信息写入短期记忆。
  8. 事后反思:任务结束后做全面复盘,提取经验。
  9. 长期记忆写入:把反思产出的经验写入长期记忆,供未来使用。

这个链路不是单向的,中间有很多反馈回路。比如过程反思发现计划有问题,会回到规划阶段重新规划。记忆召回发现当前任务和历史任务很像,会直接复用历史路径。

6.2 模块之间的数据契约要定清楚

多模块协作最容易出的问题是接口不清晰。Planner输出的格式和Tool Use期望的格式不一致,Reflection需要的字段Memory没存,这些问题在联调的时候会浪费大量时间。

我的经验是,在动手写代码之前,先把模块之间的数据契约定下来。每个模块的输入是什么、输出是什么、字段有哪些、类型是什么,全部写成接口文档。这样每个模块可以独立开发和测试,联调的时候问题也容易定位。

比如Planner的输出契约、Tool Use的输入输出契约、Memory的读写契约、Reflection的输入输出契约,这四个契约定清楚了,整个系统的骨架就稳了。

6.3 闭环的"闭合度"决定了Agent的上限

四个模块都有,不代表闭环就闭合了。闭环的闭合度取决于反馈信息能不能真正影响后续行为。

我见过一些Agent,Reflection模块产出了反思报告,但报告就躺在日志里,没有任何模块去消费它。这种就是"假闭环"。真正的闭环是:Reflection的结论会更新Memory,Memory的内容会影响Planner的规划,Planner的规划会改变Tool Use的行为。信息在整个链路里流动起来,Agent才会越用越聪明。

衡量闭合度的一个简单指标是:同一个错误会不会犯第二次。如果Agent在同一个类型的任务上反复犯同样的错,说明反思结果没有真正回流到决策环节,闭环是断的。

7. 落地时最容易踩的几个坑

7.1 上下文膨胀导致模型"失忆"

Agent执行步骤一多,上下文里塞的东西就越来越多:历史步骤、工具返回、记忆片段、反思报告。很快上下文就爆了,模型开始丢失关键信息,表现急剧下降。

我的应对策略是主动做上下文管理,而不是等它爆了再处理。具体做法:给每类信息分配一个token预算,超出预算就触发压缩。压缩不是简单截断,而是用LLM做摘要,保留关键信息,丢弃冗余细节。另外,不是所有历史步骤都需要保留在上下文里,已经完成且结果已存入Memory的步骤,可以从上下文中移除,只保留一个引用。

7.2 工具调用的"幻觉参数"

模型有时候会生成看起来合理但实际上不存在的参数。比如工具只接受"day/week/month"三个值,模型传了个"quarterly"。或者工具需要的是用户ID,模型传了个用户名。

这种问题靠prompt很难完全避免。我的做法是在参数校验层做严格校验加友好报错。校验不通过时,不是简单地说"参数错误",而是明确告诉模型"granularity参数只接受day、week、month三个值,你传的quarterly不在允许范围内,请重新生成"。模型看到具体的错误信息,修正的成功率会高很多。

7.3 反思过度导致"分析瘫痪"

Reflection是好事,但过度反思会让Agent陷入"分析瘫痪"。每一步都反复检查、反复调整,任务推进极慢,用户体验很差。

我的经验是给Reflection设触发条件和预算。不是每步都做深度反思,只有满足特定条件时才触发:比如工具调用失败、输出格式不符合预期、连续两步结果矛盾。而且每个任务的反思次数有上限,超过上限就强制推进或终止,避免无限循环。

7.4 记忆污染导致错误传播

长期记忆里如果存了错误的经验,后续任务会反复被误导。比如某次任务因为特殊原因失败了,Agent把失败原因归因错了,这个错误归因被存入记忆,下次遇到类似任务时就会做出错误的规避动作。

防止记忆污染的关键是给记忆加置信度和时效性,并且定期做记忆审计。置信度低的记忆不参与召回,时效性过期的记忆自动降权。另外,重要的决策不要只依赖单条记忆,而是综合多条记忆做判断,降低单条错误记忆的影响。

8. 关于Agent架构的一点个人体会

做Agent架构这段时间,我最大的感受是:Agent的智能不在于单个模块有多强,而在于模块之间的信息流动有多顺畅。Planner再聪明,如果Tool Use执行不了,也是白搭。Reflection再深刻,如果结论回流不到决策环节,也是摆设。

另一个体会是,不要追求一步到位的完美架构。我一开始就想把四个模块都做到很完善,结果发现模块之间的接口反复改,进度很慢。后来改成先跑通最小闭环——Planner出简单计划、Tool Use调一两个工具、Memory先不做、Reflection只做结果校验——跑通之后再逐步加模块、加深度。这样每一步都有可验证的产出,迭代速度反而快很多。

还有一点,Agent的评估比传统软件难得多。传统软件输入确定、输出确定,测试用例好写。Agent的输入是自然语言,输出是执行轨迹,中间还涉及模型的不确定性。我现在的做法是建一个任务集,覆盖典型场景和边界场景,每次架构调整后跑一遍,看成功率和执行效率的变化。没有这个评估集,优化就是盲人摸象。

最后分享一个实用的小技巧:在开发调试阶段,把Agent的每一步决策都打上详细的日志——Planner输出了什么计划、Tool Use选了什么工具、传了什么参数、返回了什么结果、Reflection得出了什么结论。这些日志在排查问题时极其有用。我踩过的很多坑,最后都是靠翻日志定位到根因的。Agent系统比传统系统复杂,可观测性不是可选项,是必需品。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 7:56:47

HLW8112高精度电能计量芯片实战:从采样电路到过压过流保护

我最早做智能空开的时候,对于电能计量这活儿其实想得很简单:单片机自带的ADC采一下电压电流,算个RMS,再乘乘除除也就出来了。直到我拿真负载去校表,才明白这个思路在商用电表或者充电桩这类场景里完全走不通——10mA待…

作者头像 李华
网站建设 2026/10/20 7:56:35

Skill框架:从会写代码到会指挥AI写代码的思维转变

1. 从“会写代码”到“会指挥AI写代码”的思维转变这两年跟不少同行聊下来,我发现一个特别有意思的现象:很多人用AI写代码的方式,还停留在“打开对话框,把需求一股脑丢进去,然后祈祷它一次跑通”的阶段。结果就是&…

作者头像 李华
网站建设 2026/10/20 7:55:59

企业级反勒索病毒系统核心组件与Python实现解析

两年前处理过一起某制造企业的勒索事件,整个OA系统的共享文件在几分钟内被全部加密,后缀统一变成了一个陌生名字,IT负责人后来跟我说,杀毒软件在事发前其实已经报警过一次,只是当时弹窗被值班同事当成误报直接关掉了。…

作者头像 李华
网站建设 2026/10/20 7:55:58

天地图离线地图内网部署:js、css与瓦片加载方案

简介:这份资源面向需要在无外网环境下部署地图服务的前端与内网应用开发者,提供天地图离线化所需的 JavaScript 与 CSS 源码,解决内网无法直连天地图在线接口、地图瓦片与控件无法加载的问题。压缩包共 5 个文件,包含 4 个 js 与 …

作者头像 李华
网站建设 2026/10/20 7:55:29

CVAT任务创建失败系统错误排查:数据库、连接池与存储后端修复指南

前阵子接到项目组的求助:CVAT任务和项目无法创建的报错反复出现,标注小组一到要开新一批任务就卡壳。页面转圈几秒后弹出一句“系统错误”,换浏览器、清缓存、重启容器都试过,问题依旧。老实说,这种“系统错误”在CVAT…

作者头像 李华
网站建设 2026/10/20 7:54:22

电场-热场耦合仿真:从焦耳热到温升的工程实战指南

电磁场耦合仿真这个系列里,主题021的“电场-热场耦合仿真”,几乎是我被问得最多的一个方向。说白了,它就是算电流流过导体之后,导体自己发热、发热到一定程度又反过来改变电阻率、电阻率再改变电流分布,形成一个真实的…

作者头像 李华