ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Demo到生产:Agent工程化实战与RAG调优指南

从Demo到生产:Agent工程化实战与RAG调优指南 Agent 这个词在过去一年里被用得太泛了。打开任何一个技术社区满屏都是三行代码搭建你的第一个 AgentAgent 开发速成指南但真正落到工程里你会发现事情远没有那么简单——Demo 跑通只要一个下午可要让它在生产环境里稳定处理几百轮对话、正确调用十几个工具、在检索结果里精准找到那一条关键信息那是另一个维度的挑战。我从去年开始陆续做了几个 Agent 相关的项目从最朴素的 ReAct 循环到带 RAG 的知识库问答再到多工具编排的复杂流程踩过的坑比写过的代码还多。这篇内容就是把这些经验整理出来从零开始讲清楚一个 Agent 到底该怎么搭、每个环节为什么这么设计、工程化落地时哪些地方最容易翻车。不管你是刚接触 Agent 开发的新手还是已经跑通过 Demo 想往生产环境推进的开发者应该都能从中找到对自己有用的东西。1. 先搞清楚 Agent 到底在解决什么问题1.1 从对话到行动的跨越很多人第一次接触 Agent 的时候会把它理解成更聪明的聊天机器人。这个理解不算错但漏掉了最关键的一点Agent 的核心价值不在于它能聊什么而在于它能做什么。普通的 LLM 对话是你问我答输入一段文本输出一段文本整个交互在一个回合内结束。而 Agent 的本质是在 LLM 的基础上加了一层行动能力——它能决定调用什么工具、按什么顺序调用、拿到结果之后下一步该干什么直到完成用户交代的任务。这个区别看起来简单但带来的工程复杂度是指数级上升的。纯对话场景下你只需要关心 prompt 写得好不好、模型选得对不对。而 Agent 场景下你要关心工具的定义是否清晰、参数校验是否严格、多轮循环的终止条件是否可靠、中间状态怎么管理、失败了怎么重试、超时了怎么兜底。这些才是 Agent 开发真正花时间的地方。我刚开始做 Agent 的时候觉得最核心的是 prompt 工程后来发现 prompt 只是冰山一角。真正决定一个 Agent 能不能用的是它的工具调用链路是否健壮、错误处理是否完善、上下文管理是否合理。这些工程层面的东西才是区分玩具和产品的关键。1.2 Agent 的四种典型形态在实际项目中Agent 并不是一个统一的概念根据复杂度和应用场景大致可以分成四种形态每种形态的技术选型和工程重点都不一样。第一种是单轮工具调用用户提一个需求Agent 判断需要调用某个工具拿到结果后直接返回。这种最简单本质上就是 function calling很多场景下已经够用了。第二种是多轮推理循环也就是经典的 ReAct 模式Agent 会反复进行思考-行动-观察的循环直到任务完成。第三种是带知识库的 RAG Agent在推理过程中会检索外部知识来辅助决策适合需要大量领域知识的场景。第四种是多 Agent 协作多个 Agent 各司其职通过消息传递协同完成复杂任务。大部分开发者从第一种或第二种起步就够了不要一上来就搞多 Agent 协作那个复杂度不是线性增长的而是组合爆炸。我见过不少团队在需求还没理清楚的情况下就上了多 Agent 架构结果调试成本高得离谱最后又退回到单 Agent 方案。1.3 什么场景适合用 Agent不是所有场景都适合上 Agent。我的判断标准很简单如果这个任务需要动态决策——也就是下一步做什么取决于上一步的结果而且这个决策逻辑很难用固定规则穷举——那才值得用 Agent。反过来如果流程是固定的、步骤是确定的那用工作流引擎或者简单的函数编排就够了没必要引入 LLM 的不确定性。举个例子用户问帮我查一下上个月的销售数据并生成报表这个任务里查数据和生成报表的顺序是固定的用工作流就能搞定。但如果用户问帮我分析一下上个月销售下滑的原因Agent 就需要先查数据、再判断可能的原因、然后针对性地查更多维度的数据来验证假设这个决策路径是动态的才真正需要 Agent 的能力。2. 手搓 Agent 的最小可行架构2.1 核心循环ReAct 模式的工程化拆解ReActReasoning Acting是绝大多数 Agent 的基础架构它的核心逻辑就是一个循环LLM 先思考当前该做什么然后选择一个工具执行拿到结果后继续思考直到判断任务完成或者达到最大轮次。听起来简单但工程实现里有几个关键决策点。第一个决策点是循环的终止条件。最理想的情况是 LLM 自己判断任务完成并输出终止信号但实际中 LLM 经常会陷入死循环反复调用同一个工具。所以你必须设置硬性的最大轮次限制一般设 5 到 10 轮比较合理。超过这个轮次还没完成要么是任务太复杂需要拆分要么是工具设计有问题。第二个决策点是中间结果的存储方式。每一轮的思考、行动、观察结果都需要拼接到上下文里传给下一轮但上下文长度是有限的。如果任务轮次多、工具返回结果长很快就会超出模型的上下文窗口。我的做法是对工具返回结果做截断和摘要只保留关键信息同时把完整的中间结果存到外部状态里需要的时候再取。第三个决策点是工具调用的错误处理。工具调用失败是常态可能是参数格式不对、可能是外部服务超时、可能是返回结果不符合预期。Agent 必须能区分可重试的错误和不可重试的错误前者自动重试后者把错误信息反馈给 LLM 让它换个思路。# 一个简化的 ReAct 循环骨架 def react_loop(query, tools, max_steps8): context [{role: user, content: query}] for step in range(max_steps): # LLM 决策是调用工具还是直接回答 response llm.chat(context, toolstools) if response.is_final_answer: return response.content # 执行工具调用 try: tool_result execute_tool( response.tool_name, response.tool_args ) context.append({ role: tool, content: truncate(tool_result, max_len2000) }) except RetryableError as e: context.append({ role: tool, content: f调用失败可重试{e} }) except FatalError as e: context.append({ role: tool, content: f调用失败请换方式{e} }) return 达到最大轮次限制任务未完成这段代码看起来简单但每一行背后都有讲究。比如truncate的阈值设多少、错误信息怎么措辞才能让 LLM 正确理解、max_steps设多少才不会浪费 token这些都需要根据实际场景调。2.2 工具定义Agent 的能力边界工具就是 Agent 的手和脚工具定义得好不好直接决定了 Agent 能不能用。我见过太多项目Agent 推理逻辑写得没问题但工具定义一塌糊涂导致 LLM 要么选错工具要么传错参数。工具定义的核心是描述要精准。LLM 选择工具的唯一依据就是你给的描述如果描述含糊它就会瞎猜。一个好的工具描述应该包含三部分这个工具做什么、什么情况下用、参数是什么意思。比如查询天气这个工具描述不能只写查询天气而要写根据城市名称查询当前天气状况适用于用户询问某地天气、温度、是否下雨等场景。参数 city 为城市中文名称如北京、上海。参数定义同样重要。每个参数都要有类型、描述、是否必填、取值范围。特别是枚举类型的参数一定要把所有可能的值列出来否则 LLM 会自己编。我踩过一个坑有个工具的参数是时间范围我没限定取值结果 LLM 传了个最近一段时间进来直接导致下游解析失败。还有一个容易被忽略的点是工具的数量。很多人觉得工具越多 Agent 能力越强但实际上工具太多会导致 LLM 选择困难准确率反而下降。我的经验是单个 Agent 的工具数量控制在 10 个以内超过的话就要考虑分组或者拆分成多个 Agent。如果确实需要很多工具可以用两级选择先让 LLM 选工具类别再在类别内选具体工具。2.3 上下文管理被低估的工程难点上下文管理是 Agent 开发里最容易被低估的部分。Demo 阶段对话轮次少、工具返回短感觉不到问题。一旦上了生产用户可能连续对话几十轮工具返回的 JSON 动辄几千字上下文很快就爆了。我的处理策略是分层管理。系统提示词永远保留这是 Agent 的行为准则。最近的 N 轮对话完整保留保证短期记忆连贯。更早的对话做摘要压缩只保留关键信息。工具返回结果按需保留如果后续轮次不再需要就丢弃如果需要就存到外部再按 ID 引用。具体实现上我会给每条消息打上优先级标签。系统提示词是 P0最近三轮是 P1工具调用的关键结果是 P2历史对话摘要是 P3。当上下文接近上限时从 P3 开始丢弃。这样能保证最重要的信息永远在。注意上下文压缩是有损的摘要做得不好会丢失关键信息。我的做法是让 LLM 自己生成摘要而不是用规则截断因为 LLM 更清楚哪些信息对后续推理重要。2.4 一个能跑的最小实现把上面这些拼起来一个最小的 Agent 大概需要这几个模块LLM 调用层负责和模型交互、工具注册中心管理所有可用工具、执行引擎跑 ReAct 循环、上下文管理器处理消息历史、状态存储持久化中间结果。我建议新手先用现成的框架跑通流程比如 LangChain 或者 AgentScope理解各个模块的职责之后再根据自己的需求做定制。不要一上来就手搓所有东西那样学习曲线太陡容易在细节里迷失。框架帮你处理了 80% 的通用逻辑你只需要关注剩下 20% 的业务特定部分。但也要注意框架不是银弹。当你的需求变得复杂框架的抽象反而会成为束缚。我现在的做法是核心循环自己写工具管理和上下文管理用框架的组件这样既有灵活性又不用重复造轮子。3. 给 Agent 装上知识库RAG 的实战细节3.1 为什么 Agent 需要 RAGLLM 有两个硬伤一是知识有截止日期训练数据之后发生的事情它不知道二是它不知道你的私有数据公司内部的文档、产品手册、客户资料这些它一概不知。RAG检索增强生成就是解决这个问题的标准方案——在 Agent 推理的时候先去知识库里检索相关内容把检索结果作为上下文一起喂给 LLM。但 RAG 不是简单的存进去、查出来。我见过太多 RAG 项目Demo 阶段效果惊艳上线之后用户一用就发现答非所问。问题往往出在检索环节——要么是切分策略不对要么是向量模型选得不好要么是召回的结果不相关。Agent 场景下的 RAG 和普通 RAG 还有一个区别Agent 是主动检索的。普通 RAG 是用户问一个问题系统检索一次生成回答。而 Agent 可以在推理过程中多次检索根据中间结果调整检索策略。这就对检索的质量和速度提出了更高要求。3.2 文档切分RAG 效果的第一道关卡文档切分是 RAG 里最基础也最关键的环节。切分策略直接决定了检索的粒度切得太碎会丢失上下文切得太大又会引入噪声。我的经验是按语义切分而不是按固定长度切分。固定长度切分比如每 500 字一段实现简单但经常把一句话从中间切断或者把不相关的内容塞进同一个 chunk。语义切分则是根据段落、标题、句子边界来切保证每个 chunk 是一个完整的语义单元。具体操作上我会先用文档结构做粗切分按标题层级然后在每个章节内部按段落切分如果单个段落太长再按句子切分。每个 chunk 的大小控制在 200 到 500 字之间这个范围在检索精度和上下文完整性之间比较平衡。还有一个技巧是给每个 chunk 加上元数据比如它属于哪个文档、哪个章节、前后文是什么。检索的时候可以带上这些元数据一起返回让 LLM 有更完整的上下文。我甚至会把相邻的 chunk 也一起返回因为很多问题的答案跨在多个 chunk 之间。# 语义切分的简化实现 def semantic_chunk(document, max_chunk_size500): chunks [] # 先按标题切分 sections split_by_heading(document) for section in sections: # 再按段落切分 paragraphs split_by_paragraph(section) current_chunk for para in paragraphs: if len(current_chunk) len(para) max_chunk_size: current_chunk para else: if current_chunk: chunks.append({ content: current_chunk, metadata: { section: section.title, source: document.source } }) current_chunk para if current_chunk: chunks.append({ content: current_chunk, metadata: { section: section.title, source: document.source } }) return chunks3.3 向量检索与关键词检索的取舍向量检索是 RAG 的主流方案它把文本转成向量然后通过计算向量相似度来找相关内容。优点是能理解语义用户问怎么退款能匹配到退货流程的文档。但它也有短板对精确匹配不敏感比如用户搜一个具体的订单号或者产品型号向量检索经常找不到。所以实际项目里我一般会用混合检索向量检索负责语义匹配关键词检索比如 BM25负责精确匹配两路结果合并后做重排序。这样既能理解语义又不会漏掉精确匹配的结果。向量数据库的选型也是个问题。小规模场景几万条以内用 FAISS 或者 Chroma 就够了部署简单、零依赖。中等规模百万级可以考虑 Milvus 或者 Qdrant支持分布式和更丰富的索引类型。大规模场景就要考虑专门的向量数据库服务了。选型的时候不要只看性能指标还要考虑运维成本、社区活跃度、和现有技术栈的兼容性。向量库适用规模部署复杂度特点FAISS万级以下低本地库无需服务适合原型Chroma十万级低轻量API 友好适合中小项目Qdrant百万级中支持过滤和混合检索性能好Milvus千万级高分布式功能全运维成本高3.4 Rerank让检索结果更精准检索出来的结果往往有几十条但真正相关的可能只有两三条。如果全部塞给 LLM不仅浪费 token还会引入噪声干扰判断。Rerank 就是解决这个问题的——用一个专门的模型对检索结果重新排序把最相关的排到前面。Rerank 模型和向量模型不一样它通常是交叉编码器Cross-Encoder把 query 和 document 拼在一起输入模型直接输出相关性分数。这种方式比向量相似度更准但计算量也更大所以一般只用于对少量候选结果做精排。我的流程是向量检索召回 Top 50然后用 Rerank 模型精排出 Top 5最后把这 5 条喂给 LLM。实测下来加了 Rerank 之后回答准确率能提升 20% 到 30%特别是对于那些需要精确匹配的问题效果提升非常明显。Rerank 模型的选择上开源的有 BGE-Reranker、Cohere Rerank 等效果都不错。如果对延迟敏感可以用小一点的模型如果追求效果就用大模型。我一般会准备两个档位简单问题用小模型复杂问题用大模型。3.5 RAG 效果调优的实战经验RAG 调优是个系统工程没有一招鲜的办法。我总结下来影响效果的因素按重要性排序大概是文档质量 切分策略 检索策略 Rerank 生成 prompt。文档质量是根基。如果原始文档本身就有问题——格式混乱、内容过时、信息重复——那后面怎么调都白搭。我一般会先花时间做数据清洗把 PDF 里的乱码、表格错位、页眉页脚这些噪声去掉保证入库的文档是干净的。切分策略是第二重要的。我踩过最大的坑就是切分太碎导致检索出来的 chunk 只有半句话LLM 根本没法用。后来改成按语义切分并且保证每个 chunk 至少包含一个完整的语义单元效果好了很多。检索策略上混合检索基本是标配了。纯向量检索在精确匹配场景下表现太差加上关键词检索能补上这个短板。Rerank 则是锦上添花有条件一定要加。生成 prompt 反而没那么关键只要把检索结果清晰地传给 LLM并明确告诉它基于以下资料回答资料里没有的不要编一般都不会出大问题。4. 从 Demo 到生产工程化的那些坑4.1 错误处理与重试机制Demo 阶段最容易被忽略的就是错误处理。本地跑的时候一切正常一上线各种问题就来了LLM API 超时、工具服务不可用、返回结果格式不对、上下文超长被截断。这些在 Demo 里都是小概率事件在生产里却是每天都在发生。我的错误处理策略分三层。第一层是重试对于网络超时、限流这类临时性错误自动重试 2 到 3 次每次间隔指数退避。第二层是降级如果重试还是失败就切换到备用方案比如换一个模型、换一个工具、或者返回一个兜底回答。第三层是熔断如果某个工具连续失败多次就暂时把它从可用工具列表里移除避免 Agent 反复调用一个坏掉的工具。这里有个细节错误信息要原样传给 LLM不要自己加工。我一开始会把错误信息包装成工具调用失败请重试结果 LLM 不知道具体错在哪就一直重试同样的调用。后来改成把原始错误信息传过去LLM 看到参数 city 不能为空就知道要补参数看到服务暂时不可用就知道要换工具。4.2 可观测性看不见的 Agent 最危险Agent 的执行过程是个黑盒LLM 为什么选这个工具、为什么这么推理你从最终结果里看不出来。没有可观测性出了问题根本没法排查。我的做法是全链路日志。每一轮的输入上下文、LLM 的原始输出、工具调用的参数和结果、耗时、token 消耗全部记录下来。这些日志在排查问题时非常有用比如发现某个工具调用特别频繁可能是工具描述有歧义发现某类问题总是走到最大轮次可能是任务太复杂需要拆分。除了日志我还会记录一些关键指标平均轮次、工具调用成功率、任务完成率、平均延迟、token 消耗。这些指标能帮你快速定位系统性问题。比如工具调用成功率突然下降可能是某个外部服务挂了平均轮次突然上升可能是模型更新后行为变了。提示日志里不要记录用户的敏感信息比如身份证号、手机号这些。如果必须记录先做脱敏处理。4.3 性能优化延迟和成本的平衡Agent 的延迟是用户体验的大敌。一次完整的 Agent 执行可能涉及多次 LLM 调用和工具调用每次都要几百毫秒到几秒累加起来用户等个十几秒很正常。优化延迟有几个方向。并行化是最直接的手段。如果多个工具调用之间没有依赖关系就并行执行。比如用户问北京和上海今天天气怎么样两个城市的天气查询可以同时发起不用串行。缓存也很有效。相同的查询、相同的工具调用结果直接返回缓存不用重新执行。特别是知识库检索同一个问题可能被不同用户反复问到缓存命中率会很高。模型分级是另一个思路。不是所有决策都需要用大模型简单的工具选择可以用小模型复杂的推理再用大模型。我一般会准备两个模型简单场景用小模型复杂场景用大模型成本能降一半以上。成本控制上token 消耗是大头。除了前面说的上下文压缩还可以通过精简工具描述、减少不必要的工具调用、用更便宜的模型来做简单任务来降本。我算过一笔账一个优化良好的 Agenttoken 消耗能比 naive 实现低 60% 以上。4.4 安全与权限控制Agent 能调用工具就意味着它能产生实际影响。如果工具里有删除数据发送邮件执行命令这类操作一旦被恶意利用或者误触发后果很严重。我的做法是最小权限原则。每个工具只授予完成其功能所需的最小权限比如查询工具只给读权限不给写权限。对于危险操作加二次确认机制让 Agent 在执行前先请求用户确认。输入校验也很重要。用户输入的内容不能直接拼到工具参数里要做转义和校验防止注入攻击。特别是涉及文件路径、SQL 查询、系统命令的工具一定要严格校验参数格式。还有一个容易被忽略的点是输出过滤。Agent 的输出可能包含敏感信息比如从知识库里检索到的内部文档内容。在返回给用户之前要做一遍敏感信息检测和过滤。5. 进阶方向Agentic RAG 与多 Agent 协作5.1 Agentic RAG让检索更聪明传统的 RAG 是一次检索一次生成Agentic RAG 则是让 Agent 自己决定什么时候检索、检索什么、检索几次。这解决了一个核心问题不是所有问题都需要检索也不是所有问题一次检索就能解决。比如用户问我们公司和竞争对手的产品相比有什么优势Agent 可以先检索自己公司的产品文档再检索竞争对手的信息然后对比分析。如果第一次检索的结果不够它还可以调整检索词再查一次。这种动态的检索策略比固定流程的 RAG 灵活得多。实现 Agentic RAG 的关键是把检索也做成一个工具让 Agent 自己决定什么时候调用。同时要给 Agent 提供判断检索结果是否足够的能力这可以通过在 prompt 里加入评估步骤来实现。5.2 多 Agent 协作的适用场景多 Agent 协作听起来很酷但实际项目中要谨慎使用。它的优势在于分工——不同的 Agent 专注于不同的领域每个 Agent 的 prompt 和工具都可以针对性优化。但劣势也很明显通信开销大、调试困难、容易出现死循环。我目前只在一种场景下用多 Agent任务可以明确拆分成多个独立子任务且子任务之间依赖关系简单。比如一个研究报告生成任务可以拆成资料检索 Agent数据分析 Agent报告撰写 Agent三个 Agent 串行工作。如果子任务之间需要频繁交互那还是用单 Agent 更靠谱。5.3 从单 Agent 到 Agent 平台的演进路径如果你做的 Agent 只是内部工具单 Agent 就够了。但如果要对外提供服务支持多个用户、多种任务那就需要考虑平台化。平台化的核心是抽象出通用的能力统一的工具注册和管理、统一的上下文和状态管理、统一的监控和告警、统一的权限控制。演进路径一般是先跑通单 Agent验证核心价值然后抽象出通用组件支持多个 Agent 复用最后做成平台让业务方可以自助配置 Agent。每一步都要根据实际需求来不要为了平台化而平台化。我在实际项目中的体会是Agent 开发最难的不是技术而是对业务的理解。你得清楚用户真正需要什么、任务的成功标准是什么、失败了怎么兜底。技术方案可以抄业务理解抄不来。所以我的建议是先深入一个具体场景把单点做透再考虑通用化。那些一上来就想做通用 Agent 平台的大多都死在了半路上。
返回列表