多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Agent开发实战:从记忆规划到工具编排的完整落地路径

Agent开发实战:从记忆规划到工具编排的完整落地路径 最近一直在跟进 Hello Agent 系列的学习到了 Task05 这里进度确实卡了一段时间。前四个任务围绕的还大多是基础概念、提示词工程、模型调用方式说白了还是在解决“怎么让模型好好说话”的问题到 Task05 之后重心一下子转向了“怎么让 Agent 真正动手干活”给它设计记忆结构、规划执行路径、注册工具调用还要琢磨多 Agent 协作时怎么分工、沙盒环境里怎么控制执行边界。这篇笔记就是 Task05 的完整复盘我会把自己实际跑过的代码、对比过的 Agent 框架、踩过的坑一并整理出来。适合正在从零学 Agent 开发的工程师参考尤其是卡在“有了大模型 API 但不知道怎么组装成有用的 Agent”这个阶段的朋友。这次 Task05 的收获对我个人来说非常大它强迫我把之前零散接触过的概念比如工作记忆、ReAct 模式、工具注册、智能体评测真正串起来落到了代码里。如果你也正处于“概念都听过但没动过手”的状态这篇笔记应该能给你一条比较清晰的落地路径。1. Task05 在学什么从对话模型到自主执行体1.1 先明确一个转变Agent 和普通 LLM 调用的本质区别Task05 的第一个学习点就是修正一个很容易混淆的认知不是“用了大模型 API”就是在做 Agent。我见过不少项目所谓 Agent 其实就是一个带上下文的聊天接口用户问一句、模型答一句最多套一层工具函数调用。严格来说这只能算“增强型对话”还谈不上 Agent。Agent 和普通 LLM 调用的关键区别在于它具备目标拆解、自主规划、环境交互和结果反馈循环的能力。也就是说你交给它一个相对开放的任务比如“帮我调研一下目前主流的 Agent 框架并整理成报告”它需要自己判断要完成哪几个子步骤先搜索资料、再阅读文档、然后对比分析、最后输出结构化报告。如果中间某一步工具调用失败了它还能根据错误信息调整策略再试一次。这种“规划—执行—观察—再规划”的循环才算是 Agent 的核心行为模式。Task05 给我的状态变化很明显之前写程序是我控制流程现在变成了我定义目标和边界Agent 自己在循环里跑。刚开始很不适应总觉得失控但跑通之后会发现这种模式在处理多步骤、多表单的综合性任务时效率和灵活性确实比硬编码流程高一个量级。1.2 Task05 的能力基线记忆、规划、工具、协作围绕“自主执行体”这个目标Task05 把需要具备的能力收敛成了四条基线后面所有实操都是围绕这四条展开的记忆能力Agent 必须能记住任务目标、中间结果和历史对话。这里就涉及短期的“工作记忆”和需要持久化的“长期记忆”两者机制完全不同。规划能力面对开放任务时Agent 需要能把目标拆解成可执行的子任务序列并且根据执行结果动态调整计划。工具调用能力Agent 需要能调用外部工具比如搜索、文件读写、代码执行否则它就只能“纸上谈兵”。多 Agent 协作能力单个 Agent 的能力有瓶颈多个各司其职的 Agent 组合起来才能完成更复杂的业务闭环。这四条能力基线其实也对应了目前行业里招聘 Agent 工程师时最常考察的几个方向。如果你以后想去面 Agent 相关的岗位把 Task05 这套东西搞清楚比死记一堆概念有用得多。1.3 学习资料怎么选Task05 期间我参考的比较多的是吴恩达的 Agent 系列教程另外就是翻了不少主流框架的官方文档。说实话吴恩达那个教程偏入门讲得很清楚但代码量不大框架文档则相反代码多但缺少系统性的串联。我自己的经验是先用一个入门教程把架构图在脑子里画出来然后立刻上手写一个最小 Demo再回去看框架文档深挖细节。这个顺序比“从头到尾刷完文档再动手”要高效得多。2. Agent 架构核心记忆、规划与工具编排2.1 记忆系统不是简单的“聊天记录拼接”Task05 在记忆这一块花了很多篇幅我刚开始觉得没什么好学的——历史消息拼起来传给模型不就行了吗真正跑起来才发现这个想法太天真了。先看工作记忆Working Memory。它指的是 Agent 在当前任务执行过程中需要保持的信息包括任务目标、当前步骤状态、已经收集到的事实。工作记忆的问题在于它直接受限于大模型的上下文窗口。我做过一个测试让 Agent 执行一个需要读 20 个网页再总结的任务如果把所有网页内容都塞进上下文很快就把窗口撑爆了而且越往后模型的注意力越分散回答质量肉眼可见地下降。解决办法只能是“摘要压缩”每读完一个网页把关键信息提炼成几十个字的结论存入工作记忆原始内容读完就丢。再看长期记忆。Agent 需要跨会话记住用户偏好、历史任务结论、领域知识。这一块我用的方案是向量数据库把文本切成块、做 embedding、存进向量库需要时做相似度召回。但这里有个容易踩的坑向量召回不等于精确查询。如果你需要 Agent 准确记住某个用户的下单地址用语义检索是不可靠的必须配合结构化的键值存储。所以我的最终方案是“混合记忆”结构化数据走数据库表非结构化知识走向量库临时状态走上下文摘要。我整理了一个简单的对照表方便你根据自己的场景选型记忆类型存储载体典型用途注意事项工作记忆上下文窗口 摘要当前任务中间状态必须做摘要压缩防止窗口溢出情景记忆向量数据库历史对话、用户偏好召回结果可能不准要人工复核语义记忆向量数据库领域知识、文档内容需要定期更新索引程序性记忆代码/配置文件工具调用流程、操作规范本质上是写死的规则不属于模型参数2.2 规划模式ReAct 是最容易上手的起点Task05 把规划模式分成了几类我逐个跑了一遍说说实际体感。ReActReasoning Acting是最直观的一种模型在每一步先输出思考过程Reasoning然后决定调用哪个工具Acting拿到工具结果后再继续思考形成循环。这种模式的优点是实现简单、适合单 Agent 任务缺点也很明显每一步都在做“思考—行动”Token 消耗大而且遇到长任务容易在细节里打转。Plan-and-Execute 则是先让模型产出完整的任务清单然后逐个执行。这种模式更接近人类做事的习惯前置规划能减少无效的中间思考但对模型的规划质量要求很高——如果第一步计划就错了后面全是白干。我的体感是开放探索型任务比如调研适合 ReAct流程相对固定的任务比如批量处理文件适合 Plan-and-Execute。Task05 还提了一种 Reflexion 模式核心是让 Agent 在任务失败后反思错误把教训存进记忆下次避免同类问题。这个思路很好但我实现下来发现它依赖两个前提一是要有一个能判断“任务成功与否”的反馈信号二是反思文本的质量得够高。否则反思环节就变成了自我感动并不会实际改善结果。2.3 工具注册约定接口比模型能力更重要工具调用这块表面上看起来是“把函数列表传给模型”但真正影响效果的是工具的描述质量和参数设计。大模型不像人脑它看不到你的函数内部实现它只能靠函数名、描述、参数 schema 来判断该不该调用、怎么调。我写过一条很微妙的工具描述一个叫predict_sales的函数描述写的是“根据历史数据预测未来销售趋势”。模型在各种场景下都倾向于调用它哪怕输入数据根本不完整。后来我把描述改成了“仅当用户明确询问销售预测且提供了至少三个月历史数据时才调用该函数”误用率立刻降了下来。还有一个细节是工具列表的长度。当 Agent 可用的工具超过 20 个时模型在每一步选择正确工具的概率会明显下降。我的做法是给工具分组先用一层路由决定“这步需要的是一个数据分析类工具还是信息检索类工具”再把具体工具列表传给模型。相当于做了一个两层的工具索引实测效果好很多。2.4 框架与编排的关系现在很多文章把“Agent 框架”和“Agent 编排”混为一谈Task05 帮我把这两个概念理清了。框架是给你提供底层能力的工具箱模型接入、工具调用协议、状态管理编排是你在框架之上设计的 Agent 工作流。举个例子LangChain 是框架它允许你用StateGraph定义状态机来编排 Agent 的运行流程而 Coze扣子这样的产品则更像是一个低代码平台编排方式与 LangChain 完全不同。框架与编排之间有个共性核心都是状态流转。Agent 的本质是一个会根据外部反馈不断改变自身状态的执行体所以不管用什么框架理解好“状态定义”和“状态转移条件”就掌握了 Agent 编排的根基。3. 主流 Agent 框架对比与选型心得3.1 我实际试用过的框架Task05 期间我花了比较多的时间做框架选型前后试了 LangChain/LangGraph、AutoGen、CrewAI也看了 Spring AI 和国内一些产品。这里做一个主观性很强的对比仅供参考框架开发语言核心模型适合场景实际体感LangGraphPython/JS状态图需要精细控制流程的复杂 Agent灵活度最高但学习曲线陡峭AutoGenPython多智能体对话多个 Agent 通过对话协作消息驱动模型调试起来比较费劲CrewAIPython角色扮演协作固定角色分工的团队型任务API 设计简单上手快深度不如 LangGraphSpring AIJava与 Spring 生态集成现有 Java 服务接入 AI 能力如果你是 Java 栈可以考虑Coze扣子低代码可视化编排快速搭建业务 Agent / 公众号助手适合产品同学不适合深度定制选型的时候我用一个非常朴素的指标我想控制的粒度在哪里。如果我只是想给客服系统接一个能查订单的机器人Coze 或 Spring AI 足够。但如果我想做一个需要多轮反思、主动记忆、动态规划的研究型 Agent最终还得回到 LangGraph 这种偏底层的框架。3.2 LangGraph 为什么成为我的最终选择Task05 的实操阶段我最终选了 LangGraph原因有三个。第一它的状态图模型和 Agent 的思维方式天然匹配。我用节点表示工具调用、用边表示状态转移每一步都在一个显式的数据结构里记录这对后续 debug 和扩展都极其友好。第二它内置了记忆持久化和 checkpointer 机制工作记忆可以跨步骤甚至跨会话保持不需要自己再造轮子。第三LangGraph 社区活跃踩坑搜一下基本都有答案。当然LangGraph 的缺点也明显概念多StateGraph、Node、Edge、Reducer、Checkpointer上手需要时间而且它的抽象层不薄出错时报错信息经常很晦涩需要自己反复实验。3.3 单 Agent 到多 Agent 的演化路径Task05 还有一个核心内容是从单 Agent 卷到多 Agent。这个不是指“同时开多个进程跑多个独立任务”而是指多个 Agent 之间存在协作关系比如一个负责信息收集、一个负责数据分析、一个负责报告撰写最后汇总结果。多 Agent 的编排有两种常见模式。一种是中心化编排一个主控 Agent 负责拆解任务、分发子任务、收集结果。这种模式管理简单但主控容易成为性能瓶颈。另一种是去中心化对话多个 Agent 平级通过消息互相协调。这种模式更灵活但容易失控任务收敛性差。我的实操结论是目前阶段中心化编排是更可靠的工程选择。去中心化模式可以玩但要先有非常强的任务边界和校验机制否则两个 Agent 会因为一个字段的分歧来回对话十几个来回。我自己实现多 Agent 时脑子里是把它当分布式系统来设计的任务要有明确的输入输出协议Agent 之间不要共享可变状态消息要有超时和重试机制最终结果要有校验层。这些工程习惯比任何 Agent 框架技巧都重要。4. 实操让 Task05 的 Agent 真正跑起来4.1 最小闭环的架构设计我这里实现了一个最小闭环的 Agent输入一个开放任务Agent 自主完成资料检索、内容整理、结构化输出。系统由四个模块组成规划器Planner生成任务步骤清单。执行器Executor按步骤调用工具获得结果。记忆管理器Memory维护工作记忆和长期记忆。反思器Reflector任务失败时分析原因并调整计划。这四个模块在 LangGraph 里分别对应四个节点状态对象结构大致如下class AgentState(TypedDict): task: str # 原始任务 plan: list[str] # 任务清单 current_step: int # 当前步骤 working_memory: list[dict] # 工作记忆 tool_results: dict # 工具执行结果 error_history: list[str] # 错误记录供反思使用 output: str # 最终输出这里最核心的设计是working_memory字段。它只存跟当前任务有关的信息一旦任务结束就被清理避免干扰下一个任务。我踩过的一个大坑就是没有及时清理工作记忆导致 Agent 在执行第二个任务时还把第一个任务的中间结果当成背景知识回答出现了严重的“串味”——那感觉就像开会时你还在想着上一场会的内容完全没法专注。4.2 分步实现的关键代码第一步初始化模型和工具注册。from langchain_openai import ChatOpenAI from langchain_core.tools import tool llm ChatOpenAI(modelgpt-4o-mini, temperature0.2) tool def search_web(query: str) - str: 当需要查询实时信息或补充背景资料时使用。输入为搜索关键词返回网页搜索结果的摘要列表。 注意仅当用户任务明确需要外部资料时才调用不要臆造结果。 # 这里接入真实搜索 APIDemo 里返回模拟结果 return f搜索结果关于{query}的摘要内容 tool def read_url(url: str) - str: 读取指定 URL 的网页正文内容。输入必须是完整 URL。 # 这里实现对网页的抓取与正文提取 return f网页内容{url} 的正文 tool def write_markdown(filename: str, content: str) - str: 将内容写入本地 Markdown 文件。输入为文件名和文件内容。 # 这里实现文件写入 return f已写入 {filename}工具描述那段话真的值得多写几句我前面讲过模型的工具选择能力完全取决于你看不见的描述质量。你可以在自己的项目里做个实验把工具描述写得很简陋与写得很详尽Agent 的成功率差距会大到让你惊讶。第二步实现规划节点。def planner_node(state: AgentState) - AgentState: prompt f你是一个任务规划器。请将以下任务拆解为 3-5 个可执行的子步骤 任务{state[task]} 要求 1. 每个步骤必须是可以直接执行的动作 2. 步骤之间要有逻辑顺序 3. 输出格式为 Python 字符串列表 response llm.invoke(prompt) state[plan] ast.literal_eval(response.content.strip()) state[current_step] 0 return state这一步看起来简单但我建议你一定要对模型返回的“步骤”做格式校验。模型偶尔会输出带编号的文本而不是合法的 Python 列表如果没做ast.literal_eval的异常处理整个流程就会在第一步崩溃。我在 Task05 初版代码里就没做这个防御被坑了整整一个晚上。第三步实现执行节点。执行节点是核心它要做的事情是读取当前步骤调用工具把结果写回工作记忆。def executor_node(state: AgentState) - AgentState: step state[plan][state[current_step]] # 从步骤文本中解析出意图这里简化处理直接让 LLM 决定调用哪个工具 tool_call_prompt f当前任务进度已完成 {state[current_step]} 步共 {len(state[plan])} 步。 当前步骤{step} 已有工作记忆{state[working_memory]} 请选择调用哪个工具来完成当前步骤。可用工具search_web, read_url, write_markdown。 只输出工具名和参数 JSON不要解释。 decision llm.invoke(tool_call_prompt) # 解析 decision调用对应工具 # ... 工具分发的逻辑 state[tool_results][step] result state[working_memory].append({step: step, result: result}) return state工具分发那一段代码其实挺多的包括参数解析、异常捕获、超时处理在 Demo 里我会简写但实际项目里这些才是工程量的主体。特别提醒工具调用一定要有超时机制不然一个外部 API 卡住整个 Agent 就僵死了。我的超时设置是 15 秒超过就报错走重试分支。第四步实现反思节点。def reflector_node(state: AgentState) - AgentState: if len(state[error_history]) 2: # 同一个步骤失败两次以上考虑调整计划 plan_prompt f当前计划 {state[plan]} 中第 {state[current_step]} 步执行失败错误{state[error_history]}。请给出修改后的计划注意规避已知问题。 new_plan llm.invoke(plan_prompt) state[plan] parse_plan(new_plan) state[current_step] - 1 return state这里我学到的一个经验是反思不要无限重试要设置次数上限。Agent 不是神仙同一个错误重复三次基本说明计划本身就是错的继续重试只会白白烧 Token。我的上限是 3 次超过就直接向用户汇报失败并给出当前进展让人类介入。4.3 多 Agent 协作的 Demo 实践在单 Agent 跑通之后我把系统扩展成了三个角色的多 Agent 版本Researcher 负责查资料Analyst 负责分析数据Reporter 负责写报告。主控 Agent 用 LangGraph 的状态图做路由每个角色是一个独立的子图。多 Agent 协作最需要注意的其实是“上下文隔离”。不要让副手 Agent 看见所有对话历史它们只需要看到跟自己职责相关的部分。我最初偷懒直接把所有消息广播给所有 Agent结果就是 Researcher 在讨论分析师该用的图表格式Reporter 在替 Researcher 做检索决策……信息过载导致每个 Agent 的工作质量都下降。后来我改成按职责过滤消息副手 Agent 各看各的质量明显回升。4.4 必要的测试单测、回归与成本控制写到这里我真的建议学 Agent 开发的同学早点养成写测试的习惯。Task05 的代码虽然是一个 Demo但一旦涉及多个工具调用和分支判断改动一个地方就可能导致意想不到的回归。我为每个工具函数写了单元测试为整个 Agent 流程写了几个典型的端到端用例比如“带清晰输入的任务正常完成”、“带模糊输入的任务需要追问”、“工具连续失败后能优雅退出”。此外成本控制也是 Agent 工程绕不开的话题。我跑一个带 8 轮工具调用的任务Token 消耗大概在 3 万左右用 GPT-4o-mini 的话成本不高但如果用更强的模型一次完整任务下来就是几美分日积月累也不少。我的优化手段是控制每一步的 Prompt 长度尽量只传“当前步骤需要的最小上下文”而不是把全部历史都塞进去。这一点在长任务中尤其重要。5. 踩坑记录与排查方法5.1 上下文窗口溢出与记忆污染Task05 期间我遇到的最大坑就是上下文窗口溢出。我在一次资料检索任务中把读取的网页全文全部塞进了工作记忆结果到第 5 个网页时模型直接报错之前的有效信息也被淹没在一堆无用文本里。排查思路其实不复杂观察每次工具调用前后的 Token 增量如果一个步骤的输入中有大量无关历史那问题一定出在摘要策略不够激进。我的解决办法是每读一个网页强制模型输出一段不超过 200 字的总结原始内容只保留在文件系统里不进入上下文。这样即使读 20 个网页工作记忆也只有大约 4000 Token非常稳定。5.2 工具调用的参数幻觉与失败重试模型在生成工具参数时经常出现幻觉。我遇到过模型凭空生成根本不存在的 URL、把用户名字拼错、把日期格式从YYYY-MM-DD写成YYYY/MM/DD。这种参数错误不会让程序报错但会导致下游工具返回空结果。我的应对方法是两层校验第一层是参数 schema 的严格类型检查第二层是结果合理性检查——如果工具返回的结果是空的或者明显异常Agent 必须把“工具返回异常结果”这个信息记录下来而不是直接硬着头皮往下用。这一点特别重要因为模型往往会拿错误数据继续推理生成一本正经的胡说八道。5.3 安全边界Agent 要有拒绝能力Task05 专门花了不少时间讲 Agent 安全。我一开始不太理解后来想明白了当 Agent 可以调用工具、访问文件、执行指令时它就不再是一个“问答机器人”而是一个“可以在你的系统里动东西的进程”。所以必须定义严格的行为边界。我写了一个safety_policy常量作为系统提示词的一部分注入。里面明确声明了三条不可逾越的规则第一任何涉及删除、修改核心数据的操作必须先向用户确认第二任何外部命令执行任务必须经过白名单校验第三当用户指令与安全策略冲突时优先遵守安全策略并明确告知。这里对应的其实是热词列表里反复出现的“Agent 安全”和“沙盒”概念——你的 Agent 越强大它的越界风险越大。生产环境里最好让 Agent 跑在隔离的容器或者沙箱中即使翻车也不会波及其他服务。5.4 并发与性能Agent 怎么扛住多请求热词里有一条是“AI Agent 怎么扛并发”这个我在 Task05 的结尾没有正式涉及但提前实验了一下。结论是Agent 的并发瓶颈主要在外部 API 的速率限制和工具的响应延迟上而不是模型调用本身。我的做法是引入两层机制第一层是异步化Agent 的工具调用全部改成异步请求这样在一个步骤等待外部响应时CPU 不会空转。第二层是任务队列和限流用一个简单的asyncio.Queue控制同时执行的 Agent 数量既保护外部 API又避免内存被疯狂占用。实测在主流程不做改动的前提下把并发从 1 提升到 8吞吐量提升了将近 5 倍主要收益来自“等待外部响应时不阻塞”。5.5 评测怎么判断 Agent 是否真的变好了最后一定要说评测。Task05 做下来我强烈建议每个 Agent 项目从第一天就建立评测集。我的评测集包含 20 个任务样本分为简单、中等、困难三档每个任务都标注了明确的验收标准。每次改代码后我都跑一遍评测集记录成功率和平均耗时。没有评测集的时候我改一个 Prompt 觉得“好像变聪明了”但说不清到底强在哪有了评测集之后每次改动的好坏一目了然。我用的是一个很简单的脚本把任务逐个喂给 Agent对比输出与验收标准自动判分再输出汇总表。评测维度占比说明成功率40%任务是否达成预设目标效率20%完成任务的步数与耗时稳定性20%相同任务多次运行结果一致性成本20%每次任务的 Token 消耗这个表看起来简单但能逼着你去关注 Agent 在真实使用中的表现而不是只盯着“有没有输出”。结语写在 Task05 之后Task05 给我的最大收获不是学会了哪个框架而是建立了对 Agent 系统的整体工程视角从记忆设计、规划模式、工具编排到多 Agent 协作每一个环节都不是孤立的它们共同决定了一个 Agent 是“能用的玩具”还是“可靠的工具”。如果说要给后来者一个建议我会说不要纠结于框架和热词找一个自己最熟悉的任务场景从零开始搭一个最小闭环的 Agent然后把记忆、规划、工具、评测这些能力一个个加进去。每加一个环节你都会对“Agent 到底是什么”多一层理解。这个过程没有捷径但很值得。最后分享一个小技巧Agent 的代码里日志是你最值得投资的地方。把每一步的思考、工具输入、工具输出全部打印出来遇到问题你会感谢当时写日志的自己。我在 Task05 中重构的一个关键动作就是给所有节点加上了结构化日志。在那之后排查效率提升不止一倍。
返回列表