news 2026/9/26 19:36:59

从零手搓生产级Agent:RAG、记忆管理与工具编排实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手搓生产级Agent:RAG、记忆管理与工具编排实战

Agent 这个词在过去一年里被用得太泛了。打开任何一个技术社区,满屏都是"三行代码搭建你的第一个 Agent",但真到了要把一个 Agent 从 demo 推进到能扛住真实流量、能稳定跑在业务链路里的时候,绝大多数人会发现手里那套东西根本不够用。我自己从最早用 LangChain 拼一个能查天气的玩具,到后来做带 RAG 检索、带记忆、带工具编排的工程化 Agent,中间踩的坑足够写一本小册子。这篇东西不打算再给你灌一遍"什么是 Agent"的概念,而是想把我从零手搓一个 Agent 的完整路径摊开讲——从最小可运行内核,到 RAG 知识库接入,再到记忆管理、工具编排、错误处理这些真正决定能不能上生产的部分。适合已经写过一点 LLM 调用、想把 Agent 做扎实的开发者,也适合那些被各种框架绕晕、想搞清楚底层到底在发生什么的人。

1. 先把 Agent 的内核剥到只剩一个循环

1.1 为什么我不建议一上来就用重型框架

很多人学 Agent 的第一反应是打开 LangChain 或者某个 Agent 框架的文档,照着 example 抄一遍。抄完能跑,但一旦出问题就完全不知道从哪下手。我早期也是这样,一个 chain 套一个 chain,报错信息翻三页都找不到根因。后来我强迫自己把框架全删掉,只用最原始的 LLM API 手写一遍,才发现 Agent 的本质简单到有点反直觉:它就是一个"思考—行动—观察"的循环,英文里常说的 ReAct 模式,Reasoning 加 Acting。

这个循环用伪代码表达大概是这样:

while not done: thought = llm.think(context, tools) if thought.is_final_answer: return thought.answer action = thought.action observation = execute(action) context.append(observation)

就这么点东西。所有框架做的事情,无非是在这个循环外面包了一层工具注册、一层记忆管理、一层错误重试。你把这个循环亲手写一遍,后面用任何框架都能一眼看穿它在干什么。我个人的经验是,手搓一遍内核花不了两天,但省下来的调试时间是以周计的。

1.2 最小可运行内核需要哪几个零件

一个能跑起来的最小 Agent,我总结下来需要四个零件:LLM 调用层、工具注册表、上下文管理器、循环控制器。这四个缺一不可,但每个都可以做到极简。

LLM 调用层负责和模型对话,这里要注意的是,Agent 场景下你几乎一定要用支持 function calling 或者 tool use 的模型接口,因为纯文本解析工具调用太脆弱了。工具注册表就是一个字典,把工具名映射到具体的函数和它的参数 schema。上下文管理器管的是消息历史,什么时候追加、什么时候截断、什么时候压缩。循环控制器就是上面那个 while 循环,加上最大轮次限制和终止条件判断。

我第一版内核大概两百行代码,跑起来能查天气、能算数、能根据结果继续追问。虽然简陋,但每一个环节我都清楚它在干嘛。这个"清楚"在后期排查问题时价值巨大。

1.3 工具调用的 schema 设计是最容易翻车的地方

工具注册看着简单,但 schema 设计是新手翻车重灾区。我见过太多人把工具参数写成一大坨自由文本,然后指望模型自己解析,结果模型十次有三次传错格式。正确做法是用严格的 JSON Schema 描述每个参数的类型、是否必填、取值范围。

举个例子,一个查询订单的工具,参数不要写成"传入订单信息",而要写成:

{ "name": "query_order", "description": "根据订单号查询订单状态,仅在用户提供了明确订单号时调用", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,通常是 16 位数字" } }, "required": ["order_id"] } }

注意 description 里那句"仅在用户提供了明确订单号时调用",这种约束性描述能显著降低模型乱调工具的概率。我实测下来,把工具描述写清楚,比换一个更强的模型带来的提升还明显。这是很多人忽略的细节:模型选得好不如 prompt 和 schema 写得准。

2. 让 Agent 真正有用的是 RAG,而不是模型本身

2.1 为什么裸 Agent 在真实业务里几乎没法用

一个只会调用几个工具的 Agent,在 demo 里很惊艳,但放到真实业务里立刻露怯。原因很简单:模型不知道你的业务知识。你问它公司报销流程,它要么编一个,要么说不知道。这时候就需要 RAG,也就是检索增强生成,把外部知识库接进来。

RAG 的核心思路是:用户提问时,先从知识库里检索出最相关的若干片段,把这些片段作为上下文塞给模型,让模型基于这些真实材料回答。这样既解决了模型不知道私有知识的问题,又大幅降低了幻觉。我做过对比,同一个问题,不带 RAG 的 Agent 回答准确率大概三成,接上 RAG 之后能到八成以上,差距非常明显。

2.2 向量检索这条链路每一步都有讲究

RAG 最经典的实现是向量检索。整条链路是:文档切分、向量化、存入向量库、查询时向量化问题、相似度检索、返回 top-k 片段。听起来顺理成章,但每一步都有坑。

文档切分这块,最忌讳的是按固定字数硬切。我早期就是每 500 字切一段,结果经常把一句话、一个表格从中间切断,检索出来的片段语义不完整。后来改成按语义边界切,比如按段落、按标题层级切,再对超长段落做二次切分,效果好了很多。切分粒度也要权衡:切太碎,单段信息量不够;切太大,检索精度下降还浪费 token。我一般把单段控制在 300 到 800 字之间。

向量化就是选一个 embedding 模型把文本转成向量。这里要注意,查询和文档必须用同一个 embedding 模型,否则向量空间对不上,检索结果会莫名其妙地差。这个坑我踩过,换了文档侧的模型忘了换查询侧,排查了半天。

2.3 向量库选型:别一上来就上重型方案

关于"向量库检索需要什么数据库"这个问题,我的建议是分阶段。个人项目或者数据量在十万条以内,直接用 FAISS 或者 Chroma 这种轻量库就够了,本地跑,零运维。数据量上到百万级、需要多租户或者高并发,再考虑 Milvus、Qdrant 这类专业向量数据库。

方案适用规模部署成本我的使用场景
FAISS十万级以内极低,纯本地库原型验证、个人知识库
Chroma十万级以内低,可本地可服务小项目快速起步
Qdrant百万级中,需独立部署中等规模生产
Milvus千万级高,集群运维大规模生产

我个人的路径是先用 Chroma 把链路跑通,等数据量和并发真的上来了再迁移。过早引入重型向量库,运维成本会拖垮你的开发节奏。

2.4 Rerank 是提升检索质量性价比最高的一步

光靠向量相似度检索,召回的结果里经常混着一些"看起来像但实际不相关"的片段。这时候 Rerank 就派上用场了。它的做法是:先用向量检索召回一个较大的候选集,比如 top 50,再用一个专门的 rerank 模型对这 50 条做精细打分,选出真正最相关的 top 5 给模型。

为什么这步有效?因为向量检索是"粗筛",它把语义压缩成向量,快但不够准;rerank 模型是"精排",它直接对问题和候选片段做交叉编码,慢但准。两者结合,既保证了速度又保证了质量。我实测下来,加了 rerank 之后,最终喂给模型的上下文相关性提升非常明显,回答质量跟着上一个台阶。这一步的投入产出比,在整个 RAG 链路里是最高的。

3. Agent 的记忆管理:短期、长期和它们各自的坑

3.1 上下文窗口不是记忆,别搞混了

很多人以为把对话历史全塞进上下文窗口就是"有记忆"了。这是误解。上下文窗口是有限的,而且塞得越多,模型注意力越分散,还越贵。真正的记忆管理要解决三个问题:什么该记、什么该忘、怎么在需要时想起来。

我把 Agent 的记忆分成两层:短期记忆和长期记忆。短期记忆就是当前这轮任务相关的对话和中间结果,任务结束就清掉。长期记忆是跨会话需要保留的信息,比如用户的偏好、历史决策、重要事实。这两层的管理策略完全不同。

3.2 短期记忆的核心是压缩和裁剪

短期记忆的挑战在于,一个复杂任务可能来回几十轮,上下文很快就爆了。我的做法是分层处理:最近几轮对话原样保留,保证连贯性;更早的对话做摘要压缩,把关键信息提炼成几句话;中间的工具调用结果,如果已经用过且不再需要,直接丢弃。

这里有个技巧:摘要不要等上下文满了才做,而是每积累若干轮就主动压缩一次。这样每次压缩的量小,信息损失也小。我一般每 5 到 8 轮做一次滚动摘要。另外,工具返回的超长结果,比如一整个网页的内容,不要原样塞进上下文,先做一次提炼,只保留和当前任务相关的部分。

3.3 长期记忆要考虑写入和召回两个方向

长期记忆的工程实现,本质上是另一套 RAG。写入方向,要把值得记住的信息抽取出来,向量化后存进记忆库。召回方向,在每轮对话开始时,根据当前问题去记忆库里检索相关记忆,塞进上下文。

这里最容易出问题的是"什么值得记"。如果什么都记,记忆库很快就被噪音淹没;如果记得太少,又起不到作用。我的经验是,只记三类东西:用户的明确偏好、已经确认的事实、重要的决策结论。模糊的、临时的、可以从别处推导出来的,都不记。

注意:长期记忆的写入一定要做去重和冲突检测。我遇到过用户改了偏好,但旧偏好还在记忆库里,导致 Agent 行为矛盾的情况。写入新记忆时,先检索是否有相关旧记忆,有的话做更新而不是简单追加。

3.4 记忆安全是个容易被忽视的维度

Agent 记忆里可能存着敏感信息,如果被恶意输入诱导泄露,后果很严重。我现在的做法是:记忆写入前做一次敏感信息过滤,召回时做一次权限校验,确保当前会话有权访问这条记忆。另外,要防止提示注入攻击通过污染记忆来长期影响 Agent 行为。这块展开能写一整篇,核心原则就是:记忆是数据,数据就要有边界和权限。

4. 工具编排与错误处理:决定 Agent 能不能上生产

4.1 工具不是越多越好,而是越清晰越好

新手常犯的错是给 Agent 塞一大堆工具,觉得能力越强越好。实际上工具一多,模型选择困难,调用错误率飙升。我现在的原则是:单个 Agent 的工具数量控制在 10 个以内,超过就拆分或者做工具分组。

更重要的是工具之间的边界要清晰。如果两个工具功能有重叠,模型就会犹豫、乱调。我遇到过"查询用户"和"搜索用户"两个工具,功能几乎一样,结果模型每次都在两个之间随机选。后来合并成一个,问题立刻消失。工具设计要像好的 API 设计一样,职责单一、命名清晰、文档准确。

4.2 工具执行失败是常态,不是异常

在 demo 里工具总是成功,在生产里工具失败是家常便饭:网络超时、接口限流、参数错误、权限不足。如果 Agent 没有错误处理能力,一次工具失败整个任务就崩了。

我的做法是给每个工具调用包一层错误处理:捕获异常、把错误信息结构化、返回给模型让它决定下一步。关键是错误信息要写得让模型能理解并采取行动。比如不要返回"Error 500",而要返回"订单查询服务暂时不可用,建议稍后重试或改用其他方式获取订单信息"。模型拿到这种信息,往往能自己调整策略。

def safe_execute(tool_name, params): try: result = tools[tool_name](**params) return {"status": "success", "data": result} except TimeoutError: return {"status": "error", "message": "工具超时,可重试"} except ValidationError as e: return {"status": "error", "message": f"参数错误:{e},请检查后重新调用"} except Exception as e: return {"status": "error", "message": f"工具执行失败:{e}"}

4.3 循环终止条件必须设死

Agent 的 while 循环如果没有硬性终止条件,遇到模型反复调用同一个工具、或者陷入死循环,就会无限跑下去,烧钱还出不来结果。我见过最夸张的一次,一个 Agent 因为工具一直返回错误,模型一直重试,跑了两百多轮才被手动掐掉。

我的终止条件设了三层:最大轮次限制,比如 15 轮;连续相同工具调用检测,如果连续三次调同一个工具且参数相同,强制终止;总 token 预算限制,超过就停。这三层任何一层触发,都要给用户一个明确的失败反馈,而不是静默卡死。

4.4 可观测性:出问题时你得知道发生了什么

Agent 跑在生产里,最怕的是出问题查不到原因。所以从第一天起就要把可观测性做进去:每一轮的输入、模型的思考、工具调用、返回结果、耗时、token 消耗,全部结构化打日志。我一般会记录成一个 trace,一个任务对应一条完整链路。

有了这个 trace,排查问题就是回放。模型为什么做了这个决策、哪一步开始跑偏、哪个工具拖慢了整体,一目了然。这块投入在早期看着多余,但一旦上量,没有它你寸步难行。

5. 从能跑到好用:工程化的几个关键决策

5.1 什么时候该引入框架,什么时候该自己写

手搓内核是为了理解,但真做项目不可能什么都自己写。我的判断标准是:如果框架能帮你省下的是"重复造轮子"的活,比如工具注册、消息管理、多模型适配,那就用;如果框架要接管你的核心逻辑,让你没法控制细节,那就谨慎。

LangChain 这类框架适合快速验证想法,但它的抽象层比较厚,出问题时排查链路长。我现在的做法是核心循环自己写,外围的模型适配、向量库客户端、工具 schema 校验用成熟库。这样既控制了复杂度,又不重复造轮子。

5.2 多 Agent 编排:别为了炫技而上

现在很流行多 Agent 协作,一个负责规划、一个负责执行、一个负责审核。听起来很美,但我的经验是,绝大多数场景单 Agent 加好工具就够了。多 Agent 带来的通信开销、状态同步、错误传播问题,往往超过它带来的收益。

真要用多 Agent,我建议从"主从模式"开始:一个主 Agent 负责拆解任务和调度,若干子 Agent 负责执行具体子任务。这种结构简单、边界清晰。等这个模式跑顺了,再考虑更复杂的对等协作。上来就搞一堆 Agent 互相聊天,大概率是一地鸡毛。

5.3 成本控制是工程化绕不开的坎

Agent 比普通 LLM 调用贵得多,因为它一轮任务可能调用十几次模型。成本控制我主要靠三招:一是缓存,相同或相似的查询结果缓存起来,避免重复调用;二是模型分级,简单任务用小模型,复杂推理才用大模型;三是上下文精简,前面说的记忆压缩和工具结果提炼,本质上都是在省钱。

我算过一笔账,一个没做任何优化的 Agent,单次任务成本可能是优化后的五到十倍。这个差距在量小的时候无所谓,量一大就是生死线。

5.4 评测:没有评测就没有迭代

Agent 好不好,不能靠感觉。我现在的做法是维护一个评测集,里面是真实场景的问题和期望结果。每次改动 prompt、换模型、调检索参数,都跑一遍评测集,看准确率、成功率、平均轮次、平均成本这些指标的变化。

没有评测集,你的所有优化都是盲猜。我早期就是凭感觉调,改了半天不知道是变好了还是变坏了。有了评测集之后,每次改动都有数据支撑,迭代速度完全不一样。评测集不用一开始就很大,几十条真实 case 就能起步,关键是持续积累。

6. 一些踩坑之后才明白的事

6.1 模型能力不是瓶颈,工程细节才是

我一开始总想着换个更强的模型就能解决问题,后来发现大部分问题根本不在模型。检索召回不准、工具描述含糊、上下文塞了无关信息、错误处理缺失,这些工程细节才是决定 Agent 表现的关键。同一个模型,工程做得好和做得差,效果能差出好几倍。所以别老盯着模型排行榜,先把工程链路打磨好。

6.2 提示词要当代码来管理

Agent 的提示词不是随便写几句话,它是核心逻辑的一部分。我现在把提示词单独抽出来,做版本管理,改动走 review,配合评测集验证。这样每次改动都可追溯、可回滚。把提示词散落在代码各处,是后期维护的噩梦。

6.3 用户预期管理比技术本身更重要

Agent 再强也有边界。如果用户以为它无所不能,一旦遇到它答不了的,信任就崩了。所以我在产品层面会明确告诉用户 Agent 能做什么、不能做什么,遇到不确定的情况主动说"这个我不确定,建议你核实一下"。坦诚反而能建立信任。技术做得再好,预期管理没做好,用户体验照样差。

6.4 安全边界要从设计阶段就考虑

Agent 能调用工具、能访问数据,这意味着它的权限边界必须清晰。哪些工具它能调、哪些数据它能碰、什么操作需要人工确认,这些要在设计阶段就定死,而不是出了事再补。尤其是涉及写操作、涉及敏感数据的场景,一定要有确认机制和审计日志。这块我踩过坑,早期一个 Agent 能直接改数据库,现在想想都后怕。

从零手搓一个 Agent,最难的从来不是让它跑起来,而是让它跑得稳、跑得准、跑得起。内核那两百行代码两天就能写完,但围绕它的检索、记忆、编排、错误处理、评测、安全,才是真正花时间的地方。我自己的体会是,把每个环节都搞明白原理,比堆砌框架和工具重要得多。你对手里这套东西理解得越透,遇到问题时就越不慌。这个领域变化很快,但底层的那些工程原则,短期内不会变。

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

SOLIDWORKS小金球解锁:核显与游戏卡RealView注册表配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 19:29:02

Unity资源依赖分析实战:从依赖图构建到YooAsset与Addressable差异

1. 从一次资源加载事故说起:为什么依赖分析不是可选项几年前接手过一个二次开发项目,场景不复杂:主界面加载角色模型,模型上挂几个特效,特效引用若干贴图。功能跑起来没问题,但真机上每次切场景都会卡顿半秒…

作者头像 李华
网站建设 2026/9/26 19:27:46

ARTEX调度原理:PostgreSQL黑板机制与Planner-Worker协同设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 19:27:42

KaihongOS 5.0 X86桌面版安装指南:虚拟机与真机部署全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华