RAG+Agent智能体实战:从知识库问答到任务执行的最小实现
先聊个真实场景。上周有人在群里问说他想做一个内部知识库问答几十份产品文档丢进去用RAG搭个接口用户问什么就答什么。他以为这事儿已经很简单了结果一测试就翻车用户根本不会用标准问法提问题而是像跟真人聊天一样带着场景和意图来问比如“我订单量突然翻倍系统扛不住该先看哪几篇文档再做什么操作”。这种请求纯RAG答不完整检索出来的片段无法组织成操作链路纯Agent又缺知识来源模型凭记忆胡说八道。就是这么一句对话把“RAG”和“Agent智能体”这两个概念彻底焊在了一起。我写这篇文章就是想把这套组合拳的基础逻辑、最小实现和避坑经验一次性讲透。面向的是刚接触大模型应用开发、准备做知识库问答、或者想把AI能力接进现有系统的开发者。文章里的代码和方案都能直接照着跑原理也用大白话拆开讲争取让没做过AI应用的读者看完也能动手。1. RAG与Agent到底解决什么问题一个管知识一个管行动很多初学者有个误区觉得RAG和Agent是两条独立的技术路线甚至认为Agent更高级、要晚点学。但从实际项目看两者根本不是竞争关系而是互补关系。RAG解决的是“模型没有某个知识、容易胡说八道”的问题Agent解决的是“模型只会聊天、无法替用户办成事”的问题。搞懂这个分工后面所有设计都顺了。1.1 为什么纯RAG会被用户问住RAG全称是Retrieval-Augmented Generation即检索增强生成。它的核心思路非常朴素大模型的知识存在训练参数里有截止日期也不包含企业私有内容。与其逼模型硬答不如在回答问题之前先查资料把查到的内容拼进提示词里再让模型基于这段资料作答。典型的RAG流程分成离线与在线两段。离线阶段要做的是把文档切块、向量化、建索引在线阶段要做的是把用户问题向量化、检索最相关的块、拼上下文、让模型生成答案。这一套流程里最容易被忽略却最要命的问题是检索结果只是一堆片段不是答案。比如用户问“系统扛不住应该怎么办”如果文档里只有“容量规划建议”和“水平扩容操作步骤”两个片段被检索出来模型确实能拼出一段回答但拼不出“先分析瓶颈再决定要不要扩容”这个决策链路。我实际测试过很多次纯RAG在“点状问题”上效果不错比如“某接口的限流默认值是多少”但在“链状问题”上经常答得七零八落甚至因为检索片段排序不对把次要的步骤当成了主要结论。这个局限不是调参能完全解决的因为RAG本身没有“规划”能力它只是被动的资料供给方。1.2 Agent的核心价值不在“聊天”而在“执行”Agent智能体这里的“智能体”强调的不是更会聊天而是能做多步任务的自主执行。它的运行逻辑可以通俗理解成把用户的目标拆成若干子任务按需调用外部工具观察返回结果再决定下一步动作直到任务完成。这个过程有一个经典范式叫ReAct即推理与行动交替执行。模型每一步都输出两部分内容一部分是“思考过程”说明为什么这么做另一部分是“动作”要么回答用户要么调用某个工具。外部工具的执行结果会被当作观察信息喂回给模型供它进行下一轮推理。这个机制的好处是让模型具备了“动手能力”。比如Agent可以调天气接口、查库存系统、写临时文件、执行一段代码甚至自己调用另一个Agent。以前大家都说大模型只是一个“嘴强王者”配上工具调用框架之后它才真正从“能说”变成“能干”。但纯Agent有个致命短板它对世界的认知仍然受限于训练数据。工具能给它实时数据却给不了它“某个未公开产品的内部规则”它能推理出“应该用哪个函数”却推不出“这个函数背后的知识背景”。这时候RAG就该进场了。1.3 两者的结合逻辑大脑配图书管理员把RAG和Agent结合最形象的比喻是给模型配了一个既会思考的大脑又配了一个随叫随到的图书管理员。Agent负责拆解任务、决定下一步做什么RAG负责被Agent调遣从外部知识库中快速找出相关资料并整理给模型。放在系统架构里看RAG完全可以作为一种特殊工具被Agent调用。当Agent把用户问题拆解后发现“这个信息需要查内部资料”它就触发一次检索拿到结果继续推理。这样既保留RAG的知识精准度又注入Agent的任务规划能力既能回答“是什么”又能解决“怎么做”。关键点在于不要让RAG和管理流程互相纠缠。我见过不少新手的Demo把检索逻辑硬编码进Agent的每一步里结果Agent每走一步都要检索一轮既贵又慢。正确做法是让Agent自主决定“是否检索、什么时候检索”系统只负责把“检索工具”准备好。2. 基础Demo的设计与选型动手之前先想清楚三件事要入门这套组合拳不建议一上来就上分布式向量库或者复杂编排框架。我自己带人做项目时习惯先跑通一个最小闭环再加复杂度。动手之前先想清楚三件事要解决什么问题、系统由哪几部分组成、用什么技术选型。2.1 三种方案对比纯RAG、纯Agent、RAGAgent先给出一个方案对比表格建议按自己的需求对号入座方案适合场景核心优势明显短板上手难度纯RAG只做文档问答不问“怎么做”实现快、可解释性强、检索质量可控答不了多步任务无法调用系统低纯Agent带工具模型做调度、调API、操作数据任务规划能力强、自动化程度高知识来源有限容易一本正经胡说中RAGAgent组合内部知识库任务执行决策辅助既能答又能干知识实时可控调试复杂延迟更高成本上升中高从表格可以直观看出纯RAG适合简单的FAQ场景纯Agent适合工具调度场景只有两者结合才能覆盖“需要专业知识支撑的多步决策场景”。这个判断非常关键因为它决定了你的架构设计。2.2 最小架构设计四个模块各司其职我给新手推荐的最小架构包含四个模块知识库模块、检索模块、Agent调度模块、外部工具模块。知识库模块负责把原始文档切块并生成向量检索模块负责接收问题或关键词返回最相关的知识片段Agent调度模块负责解析用户意图、编排行动步骤外部工具模块负责被Agent调用完成API请求、数据查询等操作。整套系统跑起来大概是这个节奏用户输入意图复杂的请求Agent先拆解任务判断“要不要查资料”如果需要就调检索模块把知识片段拼进当前上下文Agent继续推理决定是否调用外部工具执行工具后拿到结果再决定是继续、收尾还是把答案交给用户。这个架构看着不复杂但它的灵活性在于每个模块都可以独立替换。比如今天用轻量向量库明天可以换成分布式向量库今天有一个工具明天可以加五个工具。Agent不会因为模块变化而需要重写只要工具描述和接口不变就行。2.3 技术选型别为了追新而选重技术选型是很多新手最容易踩坑的地方总想一步到位上来就搭一整套成熟的Agent编排框架结果光是概念就学了两周代码还没跑起来。我的建议是最小Demo阶段尽量用自己熟悉的技术栈。文档切分与向量化阶段直接用常见大模型的Embedding接口就好。如果是中文文档优先选中文场景表现稳定的模型如果是中英混合再考虑多语言模型。向量存储用一个支持持久化的本地向量库足够不需要上分布式。Agent调度阶段如果不想自己写循环也可以借助现成的Agent框架但更推荐先手写一轮循环把“模型输出—解析—调工具—拼上下文”这个过程亲手捋一遍后面用任何框架都不会懵。我自己带人的时候经常说一句话先自己写一个能跑的轮子再开汽车。手写过程中的每一个Bug都会变成经验这个经验用框架时根本拿不到。3. 实操过程与核心实现从零跑通一个RAGAgent的Demo这一节直接上干货。我会带大家实现一个最简单的需求假设企业内有一批产品手册用户在对话中问“某模块的限流阈值是多少如果当前QPS超过阈值请告诉我该做什么”系统需要先查知识库回答阈值再根据预设条件给操作建议。全程只需要一个本地Python环境。3.1 环境准备与依赖安装建议使用Python 3.10及以上版本避免老版本语法兼容问题。需要安装的核心依赖包括大模型SDK、Embedding依赖、向量库依赖。这里不绑定具体品牌各家SDK的安装方式大同小异核心依赖形如pip install llm-sdk pip install embedding-sdk pip install vector-db-lite如果条件有限也可以把向量检索简化为本地文本加载先用一个函数模拟检索结果等整体跑通之后再替换成真正的向量检索。这个“先假后真”的思路在AI应用开发里极其好用能让你把变量控制在最小范围快速定位问题到底出在调度逻辑还是检索逻辑。3.2 知识库准备与RAG检索模块实现资料首先要有一批可检索的文档比如几份产品手册。为了方便演示我直接在脚本里内置一小段文档内容。实际项目中这些内容应该是从PDF、Word、网页里抽取的文本。文档切分这一步值得多说一句。很多新手喜欢按固定字符数切分比如每500字切一块结果把完整的技术流程拦腰截断检索时只能找到零碎内容。我自己习惯的切分逻辑是分两层先用结构和语义边界比如标题、章节、空行做粗切再对超长段落做细切并保留一小段重叠区域。重叠区域一般在50到100字作用是避免切分边缘把关键语义切断。# 模拟文档切分后的知识块 knowledge_chunks [ 模块A的限流阈值为每秒2000个请求超过阈值会触发排队机制系统自动拒绝新请求。, 当QPS超过限流阈值时建议优先扩容接入层节点再排查是否存在慢SQL或锁竞争。, 模块B的缓存策略为本地缓存5分钟缓存过期后回源数据库。, ] def search_knowledge(query: str) - list[str]: 极简检索函数按关键词打分并返回最相关片段真实场景请替换为向量检索。 scored [] for chunk in knowledge_chunks: score 0 for token in [限流, 阈值, 扩容, QPS, 缓存]: if token in query and token in chunk: score 1 scored.append((score, chunk)) scored.sort(reverseTrue, keylambda x: x[0]) return [chunk for _, chunk in scored[:2]]这段代码的核心逻辑一眼就能看懂关键词命中越多排序越靠前。真实项目里需要把关键词打分替换成Embedding相似度计算但返回结构是一样的都是“片段列表”。这也是我把两层设计分开的原因——调度层不需要关心检索内部实现只要拿到片段列表就够了。3.3 定义一个“检索工具”供Agent调用在Agent架构里任何能力都统一抽象成“工具”。检索知识库就是一个典型的只读工具。工具的描述信息非常重要因为它决定了Agent能不能在正确时机调用它。描述写得模糊Agent可能该调用时不调用不该调用时瞎调。我遇到的坑几乎都出在工具描述上。比如检索工具的描述我至少会写清楚三件事它能查到什么、什么时候用、什么时候别用。示例结构如下。tools [ { name: search_knowledge_base, description: 在内部产品手册中检索技术知识适合回答限流阈值、缓存策略、扩容建议等事实性问题。 如果问题涉及公司制度或代码细节不要使用本工具。, parameters: {query: 字符串简短的关键词比如QPS超过阈值怎么办}, function: search_knowledge, } ]这个描述里隐含了一层筛选逻辑检索工具只服务事实性知识问题。Agent看到这个描述就不会把“帮我写一段代码”这种需求往检索工具上引。这种设计比在代码里硬拦请求要优雅得多也给模型留了合理决策的空间。3.4 Agent调度循环的实现Agent的核心是循环调度。我手写一个极简Agent循环展示“思考—行动—观察—再思考”的完整过程。真实项目里的模型输出是结构化JSON或者可解析的文本我这里为了让演示更直观直接模拟模型的格式化输出。class SimpleAgent: def __init__(self, tools: list[dict]): self.tools tools def build_prompt(self, query: str, history: list[str]) - str: tool_desc \n.join( f工具名{t[name]}描述{t[description]}参数{t[parameters]} for t in self.tools ) history_text \n.join(history) return f 你是内部技术支持助手。请根据用户问题决定是否调用工具。 可用工具{tool_desc} 对话历史{history_text} 用户问题{query} 请按如下格式输出 思考……(说明你的判断) 动作调用工具名(参数) 或 直接回答 回答…… def parse_response(self, response: str) - dict: # 实际项目中这里需要解析模型输出演示代码按固定格式解析 lines response.splitlines() thought action answer for line in lines: if line.startswith(思考): thought line.split(, 1)[1] elif line.startswith(动作): action line.split(, 1)[1] elif line.startswith(回答): answer line.split(, 1)[1] return {thought: thought, action: action, answer: answer} def run(self, query: str, max_steps: int 3): history [] for step in range(max_steps): prompt self.build_prompt(query, history) # 实际项目中这里调用大模型 response 思考用户问的是限流阈值和扩建议需要检索知识库。\n动作调用search_knowledge_base(queryQPS超过阈值 扩容)\n回答 parsed self.parse_response(response) if parsed[answer]: return parsed[answer] if parsed[action].startswith(调用): # 解析动作参数并执行工具 tool_name search_knowledge_base params {query: QPS超过阈值 扩容} result self.tools[0][function](**params) history.append(f工具返回{result}) # 根据工具返回生成最终回答 return f模块A的限流阈值为2000 QPS。如果当前QPS超过该值建议优先扩容接入层节点同时排查慢SQL或锁竞争。 return 处理超时请简化问题后重试 agent SimpleAgent(tools) print(agent.run(模块A的限流阈值是多少如果超了怎么办))这段代码把Agent的关键机制浓缩成了三步根据描述决定调不调工具、执行工具、把结果转成回答。真实项目中多轮循环体现在“工具返回后模型要继续推理”而上面的示例是工具返回一步就到答案了。你自己扩展的时候只要记住这个循环是可以反复迭代的不是一步到位。3.5 核心参数的设计逻辑运行Agent时有几个参数需要刻意设计不能随手用默认值。第一是温度temperature参数。Agent任务建议设置为0或者接近0因为Agent需要稳定执行工具调用而不是发挥创意。如果把温度调到0.7模型可能会把工具参数写得很随机甚至凭空发明一个不存在的工具名。第二是最大循环轮数。在真实项目中至少要设一个上限否则Agent在一个任务上反复推演几十轮成本和延迟都失控。我的经验是普通问答场景设3到5轮足够复杂任务场景可以放宽到8轮但要配合“每轮工具返回后强制总结进展”的提示词防止Agent原地打转。第三是检索返回的片段数量。在Agent场景里返回片段不是越多越好。每多一个片段都要拼进上下文既增加模型处理负担又容易引入噪声。我常用的是先返回5个让模型自行筛选如果发现模型总被无关片段干扰再降到3个。3.6 从Demo到可用系统还需要补什么上面的Demo是跑通逻辑的最小闭环它已经能回答“阈值操作建议”这种复合问题了。但要变成一个真正能放上线的系统还需要补不少东西。首先是把模拟检索换成真正的向量检索。这一步不复杂核心就两件事用Embedding模型把知识块转成向量存入向量库查询时把用户问题转成新向量做相似度检索。这里有两个小经验一是知识块一定要带元数据比如文档名、章节号、更新时间否则用户问“这段话出自哪版手册”时模型答不上来二是检索时建议做个简单的重排序或者至少过滤掉相似度低于阈值的结果避免把无关片段强行塞给Agent。其次是加一层意图判断。这个Demo假设所有问题都涉及知识库检索但现实场景中用户可能问“你能做什么”“现在几点”或者“帮我写首打油诗”。如果没有意图判断所有问题都会先检索一轮既浪费又影响体验。最简单的做法是在Agent的系统提示词里加一句“如果问题不需要工具直接回答不要强行调用工具”。大模型通常能遵循这个约束。然后是对话记忆管理。用户的真实提问往往是多轮的比如先问“模块A限流多少”再问“那模块B呢”。如果对话历史没有处理好第二问就变成了无头无尾的孤立问题。我建议在Agent入口做一次简单的上下文改写把最近一轮用户问题结合之前的主题补全成完整问题再送进Agent。这一步用模型做也行用规则做也行但千万别不做。4. 常见问题与排查技巧实录我在实际项目中踩过的坑这部分不写理论直接按“现象—原因—解法”整理成速查表都是我实际调试中遇过的问题值得收藏起来当排查手册用。问题现象根本原因排查方法解决建议检索经常返回无关内容文档切分粒度过大嵌入向量语义模糊打印检索结果人工判断片段是否相关用标题和段落边界切块适当减小chunk_sizeAgent反复调用同一个检索工具工具返回内容未改变模型无法获得新信息打印每次工具返回结果观察是否重复工具返回中加入“已检索过”的标记或限制单工具调用次数模型生成了不存在的工具名工具描述与意图不匹配温度参数过高检查工具描述是否过于笼统降低温度在提示词中明确“只能使用给定的工具”用户问题带有指代词Agent理解不了缺少对话历史和指代消解处理观察是否把多轮问句当成孤立问题处理在进入Agent前补齐指代词或拼接最近对话上下文文档更新后检索结果仍是旧内容向量索引未同步更新检查离线入库流程是否触发增量更新建立文档更新监听删除旧向量并写入新向量Agent每步都很慢整体延迟超10秒多轮模型调用串行检索也占用时间拆开统计每步耗时定位瓶颈把不依赖推理的检索提前并行或精简提示词减少模型推理耗时回答看似流畅但关键数字是编的模型生成时没有严格引用检索片段对比“检索片段”和“最终回答”在提示词中要求“只基于检索内容输出不要补充未经证实的信息”4.1 检索质量类问题不要急着换模型先看数据检索质量差是RAG项目里最常被吐槽的问题。我见过有人因为检索不准直接把Embedding模型换了好几个效果却依然不理想。后来我帮他一查发现文档切分极其粗暴一个一万多字的章节被切成了十几个互不关联的文本块很多块里既没有完整结论也没有上下文模型当然召不回有效信息。建议遇到检索问题时先自查三件事文档切分是否符合语义结构、文本块是否带有足够上下文、检索结果排序是否把最相关的块放在前面。如果这三项都正常再考虑换Embedding模型。换模型不是解决检索问题的第一选项而是最后选项。4.2 Agent行为类问题先看工具描述再怪模型Agent调错工具、或者反复调用同一个工具这类问题几乎都能追溯到工具描述不够清晰。模型不是神它只能通过描述来理解工具的使用边界。描述越模糊模型发挥空间越大错误概率也越高。比如同样是“查天气”如果描述只写“天气查询工具”模型可能在任何涉及“天气”的闲聊里都去调用一次。如果描述写成“根据城市名称查询实时天气支持国内主要城市不用于历史天气回顾”模型的调用准确率会显著提升。写工具描述时最好把自己当成一个完全不懂内部逻辑的新员工把“做什么、什么时候用、什么时候不用”写清楚。还有一个调试技巧在Agent循环里打印每一步的“思考”内容。大模型的思考过程会透露它为什么做这个决定这是定位问题最有效的钥匙。哪怕生产环境不开日志开发环境也一定要保留完整轨迹。4.3 性能与成本问题也不是堆算力就能解决RAGAgent相比普通的大模型问答天然多出“检索”和“多轮推理”的成本。如果优化不到位一个简单问题可能触发两三次模型调用和多次检索云账单涨得飞快。我常用的优化手段有三个。第一在做检索之前先把用户问题做一次分类明显不需要知识库的问题直接走普通问答链路。第二控制上下文长度检索到的片段不要无脑全拼先做一次相似度截断只保留前三名。第三设置合理的缓存相同或者高度相似的问题短时间内直接返回历史结果不必重新走全链路。延迟问题也一样很多情况下不是模型推理慢而是工程上串行环节太多了。能并行的步骤尽量并行比如多个无关的检索请求可以同时发出等结果回来再统一交给Agent。这些都是老生常谈的工程手段但在AI应用场景里特别容易被忽略。5. 实操心得与后续扩展建议把RAG和Agent的组合拳在真实项目里跑通之后我对这套架构有了几个比较深的感触。最直接的一点是工具描述和文档切分的质量几乎决定了整个系统体验的上限。模型能力当然重要但如果工具描述含糊、知识块切得稀碎再强的模型也发挥不出来。再有一个经验是调试这类系统要养成分层排查的习惯。问题到底出在检索、提示词、工具调用还是模型理解每层都有可能引入Bug。别一上来就怀疑模型能力或者换框架先把每一层的输入输出打出来看一遍问题基本就水落石出了。我自己还有一个坚持了很久的小习惯每次上线新的Agent工具都会先准备几个边缘测试用例故意让工具在不太合适的场景下被调用验证模型的拒识能力。比如检索工具只该查产品手册我就偏问它“今天天气怎么样”观察它会不会瞎调工具。这类测试能在用户踩坑之前先把大部分隐患排掉。后续如果要继续深入建议按这个顺序扩展先把向量检索和重排序做扎实再给Agent增加更多真实工具接着引入对话记忆和更细的权限管控。每走一步把前一步的稳定性打牢比盲目堆功能重要得多。RAG和Agent都不是新概念但把它们合理组合起来确实能让很多以前“只能想想”的内部应用变成现实。