Agent开发真正要学的五件事:边界、控制流、记忆、工具与评测

发布时间:2026/11/4 9:53:18
Agent开发真正要学的五件事:边界、控制流、记忆、工具与评测

做了近两年Agent开发,我有一个很反直觉的体会:真正卡住人的从来不是模型能力,也不是某个框架的新特性,而是那些看起来特别基础、特别容易一带而过的事。两年前我刚接触Agent时,以为核心是写提示词,后来以为是搞懂LangChain的源码,再后来以为是追各种新框架。等到自己从零搭过、上线过、也给线上事故背过锅之后,才慢慢收敛出一个结论——Agent开发真正要学的,其实就五件事。

这篇文章不是框架教程,也不是某个项目的复盘,而是把我这两年里反复踩、反复琢磨的东西重新梳理了一遍。如果你正在学Agent,或者已经开始搭自己的智能体,但总觉得学了一堆东西还是串不起来,那这篇文章大概率对你有用。我会尽量用"当时我怎么想的、后来为什么改"的方式来写,少一点概念堆砌,多一点可以拿去用的东西。

1. 定义边界:很多人做了半年Agent,做的其实还是"假Agent"

先把一个最容易忽略的问题摆在最前面:你做的到底是不是Agent?

我见过太多项目,号称是Agent,实际只是给模型套了一层角色设定,然后循环调几次API。也见过反过来的情况——明明就是个多轮对话机器人,非要在PPT里叫Agent。边界不搞清楚,后续所有设计都会跑偏。所以第一件事不是学框架,而是把"Agent到底是什么、和之前的东西差在哪"想明白。

1.1 Agent和普通LLM应用真正的分水岭

普通LLM应用,比如最常见的ChatBot,核心是"输入Prompt,输出回答",整个控制流掌握在用户手里。用户问一句,模型答一句。模型不主动调用任何东西,也不决定下一步该做什么。RAG应用稍微进了一步,但本质仍是"检索—拼接—生成"这条固定管线:用户问题进来,系统去向量库捞相关文档,把文档塞进上下文,让模型据此回答。检索是检索,回答是回答,先后顺序写死在代码里。

Agent不一样,分水岭在于:模型要自己决定"下一步做什么"。

这个决策权从人手里转交给模型,是整个开发思维的转折点。Agent拿到一个目标后,可以自己判断:要不要检索资料、要不要调计算器、要不要问用户澄清、先用哪个工具、失败之后重试还是换一条路。它是一个带控制权的循环,而不只是一次性的生成。

我用一个朴素的标准判断手头项目是不是Agent:**如果把模型换成随机回答,控制流还能不能显著影响最终结果。**如果能,说明系统里Agent味很重;如果整个流程就是"一问一答",那无论怎么宣传,还是普通应用。

1.2 Workflow、RAG和Agent的边界,其实就一句话

很多人把Workflow和Agent混在一起,是因为它们都有"多条处理步骤"。区别只在步骤的确定性和模型决策权。

Workflow是蓝图提前画好的:步骤A做意图识别,步骤B用工具查天气,步骤C做结果整理。每一步做什么,工程师在开发期就拍板了。Agent是蓝图边跑边画的:模型拿到目标之后动态拆解、动态决定调用哪个工具,同一个任务今天可能走三步,明天可能走五步,路径不完全固定。

所以我的结论是:

  • Workflow,适合流程稳定、容错要求高的场景,比如电商退款、工单流转。
  • RAG,适合知识密度高、但答案基本确定的场景,比如企业知识库问答。
  • Agent,适合路径不固定、需要临场组合能力、步骤可能动态变化的场景,比如个人助理、复杂编排、代码辅助。

这三者不是先进和落后的关系,是成本和可控性的取舍。Agent在正确率、延迟、可解释性上都要付出代价,换来的是灵活性。你如果场景用固定流程就能覆盖,硬上Agent反而给自己挖坑。

1.3 伪Agent的三个典型症状

判断完概念,再对照一下实际项目常见的三种"伪Agent":

**第一个症状:只有Prompt,没有状态。**模型始终在无状态地"猜"下一步,每次调用都是孤立的输入输出,没有中间记忆,没有任务上下文。这种即使套了Agent框架,也只是换个姿势调API。

**第二个症状:循环写死了。**外面一个for循环,里面固定三步:生成、调工具、给结果。没有反思机制,没有步骤记忆,失败就break。这种更像是自动化脚本,不是Agent。

**第三个症状:模型决定,但没有兜底。**让模型自主决策,却完全没有异常处理、超时控制、用户确认机制。一旦步骤错了,一路错下去,最终给用户一个莫名其妙的结果。这个我后面专门讲,属于运行边界问题。

先把这个边界立住,后面谈控制流、记忆、工具、评测,才有共同语言。你永远没法优化一个说不清楚是什么的系统。

2. 控制流是Agent的骨架:先手写一遍,再谈框架选型

第二件事是控制流。我直接给一个可能有点冒犯的建议:**先别急着上LangChain、CrewAI这类框架,先手写一遍最简单的ReAct循环。**手写过之后,你对框架里那些抽象概念的理解会完全不同。因为Agent框架做的事情,本质就是帮你管理"模型决策循环"这件事,你不理解循环,就用不好框架。

2.1 ReAct循环:最朴素的自主决策闭环

ReAct这个名字来自论文《ReAct: Synergizing Reasoning and Acting in Language Models》,核心思想是让模型在"推理"和"行动"之间交替。翻译成代码逻辑,就是下面这个循环:

def run_agent(task): messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.append({"role": "user", "content": task}) for step in range(MAX_STEPS): response = llm.chat(messages) action = parse_action(response) # 期望模型输出指定格式:Thought/Action/Action Input if action is None: # 模型认为任务完成 return response.final_answer result = execute_tool(action) # 调用对应工具 messages.append(response_message) messages.append({"role": "tool", "content": result}) raise TimeoutError("超过最大步数")

就这么一个循环,所有Agent框架的底层都逃不出它的变体。你不用框架也能实现,无非是自己定义模型输出的解析格式、管理消息队列、写工具注册表。这个过程能帮你搞清楚几个关键问题:

  • 模型输出什么格式才能可靠地被解析?(JSON常有格式问题,带转义的Markdown也常翻车,自己做一遍就懂了。)
  • 工具执行结果如何回填到上下文?
  • 循环什么时候停?模型说"done"就真done吗?

我实际写下来的经验:解析格式别用太复杂的自由文本,尽量让模型输出结构化JSON,并且在Prompt里给一个严格的JSON示例。另外一个容易踩的坑是,工具返回结果可能很长,直接塞进上下文会爆窗口,后面记忆章节会讲怎么处理。

2.2 Plan-and-Execute和Reflection:当单轮循环不够用

ReAct适合步骤在10步以内的任务。任务一旦复杂,单轮ReAct会暴露出一个问题:**每一步都重新短视决策,容易迷失全局方向。**比如一个"调研竞品并输出报告"的任务,模型可能在第10步还在调搜索API,不知道第1步搜索的结果已经足够支撑结论了。

所以有了Plan-and-Execute模式:先让模型一次性产出一份Plan,然后按照Plan去执行每一步,执行完再根据结果决定是继续还是修订Plan。它和ReAct的差别是:ReAct是"边想边干",Plan-and-Execute是"先规划再分步干,干的过程中允许改计划"。

Reflection(反思)模式是我后来才体会到好处的:每一步执行完之后,额外问一轮模型"上一步结果符合预期吗?有没有遗漏?"然后在下一轮决策时把反思结论拼进去。代价是额外的时间和Token,收益是能纠正模型自己跑偏的趋势。在容错要求高的流程里,这个代价值得花。

我的用法是分场景:

场景推荐控制流理由
单工具调用、问题明确单个LLM调用+工具不需要循环
多步骤、步骤动态ReAct循环灵活,路径不固定
多步骤、要全局规划Plan-and-Execute防止短视
结果可靠性优先ReAct+Reflection多一轮纠偏,代价高一些

2.3 Harness不是Agent本体,但决定了运行的边界

热词里有个「harness和agent区别」,很多初学者确实搞混。Agent和Harness不是一回事。Agent,通常指模型、Prompt、工具集合、记忆这些智能体"内在"的东西。Harness,指承载Agent运行的那层执行环境,包括循环调度、工具权限管理、限流、缓存、日志、超时终止、错误恢复、人工介入接口。

你可以把Agent理解成"人",Harness理解成"职场规则和基础设施"。人再能干,没有办公系统、审批流程、出差报销这些基础设施,也没法高效工作。

这个问题在开发中很容易被忽略,因为框架默认给你一套Harness,你几乎不感知它的存在。但到线上就麻烦了:

  • Agent陷入死循环时,Harness能不能及时终止?
  • 工具调用进入未处理异常时,Harness会不会吞掉错误,导致用户拿到一个"看似正常其实出错"的答案?
  • 某个工具连续失败时,Harness有没有熔断机制?

这就能解释为什么会有「agent execution terminated due to error.」这种报错成为搜索热词——Agent在执行过程中出错被终止,恰恰是Harness机制在起作用。真正合格的做法是,把超时、重试、熔断、终止条件当成一等公民来设计,而不是事后补丁。

2.4 多Agent协作:不是人越多越能干

最后说多Agent。我看到太多人一上来就设计三个角色:规划Agent、执行Agent、审查Agent。结果Token烧得飞快,流程复杂到无法调试,效果还不如单个Agent。原因很简单:每一次多Agent交互,都是一次信息在模型之间的传递,传递必然伴随损失和延迟。

我的建议是:

  • 单Agent能干的,绝不拆成多Agent。
  • 必须拆的场景,通常是角色技能差异明显,或者有天然并行需求。比如一个Agent检索资料、一个Agent做数据分析,最后汇总。
  • 多Agent之间的通信协议要设计得极其简单,能传结构化消息就不要传自然语言长文。

我自己做多Agent时,最大的教训是:Agent之间的"聊天"看起来很像人,实际是最大的效率黑洞。每轮消息都是Token,每轮都可能理解偏,必须加一个协调者去收敛结果,而不是任由几个Agent自由讨论。

3. 记忆不能一招吃遍天:把读写成本拆开看就清楚了

第三件事是记忆体系。这是热词里「agent记忆框架以及选型」「记忆中短期、长期、永久记忆如何实现」的高频来源。说实话,记忆是个需求非常多、但网上教程普遍碎片化的方向。

我刚开始做Agent时,想的很简单:Agent不记得之前聊过什么,那就把聊天记录全部存起来,每次全塞进上下文。等对话长了,Token超了,我开始做截断;截断后质量下降了,我开始做摘要;摘要也长了,我开始做向量检索。到最后才发现,记忆不是"存下来"就完了,它本质上是一套带读写成本的分层存储系统。

3.1 短期记忆:在上下文窗口里做工程

短期记忆最直观,就是把最近几轮对话直接放进上下文。实现简单,但问题是有人贪多,把几十轮历史全塞进去,又费Token又稀释当前指令的注意力。工程上可以做两件事:

  • 滑动窗口:只保留最近N轮完整的消息。N的选择要看你的模型窗口和任务复杂度,通常对话类任务10~20轮够用。
  • 关键信息抽取:每轮对话后用一个小模型把"用户偏好、未完成任务、关键数据"抽成结构化字段,放进系统Prompt的固定区域。

我最常用的做法是"滑窗+状态变量":滑窗负责保留最近上下文,状态变量负责携带用户核心信息。比如用户说"我住杭州,预算5000",就把user_profile更新一次,之后所有轮次都能看到。

3.2 中期记忆:摘要和结构化存储

当对话超过窗口上限,或者任务跨多天需要压缩历史时,摘要就登场了。但摘要要小心,无脑让模型"总结一下之前的对话"会把细节全磨平,尤其是数值、偏好、术语这类关键信息。我的做法是分级摘要:

  • 保留原始关键消息的引用ID,摘要里不写原文,但存指针。需要细节时去原始记录里捞。
  • 摘要Prompt要强制输出固定结构:任务背景、已确认信息、待办事项、用户偏好、风险点。结构化摘要比自由文本摘要的可用性高一个量级。

中期记忆还有一个常被忽略的痛点:什么时候触发摘要。不能等Token快爆才摘要,要提前设置阈值。比如窗口上限8K,4K时就触发一次压缩,留出余量给后续工具返回。

3.3 长期记忆:向量检索只是入口,不是全部

长期记忆通常指跨会话、长期沉淀的用户画像、历史偏好、知识偏好。最流行的实现是:把历史记忆embedding化,存入向量库,每次任务开始时做相似度检索。这套方案能跑,但我用下来有两个坑:

第一个坑是检索结果不可控。向量检索按相似度返回片段,片段之间可能重复、矛盾、或者缺少必要的上下文。检索结果直接塞进上下文,会让模型被不相关内容带偏。解决方法是做一层重排序,或者干脆用LLM对检索结果做相关性过滤。

第二个坑是记忆的一致性。同样一条用户偏好,可能在三个月前被记录为"喜欢简洁风",上个月又变成"喜欢详细解释"。如果向量库里两条都存着,模型可能选错。这件事没有完美解法,但可以缓解:给长期记忆条目加时间戳,检索时按时间加权。

选型上,大多数项目从Postgres+pgvector起步就够,没必要一上来就上独立的向量数据库。等到数据量上了千万级,再考虑专用的向量库。

3.4 记忆写入和遗忘:最容易被忽略的一环

记忆不只有读,还有写和遗忘。我见过太多项目只做了"存"的设计,没有"为什么要存这条"的过滤逻辑。结果向量库里塞满了垃圾,检索质量直线下降。

我的经验是写一条记忆时,预设几个问题去过滤:

  • 这条信息是否会在未来任务中被复用?
  • 它是事实,还是临时状态?
  • 如果存了,下次检索时会不会造成误导?
  • 这条信息有效期多久?

有效期尤其关键。我现在实现记忆时,每条记忆都带expires_at,到期了就归档或删除。比如"用户下周出差去上海"这种临时信息,过期后还躺在长期记忆里,就会干扰后续判断。遗忘和写入一样,都是系统主动设计出来的,不是等着自然失效。

4. 工具调用与Skill:Agent的能力上限,由工具边界决定

第四件事,也是我认为最接近"实战核心"的一件事:工具调用。Agent模型本身不能执行任何操作,它所有影响真实世界的能力,全部通过工具来实现。你把工具边界划到哪,Agent的能力就到哪。所以工具层的设计质量,直接决定Agent的可用性。

4.1 工具调用不是"调个API",是给模型造可控的外接大脑

很多新手以为工具调用就是把Function列表传给模型。这是API层面的理解,不是工程层面的理解。工程层面要处理的事至少包括:

  • 工具描述质量。**同一个函数,描述是"获取天气"还是"根据城市名获取实时天气,支持5天预报",模型调用准确率完全不同。**工具描述是给模型看的说明书,要用模型能理解的、包含边界条件和返回结构的语言去写,而不是程序员脑中的注释。
  • 参数校验。模型生成的参数经常缺字段、格式错、超出枚举范围。工具层必须有一套严格校验,不接受任何"模型以为对了"的参数。
  • 返回结果的大小控制。工具返回一个超大JSON,一下塞爆上下文,是整个工具层最常见的翻车原因。解决办法是:工具返回前先做裁剪、截断或摘要,只回传必要字段。

我做工具调用的心得是:先给工具做一层"适配器"。上层统一接JSON格式,下层适配不同的API、SQL查询、文件操作。模型不直接面对底层系统的复杂性,只面对适配器暴露的简洁界面。这样调试时也能快速定位是模型选错工具,还是工具执行出错。

4.2 Skill到底跟工具、Prompt有什么区别

「skill和agent的区别」这个关键词热度很高,说明很多人把Skill当成另一个神秘概念。我的理解比较朴素:

  • 工具(Tool),是单一可执行动作。比如"搜索网页""发送邮件"。
  • 技能(Skill),是一组可复用的动作编排,通常包含几个步骤的调用序列、前置条件、后处理逻辑。比如"调研一个行业趋势"这个Skill,内部包含"搜索行业报告、抓取重点数据、生成摘要、整理成结构化笔记"四个动作。

区别在于粒度:工具是原子的,Skill是流程化的。Skill的价值在于把Agent高频使用的动作模式固化下来,避免每次让模型临场现想、临场组合。它像是给Agent预置的"肌肉记忆"。

在实际开发中,我的经验是:能用工具解决的,不要先上Skill;当发现Agent反复用同一套动作组合完成同一类任务,再把它抽成Skill。抽早了容易过度抽象,抽晚了浪费Token。什么时候抽?观察日志里工具调用序列的重复度,重复三次以上,就值得固化成Skill。

4.3 工具安全:权限最小化是底线

工具调用也是最容易出安全事故的地方。Agent一旦能调用真实世界的工具——发邮件、下单、写文件、操作数据库——工具的权限边界就必须收敛到一个可控范围内。热词里有「agent安全」这个词,这两年Agent安全确实从"学术讨论"变成了"生产刚需"。

我在框架上建议至少做三层:

  • 第一层,工具注册白名单。Agent只能调用配置里声明过的工具,未注册的一律拒绝。
  • 第二层,操作权限分级。只读类工具(查日历、读文档)可以放开;写操作(发邮件、改数据)必须加上人工确认。
  • 第三层,敏感操作熔断。当Agent短时间内连续调用高风险工具,或参数里出现危险操作特征,直接终止流程并转人工。

尤其要注意的是不要让模型自己判断"这个操作是否危险"。模型判断不可靠,安全边界应该放在Harness层用代码控制,而不是写进Prompt里让模型自觉。你可以让模型决定"做什么",但不能让模型决定"能不能做"——这个决定权是人或系统自己的。

5. 评测和可观测性:能不能落地,被这两件事卡死

最后一件,也是最容易被砍掉的事:评测和可观测性。我见过太多Agent项目有功能Demo、有PRD、有炫酷前端,就是没有评测集。这种项目一上线就变成了开盲盒——每次跑同一个任务,结果都不太一样,而且说不出哪里变了、为什么变。

Agent是概率系统,这句话听了无数遍,但真正接受它的人不多。概率系统的工程化,必须有评测和可观测性兜底,否则永远在"看起来能跑"和"不知道什么时候会炸"之间反复横跳。

5.1 没有评测集的Agent项目,上线就是赌博

先把评测集这件事说透。传统软件开发有单元测试、集成测试,Agent开发同样需要一套固定评测集。但Agent的输出没有唯一正确答案,评测集要做的是"行为级判定",而不是"字符串级断言"。

我的评测集一般分三层:

  • 第一层,流程正确性。任务有没有走完预期的主要步骤?该调的工具调了吗?没该调的调了吗?这个可以自动化判定。
  • 第二层,结果有效性。最终输出里包含必要信息吗?格式对吗?关键数据存在吗?可以用规则+LLM-as-judge共同判定。
  • 第三层,用户满意度。真实用户能不能接受这个结果。这个只能靠人工抽样或者上线后的反馈统计。

评测集的构建要贴近真实场景。我知道很多团队懒,用几个玩具问题当评测集,那等于没测。我自己的做法是:把线上用户真实问题脱敏后攒成池子,再人工标注每个问题的"预期行为路径"和"可接受答案范围"。规模不用太大,300~500条就能发现大量问题,比跑1000条虚构问题有用得多。

5.2 LLM-as-judge怎么用,才不会自欺欺人

用大模型来给大模型打分,是目前最流行的高性价比方案,但用不好就是自欺欺人。我踩过的坑有几个:

  • 评分模型和被测模型同源时,偏差方向一致。比如两个都是追求"看起来全面"的模型,评分时可能被流畅度带偏而忽略是否跑题。
  • 只打分不给理由,等于没评。评分结果需要附带可追溯的判断依据,方便人工复核。
  • Prompt里的评分标准写得太抽象,比如"回答要准确",这等于没写。要写"必须包含任务要求的三个信息点,缺一个扣一分"这种可验证标准。

我现在的方案是双人制:先用评分Prompt做初筛,把明显不合格的捞出来;然后再由人工审查初筛结果和中间步骤日志。评测不是要100%替代人,而是把人力聚焦在最有问题的case上。这套方案在成本和覆盖之间比较平衡。

5.3 可观测性:把Agent的每次思考过程记录下来

评测集是"事后考试",可观测性是"手术台的无影灯"——出了问题要能看清现场。

传统日志只记录输入输出,对Agent来说远远不够。Agent出问题往往出在中间决策点:模型哪一步选错了工具?哪一步被上下文误导了?哪一步工具返回了错误格式?这些细节不记录,你只能对着最终错误结果猜原因。

关键要记录的信息至少包括:

  • 每一轮大脑的完整输入:当时的系统Prompt、历史消息摘要、当前检索到的记忆片段。
  • 模型输出:行动计划、工具选择、最终答案。
  • 工具执行情况:参数、返回结果、耗时、错误栈。
  • 关键决策点日志:为什么在这个节点选择了Plan A而不是Plan B?如果有Reflection,把反思结果也记录下来。

有了这些Trace数据之后,评测集里的失败案例才能真正用来定位问题。否则评测发现"这个case挂了",然后你不知道挂在哪一步,评测的价值就少了一大半。我的习惯是,每次排查线上问题,第一步永远是拉全部Trace,先把出问题的决策步骤找到,再去看模型和工具层面的事。这一步做扎实之后,很多之前觉得玄学的Agent问题,都会变成可解释的工程问题。

另外还想提醒一句:可观测性是逐步加厚的,不用一开始就上重型平台。我最早就是在代码里手动打JSON日志,用文件按天存储,出问题再翻日志。后来量大了才接专项追踪工具。工具不是重点,"先有记录的习惯"才是重点。

写在最后

这两年做Agent开发,我最大的变化是:不再把Agent当成一个"模型相关"的新奇玩具,而是当成一个"模型驱动的分布式系统"来做。控制流是骨架,记忆是血液,工具是手脚,评测和可观测性是眼睛。骨架不正,后面全是补丁;眼睛不亮,上线就是摸黑。

如果你还在学Agent,我的建议是把顺序反过来。**先学评测和可观测性,再学工具和安全,再碰记忆和控制流。**这个顺序和多数教程正好相反,但更接近真实项目里踩坑的顺序。一套系统跑起来之后,真正让你崩溃的从来不是模型不够聪明,而是出了问题你根本不知道它为什么出了这个问题。

最后再分享一个小技巧:所有Agent项目的第一个版本,不要追求智能,要追求"可复现"。哪怕能力弱一点,只要同一个输入在相同条件下能产生稳定行为路径,后面的优化才有参照系。我见过太多团队第一版就追求花哨工具和多Agent协作,结果后面每天在夹缝里修Bug。稳,比快更容易走到终点。