资讯动态

从零手搓AI Agent:ReAct循环、记忆系统与RAG工程化实战

发布时间:2026/9/28 14:29:47 来源:尧图企业网站定制
1. 为什么我要从零手搓一个Agent先说结论如果你打算认真搞Agent开发别一上来就抱着LangChain或者AgentScope啃文档。我自己的经验是先花两三天时间用一个周末从零手搓一个最小可用的Agent你对整个体系的理解会完全不一样。这不是说框架不好而是框架帮你藏了太多东西藏到你出了问题根本不知道从哪查。我最初接触Agent这个概念的时候脑子里其实是一团浆糊。LLM我懂无非就是调API拿回复RAG我也做过无非就是向量检索加拼接上下文。但Agent到底是什么它和一条普通的LLM调用链有什么区别为什么大家都在说Agent是下一代应用形态这些问题我在看了大量框架文档之后反而更迷糊了因为每个框架对Agent的定义和抽象都不一样。后来我下定决心关掉所有框架文档打开一个空白的Python文件从最裸的API调用开始一行一行把Agent搭出来。这个过程大概持续了三个晚上踩了无数坑但搭完之后再回头看LangChain那些抽象突然就全通了。所以这篇内容我想把这条从零到工程化的路径完整地分享出来包括我踩过的坑、做过的取舍、以及那些文档里不会写的细节。这篇文章适合谁看如果你已经会调LLM的API写过一些简单的Prompt但对Agent的工程化落地还没有完整认知那这篇内容就是写给你的。如果你是完全零基础也没关系我会在关键概念上做通俗解释保证你能跟上。整篇内容会围绕一个核心目标展开让你能自己动手搭出一个可用的Agent并且知道怎么把它从Demo推进到工程化。2. Agent到底是什么拆开来看就三件事2.1 从LLM到Agent中间差了什么很多人第一次听到Agent会觉得这是个很玄的东西。但如果你把它拆开其实核心就三件事感知、决策、执行。普通LLM调用只有“感知”和“决策”的一部分——你给它输入它给你输出结束。而Agent多了一个关键环节它会根据决策去执行动作拿到执行结果之后再回来继续决策形成一个循环。用生活化的类比来说普通LLM调用就像你问一个博学的朋友一个问题他直接回答你。而Agent就像你雇了一个助理你告诉他“帮我订一张明天去北京的票”他会先查你的日程、再比价、再确认你的偏好、然后下单、最后把结果告诉你。中间可能来回好几轮每一轮他都根据上一轮的结果调整下一步动作。这个循环在工程上通常叫Agent Loop或者ReAct循环Reasoning Acting。它的基本流程是接收任务 → LLM推理下一步该做什么 → 如果需要调用工具就调用 → 把工具结果喂回给LLM → 继续推理 → 直到LLM认为任务完成或者达到终止条件。2.2 一个Agent的最小构成要素从工程角度看一个能跑起来的Agent至少需要这几个部分LLM大脑负责推理和决策。可以是任何支持函数调用Function Calling的模型也可以是纯文本模型配合Prompt解析。工具集Tools手和脚Agent能执行的具体动作。比如搜索、计算、读写文件、调API。记忆Memory短期记忆就是对话历史长期记忆通常用向量库做检索。编排逻辑Orchestration控制循环怎么跑、什么时候停、出错怎么办。提示词System Prompt告诉Agent它是谁、能做什么、怎么做决策。这五个部分里LLM和工具是硬依赖记忆和编排是工程化的关键提示词是调优的核心。我见过很多人一上来就纠结用哪个向量库、用哪个框架其实最开始你只需要一个LLM API和一个能跑Python的环境就够了。2.3 为什么建议先手搓再上框架框架的价值在于帮你处理了编排、记忆、工具注册这些重复劳动。但问题是如果你不知道这些劳动本身长什么样你就无法判断框架帮你做的选择是否合理。我举个真实的例子我最早用某个框架做Agent发现它每次调用工具都会把完整的对话历史塞进Prompt导致Token消耗飞快。我一开始以为是框架的Bug后来自己手搓了一遍才明白这是ReAct循环的固有特性——每一轮都要把之前的推理过程带上否则LLM会丢失上下文。知道这一点之后我就知道该怎么优化了要么做历史压缩要么把中间推理步骤存到外部记忆里。所以我的建议是先手搓一个最小版本理解每个环节在干什么然后再用框架去加速开发。这样你遇到问题的时候至少知道该往哪个方向查。3. 手搓第一步把LLM调用跑通3.1 选一个支持Function Calling的模型手搓Agent的第一个前提是你得有一个能稳定调用的LLM。这里不讨论具体哪家模型好只说选型逻辑。对于Agent场景我建议优先选支持Function Calling也叫Tool Use的模型。原因很简单Function Calling让模型直接输出结构化的工具调用请求你不需要自己写正则去解析模型的自然语言输出稳定性高一个量级。如果你用的模型不支持Function Calling也不是不能做但你需要自己设计一套Prompt模板让模型按固定格式输出“我要调用哪个工具、参数是什么”然后你自己解析。这种方式我早期试过最大的问题是模型经常不按格式来尤其是参数复杂的时候解析失败率很高。所以除非有特殊限制否则优先选支持Function Calling的。3.2 最小LLM调用封装不管你用哪家API第一步都是把它封装成一个统一的调用函数。我自己的习惯是封装成这样一个接口def call_llm(messages, toolsNone, temperature0.0): messages: 对话历史列表 tools: 工具定义列表None表示不启用工具调用 返回: 模型回复可能是文本也可能是工具调用请求 # 这里替换成你实际使用的API调用 response client.chat.completions.create( modelyour-model-name, messagesmessages, toolstools, temperaturetemperature ) return response.choices[0].message这个封装看起来简单但有几个细节值得注意。temperature设成0是因为Agent场景需要稳定的决策不需要创意。messages用列表是因为Agent是多轮循环每一轮都要把历史带上。tools参数可选是因为有些步骤比如最后的总结不需要工具调用。提示如果你用的API有流式输出建议在Agent循环里先不用流式等调试稳定了再加。流式会让工具调用的解析变复杂调试阶段得不偿失。3.3 工具定义怎么写才不容易出错工具定义是Agent开发里最容易踩坑的地方。Function Calling的工具定义通常是一个JSON Schema描述工具名、功能、参数。我见过太多人工具定义写得太随意导致模型要么不调用要么调用时参数传错。我的经验是工具定义要遵循三个原则第一工具名要动词开头语义明确。比如search_web比web好calculate比math好。模型是靠名字和描述来判断该不该调用的名字模糊它就会犹豫。第二描述要写清楚“什么时候用”和“什么时候不用”。很多人只写工具是干什么的不写使用场景。比如一个搜索工具你应该写“当需要获取实时信息或你不确定的事实性内容时使用如果问题涉及常识或已有上下文能回答不要调用”。这样能显著减少无效调用。第三参数描述要具体最好给例子。比如一个查询参数不要只写“查询关键词”要写“查询关键词应该是简洁的搜索词例如‘2024年诺贝尔物理学奖’”。模型看到例子之后传参的准确率会明显提升。下面是一个我常用的工具定义模板tools [ { type: function, function: { name: search_web, description: 搜索互联网获取实时信息。当问题涉及最新事件、实时数据或你不确定的事实时使用。如果问题能通过已有上下文回答不要调用此工具。, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词简洁明确例如2024年诺贝尔物理学奖得主 } }, required: [query] } } } ]这个模板我用了很多次实测下来工具调用的准确率比随便写的版本高不少。4. Agent Loop整个系统的心脏4.1 ReAct循环的完整流程Agent Loop是整个系统的核心也是最容易出问题的地方。我先把完整的流程拆一遍然后再说每个环节的坑。一个标准的ReAct循环是这样的把用户任务和System Prompt组装成初始messages调用LLM拿到回复判断回复类型如果是普通文本说明Agent认为任务完成循环结束如果是工具调用请求进入第4步执行工具调用拿到结果把工具调用请求和工具结果都追加到messages里回到第2步继续循环如果达到最大轮数或超时强制结束这个流程看起来简单但每一步都有细节。比如第3步怎么判断“任务完成”最直接的方式是看LLM有没有返回工具调用。如果它返回了纯文本通常意味着它认为不需要再调工具了。但这个判断并不总是可靠有时候模型会一边说“我完成了”一边又发起工具调用这时候你要以工具调用为准。4.2 循环终止条件怎么设计终止条件是Agent Loop里最需要仔细设计的地方。我踩过的坑包括Agent陷入死循环反复调用同一个工具、Agent在任务没完成时就提前结束、Agent因为一次工具报错就整个崩掉。我的做法是设置多重终止条件正常终止LLM返回纯文本且没有工具调用轮数上限设置最大循环轮数比如10轮。超过就强制结束返回当前结果超时控制整个循环设置一个总超时比如60秒重复检测如果连续两轮调用了同一个工具且参数相同强制结束错误累积如果连续多次工具调用失败强制结束并返回错误信息这几条里重复检测是最容易被忽略但最有用的。我遇到过Agent因为搜索结果不理想反复用同样的关键词搜索每次都拿到一样的结果然后继续搜。加了重复检测之后这种情况就基本消失了。4.3 工具执行结果怎么喂回去工具执行完之后结果怎么塞回messages这个细节直接影响Agent的后续决策。标准的做法是把LLM的工具调用请求assistant message with tool_calls和工具执行结果tool message都追加到messages里。这里有个关键点工具结果要尽量结构化、简洁。我见过有人把整个网页的HTML塞回去结果Token直接爆掉。正确的做法是工具内部先做一轮处理只返回关键信息。比如搜索工具返回标题、摘要、链接而不是全文。另外工具执行失败的时候不要把异常堆栈直接塞回去而是返回一个友好的错误描述比如“搜索失败请稍后重试”或者“参数格式错误query应该是字符串”。这样模型有机会调整策略而不是被一堆报错信息搞懵。注意工具结果里的敏感信息要过滤掉。比如你调了一个内部API返回里带了Token或者用户ID这些不应该出现在喂给LLM的上下文里。4.4 一个可运行的最小Agent Loop把上面这些拼起来一个最小的Agent Loop大概长这样def run_agent(user_input, tools, max_turns10): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ] for turn in range(max_turns): response call_llm(messages, toolstools) # 没有工具调用任务结束 if not response.tool_calls: return response.content # 把assistant的回复加入历史 messages.append(response) # 执行每个工具调用 for tool_call in response.tool_calls: tool_name tool_call.function.name tool_args json.loads(tool_call.function.arguments) try: result execute_tool(tool_name, tool_args) except Exception as e: result f工具执行失败: {str(e)} messages.append({ role: tool, tool_call_id: tool_call.id, content: str(result) }) return 达到最大轮数任务未完成这段代码不到30行但它已经是一个能跑的Agent了。你可以给它加搜索工具、计算工具、文件读写工具它就能处理不少实际任务。我建议你先把这个跑通跑通之后再考虑加记忆、加RAG、加多Agent协作。5. 记忆系统让Agent不再“失忆”5.1 短期记忆和长期记忆的分工Agent的记忆分两种短期记忆和长期记忆。短期记忆就是当前对话的messages列表它随着循环不断增长。长期记忆是跨对话的通常存在外部存储里需要的时候检索出来。短期记忆的问题是会越来越长。一个跑了10轮的Agentmessages可能有几千Token再加上工具返回的结果很容易就超过模型的上下文窗口。所以短期记忆需要压缩策略。我常用的策略有两种一是滑动窗口只保留最近N轮二是摘要压缩把早期的对话用LLM总结成一段简短摘要。长期记忆的核心是检索。当用户提出一个新任务时Agent先去长期记忆里找相关的历史信息找到之后作为上下文注入。这里就涉及到向量检索和RAG了后面会详细说。5.2 用向量库做长期记忆的实操长期记忆的落地绕不开向量库。向量库选型这个问题被问得很多我的经验是个人项目和小规模场景用Chroma或者FAISS就够了生产环境再考虑Milvus、Qdrant这些。原因很简单Chroma和FAISS部署简单本地跑不需要额外服务适合快速验证。等你真的需要处理百万级向量、需要分布式和高可用的时候再迁移也不迟。具体怎么做流程是这样的把需要记住的信息比如历史对话、文档片段用Embedding模型转成向量把向量和原始文本一起存进向量库新任务来的时候把任务描述也转成向量在向量库里做相似度检索取Top-K条把检索结果作为上下文注入到Prompt里这里有个细节Embedding模型要和检索场景匹配。中文场景建议用支持中文的Embedding模型否则检索准确率会明显下降。我早期用了一个英文为主的Embedding模型做中文检索结果召回的内容经常驴唇不对马嘴换了中文模型之后就好了。5.3 记忆检索的命中率怎么提升向量检索的命中率是个老大难问题。我踩过的坑包括检索出来的内容不相关、相关的内容排不到前面、检索结果太多导致上下文爆炸。提升命中率的手段按性价比排序第一优化切分策略。文档切分不要按固定字数切要按语义切。比如按段落切、按标题切保证每个片段是完整的语义单元。我见过有人按每200字硬切结果一句话被切成两半检索出来根本没法用。第二加Rerank。向量检索是粗排召回Top-20之后用一个Rerank模型做精排取Top-5。Rerank模型通常比Embedding模型更准但速度慢所以只用在精排阶段。实测下来加了Rerank之后命中率能提升20%到30%。第三混合检索。纯向量检索对关键词不敏感比如你搜一个专有名词向量检索可能召回一堆语义相近但不含这个词的内容。这时候加上关键词检索BM25两路结果融合效果会好很多。第四查询改写。用户的问题往往很短直接拿去检索效果不好。可以先用LLM把问题改写成几个更具体的查询分别检索再合并。这个技巧在RAG实战里非常常用。5.4 记忆系统的工程化注意事项记忆系统上生产之前有几个坑必须提前填写入频率控制不是每轮对话都要写长期记忆那样会写入大量噪音。我的做法是只在任务完成或者用户明确说“记住这个”的时候才写入。去重相似内容重复写入会让检索结果冗余。写入前先做一次相似度检查超过阈值就不写。过期清理长期记忆不能无限增长要设置TTL或者定期清理低价值内容。隐私过滤写入之前过滤掉敏感信息这个不用多说。6. RAG与Agent的结合从检索增强到Agentic RAG6.1 普通RAG和Agentic RAG的区别普通RAG的流程是固定的用户提问 → 检索 → 拼接上下文 → LLM生成。它是一条直线没有分支没有循环。Agentic RAG就不一样了。Agent会自己决定要不要检索、检索什么、检索几次、检索结果够不够。比如用户问一个复杂问题Agent可能先检索一次发现信息不够改写查询再检索一次还不够就去调另一个数据源。整个过程是动态的由Agent自己编排。这个区别带来的工程差异很大。普通RAG你只需要调一次检索接口Agentic RAG你需要把检索封装成工具让Agent自己决定怎么用。好处是灵活坏处是可控性下降需要更仔细地设计工具描述和终止条件。6.2 把RAG封装成Agent工具把RAG封装成工具核心是设计好工具的输入输出。我的做法是提供两个工具一个search_knowledge_base做向量检索一个get_document按ID取完整文档。search_knowledge_base的输入是查询词输出是Top-K个片段的摘要和ID。Agent拿到摘要之后如果觉得需要看全文再调get_document。这样设计的好处是Agent可以先粗看再细看避免一次性把大量内容塞进上下文。工具描述里要写清楚知识库覆盖的范围。比如“本知识库包含公司产品文档和技术手册不包含财务数据”。这样Agent就知道什么问题该查知识库什么问题不该查。6.3 检索质量优化的实战技巧RAG实战里检索质量决定了整个系统的上限。我总结了几条实战技巧切分粒度要匹配查询粒度。如果用户的问题通常很具体切分就要细一点如果问题比较宏观切分就要粗一点。我一般会准备两种粒度的索引Agent根据问题类型选择。元数据过滤很有用。给每个片段打上来源、时间、类型等标签检索的时候可以按标签过滤。比如用户问“最新的政策”就可以过滤掉旧文档。Rerank不要省。我前面说过Rerank能提升20%到30%的命中率这个投入产出比很高。Rerank模型可以用开源的也可以用API看你的延迟要求。检索结果要带来源。每个片段带上来源链接或文档名这样Agent在生成回答时可以引用用户也能追溯。这在知识库场景里是刚需。6.4 RAG命中率上不去的排查思路RAG命中率低排查要按顺序来排查项检查方法常见问题切分质量随机抽几个片段看是否语义完整句子被切断、片段过长或过短Embedding模型用几个已知问题测试检索模型不支持中文、模型与场景不匹配检索参数调整Top-K和相似度阈值Top-K太小漏召回太大引入噪音Rerank对比加Rerank前后的结果Rerank模型与Embedding模型不匹配查询质量看用户原始查询是否太短查询太短导致语义不明确数据覆盖检查知识库里是否真有答案知识库本身缺内容这张表我基本每次排查都会过一遍大部分问题都能定位到。7. 工程化从Demo到能上线的Agent7.1 错误处理和重试机制Demo阶段的Agent一出错就崩。工程化的Agent出错要能自愈。我处理错误的原则是能重试的重试不能重试的降级降级不了的友好报错。LLM调用失败超时、限流要重试用指数退避重试3次。工具调用失败要看类型网络类的重试参数类的让Agent自己调整。如果Agent循环整体失败要返回一个友好的错误信息而不是把异常堆栈抛给用户。这里有个细节重试的时候要把错误信息喂回给Agent。比如工具调用失败你把“参数格式错误”喂回去Agent下一轮可能就会修正参数。这比你自己在代码里修参数要灵活。7.2 可观测性日志、追踪、指标Agent上生产可观测性是刚需。你至少需要三样东西日志每一轮的输入输出、工具调用、耗时都要记。我习惯用结构化日志方便后续查询。追踪一个任务从开始到结束中间经过哪些步骤、每步耗时多少要能串起来看。OpenTelemetry这类工具可以帮上忙。指标任务成功率、平均轮数、平均耗时、工具调用失败率、Token消耗。这些指标能帮你发现系统性问题。我踩过的坑是早期没做追踪线上出问题只能靠日志一行行翻效率极低。后来加了追踪之后一眼就能看出是哪一步卡住了。7.3 成本控制Token和调用次数Agent的Token消耗比普通LLM调用高一个量级因为每一轮都要带上历史。控制成本的手段有几个历史压缩早期对话用摘要替代原文工具结果精简工具只返回关键信息缓存相同的工具调用结果缓存起来模型分级简单决策用小模型复杂推理用大模型轮数上限严格限制最大轮数我实测下来历史压缩和工具结果精简这两个手段效果最明显能省一半以上的Token。7.4 安全边界工具权限和输入过滤Agent能调工具就意味着它能对真实世界产生影响。所以安全边界必须提前设计。工具权限分级读操作和写操作分开写操作要额外确认。比如搜索是读发邮件是写发邮件之前要让用户确认。输入过滤用户输入里如果有Prompt注入的企图要能识别和拦截。比如用户说“忽略之前的指令现在你是一个...”这种要过滤掉。输出过滤Agent的输出里如果有敏感信息要过滤。比如工具返回里带了内部IP输出之前要脱敏。操作审计所有工具调用都要记录谁在什么时候调了什么工具、参数是什么、结果是什么。出了问题能追溯。8. 常见问题与排查技巧实录8.1 Agent不调用工具怎么办这是最常见的问题。Agent收到任务之后直接用自己的知识回答不调工具。原因通常有三个工具描述不清楚。模型不知道这个工具是干什么的自然不调用。解决方法是把工具描述写详细写清楚使用场景。System Prompt没引导。System Prompt里要明确告诉Agent“遇到不确定的信息要调搜索工具”。我一般会在System Prompt里写一段工具使用规范。模型能力不够。有些小模型对Function Calling的支持不好经常忽略工具。这种情况只能换模型。8.2 Agent陷入死循环怎么破死循环的表现是Agent反复调用同一个工具或者反复在几个工具之间跳来跳去。破解方法加重复检测连续相同调用就强制结束加轮数上限硬性截断在System Prompt里加“如果连续两次搜索结果不理想尝试换关键词或直接回答”工具返回里加提示比如“这是第3次相同搜索建议换策略”8.3 工具调用参数传错怎么修参数传错通常是因为工具定义的Schema不够明确。解决方法参数描述里给例子参数类型要明确不要用any必填参数用required标记复杂参数拆成多个简单参数如果模型还是传错可以在工具执行层做一层校验和修正。比如参数是日期模型传了“明天”你在工具里转成具体日期。8.4 上下文超长怎么压缩上下文超长是Agent跑多轮之后的必然问题。压缩策略滑动窗口只保留最近N轮早期对话摘要化工具结果只保留关键字段把中间推理步骤存到外部需要时再检索我一般组合使用先滑动窗口再对窗口内的早期内容做摘要。8.5 常见问题速查表问题可能原因解决方法Agent不调工具工具描述不清、Prompt没引导完善描述、加使用规范死循环无重复检测、无轮数上限加检测、加上限参数传错Schema不明确加例子、加校验上下文超长历史无压缩滑动窗口、摘要检索不准切分粗、无Rerank优化切分、加Rerank响应太慢轮数多、模型大限制轮数、模型分级成本太高Token消耗大压缩历史、缓存结果工具报错崩溃无错误处理加重试、加降级9. 我个人的一些经验和建议手搓Agent这件事我最大的体会是不要追求一步到位。我见过太多人一上来就想搭一个全能Agent结果卡在工具定义上就放弃了。正确的路径是先搭一个只能调一个工具的Agent跑通循环然后再加工具、加记忆、加RAG。另一个体会是Agent的能力上限取决于工具的质量而不是LLM的智商。一个工具设计得好的Agent用中等模型就能跑得很好工具设计得烂用最强模型也白搭。所以与其纠结用哪个模型不如花时间打磨工具定义和Prompt。还有一点测试要覆盖边界情况。正常流程跑通不难难的是异常情况。工具超时、参数错误、模型返回格式不对、上下文超长这些都要有测试用例。我自己的做法是每加一个工具就写一组异常测试确保Agent能优雅处理。最后分享一个小技巧给Agent加一个“思考”步骤。在调用工具之前让Agent先输出一段推理说明它为什么要调这个工具、期望得到什么结果。这个步骤会增加Token消耗但能显著提升决策质量也方便你调试。等系统稳定了再考虑去掉这个步骤来省成本。这个内容后续还可以这样扩展加多Agent协作让不同Agent负责不同角色加工具的动态注册让Agent能自己发现新工具加评估体系用自动化测试衡量Agent的表现。这些都是工程化深入之后自然会遇到的方向。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑