1. 项目概述为什么现在必须掌握 RAG 与 Agent 的融合最近和几个做 AI 应用落地的朋友聊天大家普遍有个共识单纯调 API 调用大模型的时代已经过去了。客户不再满足于一个会聊天的“鹦鹉”他们需要一个能真正理解业务、能查询内部知识、能执行复杂任务的“智能员工”。这背后正是RAG检索增强生成和Agent智能体这两项技术从幕后走向台前。这个项目就是一次从零开始的实战目标不是复现某个教程而是搭建一个能跑起来、能解决实际问题的检索增强生成系统并为其注入 Agent 的“行动力”。简单来说RAG 解决了大模型的“幻觉”和知识滞后问题。它让模型在回答前先去你的专属知识库比如公司文档、产品手册、技术资料里检索相关信息然后基于这些“证据”来生成答案确保回答的准确性和时效性。而 Agent 则赋予了系统“思考”和“执行”的能力。它不再是被动的一问一答而是可以理解复杂意图、拆解任务、调用工具比如检索、计算、写代码、并持续完成多轮目标。所以这个实战项目要解决的核心问题是如何将一个静态的 RAG 知识库问答系统升级为一个能主动规划、能利用工具、具备记忆能力的智能体这不仅仅是技术堆砌更是架构设计和工程思维的考验。无论你是想为自己的产品增加智能客服、知识助手功能还是想深入理解当前 AI 应用开发的核心范式这次从零搭建的过程都会给你带来一手经验。2. 核心架构设计从 RAG 管道到 Agent 大脑的演进在动手写代码之前我们必须把架构想清楚。一个健壮的 RAG-Agent 系统不是简单的 11它需要清晰的层次和职责划分。2.1 经典 RAG 管道的再审视一个标准的 RAG 系统通常包含以下几个核心环节我习惯称之为“数据处理与响应流水线”文档加载与解析这是源头。你的知识可能来自 PDF、Word、网页、数据库甚至 API。关键是要选对解析器比如用PyPDF2或pdfplumber处理复杂排版的 PDF用BeautifulSoup处理网页确保文本和格式被正确提取。文本分割分块这是影响检索效果最关键的步骤之一。你不能把整本书扔给模型也不能切得太碎丢失上下文。常见的策略有固定大小重叠分块比如每块 500 个字符重叠 100 字符。简单但对语义完整性不友好。基于分隔符分块按段落、标题等自然分隔符切分。更符合阅读习惯。语义分块使用嵌入模型计算句子相似度在语义边界处切割。效果更好但计算成本高。我的经验是对于技术文档优先按章节标题如##分割对于普通文章固定大小重叠分块是稳妥的起点。向量化与存储将文本块转换为向量嵌入并存入向量数据库。这里有两个关键选择嵌入模型选择适合你语种和领域的模型。中文场景下BGE、text2vec系列是不错的选择。OpenAI 的text-embedding-3系列效果稳定但需调用 API。向量数据库Chroma轻量易用适合原型和中小规模数据Milvus或Qdrant适合生产环境支持分布式和高级检索功能PGVector如果你已经在用 PostgreSQL集成起来会很方便。本项目为了展示完整性和性能我们选择Qdrant它在性能和易用性上取得了很好的平衡。检索器Retriever负责根据用户问题从向量库中找出最相关的文本块。最简单的就是相似性搜索余弦相似度。但工业级系统往往会用上多路召回比如同时用关键词搜索BM25和向量搜索再将结果融合以提高召回率。重排序Reranker检索器返回的 Top K 个结果比如 20 个在相关性上可能仍有噪音。用一个更精细但更慢的重排序模型如BGE-Reranker对这 20 个结果重新打分排序只保留 Top N如 5 个最相关的送入大模型。这能显著提升最终答案的质量。提示工程与生成将检索到的上下文Context和用户问题Question组装成提示Prompt喂给大语言模型LLM让它生成最终答案。提示模板的设计至关重要要明确指令模型“基于以下上下文回答”并处理“上下文不包含答案”的情况。2.2 引入 Agent 框架为系统装上“大脑”当 RAG 管道就绪后它还是一个“反射弧”系统输入问题输出答案。Agent 的引入让它变成了一个具有“目标感”的系统。Agent 核心循环经典的 ReActReasoning Acting框架是基础。Agent 接收目标然后进入“思考 - 行动 - 观察”的循环。思考分析当前状态决定下一步该用什么工具或者直接给出答案。行动调用选定的工具Tool比如我们的 RAG 检索工具、计算器、代码执行器、搜索引擎 API 等。观察获取工具执行的结果作为下一轮思考的输入。工具Tools抽象这是 Agent 与外界交互的手脚。我们将 RAG 检索功能封装成一个标准的工具比如search_knowledge_base(query: str) - str。Agent 在需要查询内部知识时就会调用这个工具。同样你可以封装计算、网络搜索等工具。记忆Memory管理Agent 需要有记忆才能进行多轮对话和复杂任务规划。记忆通常分为短期记忆/对话历史保存当前会话的上下文。长期记忆可以是一个向量数据库存储之前对话或学习的摘要供未来检索。这其实就是将 RAG 技术用于 Agent 自身的经验存储。规划Planning与执行对于复杂任务Agent 需要先拆解Plan。比如用户问“对比我们产品 A 和竞品 B 在安全特性上的差异”Agent 可能会规划成1) 检索产品 A 的安全文档2) 搜索竞品 B 的公开安全信息3) 综合两者信息生成对比表格。架构融合点在这个项目中RAG 系统将主要扮演两个角色一是作为 Agent 的一个核心工具供其查询结构化知识二是作为 Agent长期记忆的存储后端可选。我们将使用LangChain或LlamaIndex这类框架来高效地搭建管道和定义 Agent因为它们提供了良好的抽象和丰富的集成。3. 实战搭建一步步构建你的智能知识引擎理论说再多不如一行代码。我们开始从零搭建。环境准备Python 3.9一个干净的虚拟环境是必须的。3.1 第一步搭建基础 RAG 管道我们先抛开 Agent确保最核心的“检索-增强-生成”链路是通畅的。# 安装核心依赖 pip install langchain langchain-community langchain-qdrant langchain-openai sentence-transformers pypdf# 示例代码结构关键步骤注释 import os from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_qdrant import QdrantVectorStore from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.chains import RetrievalQA from qdrant_client import QdrantClient # 1. 加载文档 loader PyPDFLoader(path/to/your/product_manual.pdf) documents loader.load() # 2. 分割文本 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 块大小 chunk_overlap50, # 重叠大小避免语义割裂 separators[\n\n, \n, 。, , , ] # 中文友好的分隔符 ) chunks text_splitter.split_documents(documents) print(f将文档切分为 {len(chunks)} 个块) # 3. 向量化与存储 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 或使用本地模型 HuggingFaceEmbeddings(model_nameBAAI/bge-small-zh-v1.5) # 初始化 Qdrant 客户端本地用内存模式生产用远程服务器 client QdrantClient(location:memory:) # 或 hostlocalhost, port6333 vector_store QdrantVectorStore.from_documents( documentschunks, embeddingembeddings, clientclient, collection_nameproduct_knowledge, ) # 4. 创建检索器 retriever vector_store.as_retriever( search_typesimilarity, # 相似度搜索 search_kwargs{k: 5} # 检索前5个相关块 ) # 5. 创建提示模板 from langchain.prompts import PromptTemplate prompt_template 基于以下上下文信息回答用户的问题。如果你不知道答案就说你不知道不要编造答案。 上下文 {context} 问题{question} 请用中文给出有帮助的答案 PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) # 6. 构建 QA 链 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # temperature0 使输出更确定 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有上下文塞入提示 retrieverretriever, chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue # 返回源文档便于调试 ) # 测试 result qa_chain.invoke({query: 产品支持哪些支付方式}) print(答案, result[result]) print(来源, result[source_documents])注意嵌入模型的选择是性能和成本的权衡。如果数据敏感或想离线务必使用本地模型如BGE。text-embedding-3虽然效果好但会产生 API 调用费用和网络延迟。chunk_size需要根据你的模型上下文长度和文档特点调整不是越大越好。3.2 第二步升级 RAG —— 实现多路召回与重排序基础版直接用向量相似度检索在有些场景下可能不够准。我们来增强它。# 安装额外依赖用于关键词召回和重排序 # pip install rank_bm25 sentence-transformers from langchain.retrievers import BM25Retriever, EnsembleRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain.retrievers import ContextualCompressionRetriever from langchain_community.cross_encoders import HuggingFaceCrossEncoder # 1. 创建关键词检索器 (BM25) # 需要先将文档转换为纯文本列表 texts [chunk.page_content for chunk in chunks] bm25_retriever BM25Retriever.from_texts(texts) bm25_retriever.k 5 # 也取前5个 # 2. 创建混合检索器 vector_retriever vector_store.as_retriever(search_kwargs{k: 5}) ensemble_retriever EnsembleRetriever( retrievers[bm25_retriever, vector_retriever], weights[0.4, 0.6] # 可以调整权重给向量检索更高权重 ) # 3. 创建重排序模型 # 使用一个轻量级的交叉编码器模型它比嵌入模型更擅长判断相关性但更慢 cross_encoder HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-base) compressor CrossEncoderReranker(modelcross_encoder, top_n3) # 从混合结果中重排只留最好的3个 # 4. 创建压缩检索器即带重排序的检索器 compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverensemble_retriever ) # 5. 将增强后的检索器接入 QA 链 enhanced_qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrievercompression_retriever, # 使用增强检索器 chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue ) # 测试复杂问题 result enhanced_qa_chain.invoke({query: 请总结一下第三章提到的安全协议的具体实施步骤}) print(增强检索后的答案, result[result])实操心得多路召回和重排序会显著增加响应时间但对于复杂、关键的查询准确率的提升是值得的。在线上系统可以考虑对简单查询走快速通道仅向量检索对复杂查询走增强通道。权重的设置如weights[0.4, 0.6]需要在自己的数据集上做 AB 测试来确定。3.3 第三步定义 Agent 的工具与技能现在我们把上面构建的增强版 RAG 系统封装成 Agent 可以调用的工具。from langchain.agents import Tool, initialize_agent, AgentType from langchain.memory import ConversationBufferMemory # 1. 将 RAG QA 链封装成工具函数 def search_knowledge_base(query: str) - str: 用于查询内部知识库的工具。输入是一个问题输出是相关的答案。 try: result enhanced_qa_chain.invoke({query: query}) answer result[result] # 可以在这里添加一些后处理比如截断长度 return answer except Exception as e: return f查询知识库时出错{str(e)} # 2. 创建 Tool 对象 tools [ Tool( nameProduct_Knowledge_Base, funcsearch_knowledge_base, description当需要查询关于产品功能、规格、使用指南、故障排除等内部知识时使用此工具。输入应该是一个明确的问题。 ), # 你可以继续添加其他工具例如 # Tool(nameCalculator, funccalculator, description用于执行数学计算。), # Tool(nameWeb_Search, funcduckduckgo_search, description用于搜索最新的公开信息。), ] # 3. 创建对话记忆 memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) # 4. 初始化 Agent agent initialize_agent( toolstools, llmllm, # 使用和 RAG 相同或更强的 LLM如 gpt-4 agentAgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION, # 适合多轮对话的 Agent 类型 verboseTrue, # 开启详细日志可以看到 Agent 的“思考过程” memorymemory, handle_parsing_errorsTrue # 优雅处理解析错误 )现在这个agent就是一个具备了内部知识查询能力的智能体了。你可以像对话一样向它提问它会自己判断是否需要调用Product_Knowledge_Base工具。3.4 第四步运行与测试你的 RAG-Agent让我们进行端到端的测试看看它如何工作。# 测试 1直接的知识查询Agent 应调用工具 print( 测试 1知识查询 ) response agent.invoke({input: 我们产品的数据备份策略是什么}) print(Agent 回复, response[output]) # 在 verboseTrue 模式下你会看到类似以下的思考过程 # Entering new AgentExecutor chain... # 思考用户问的是产品具体功能我需要使用 Product_Knowledge_Base 工具来查找。 # 行动调用 Product_Knowledge_Base参数为“数据备份策略是什么” # 观察工具返回了从知识库检索到的具体备份策略文本... # 思考我得到了相关信息现在可以组织语言回答用户。 # 最终回复根据知识库我们的产品提供每日全量备份和每小时增量备份... # 测试 2混合任务需要知识推理 print(\n 测试 2混合任务 ) response agent.invoke({input: 基于我们的安全协议如果用户密码输错三次接下来应该建议他怎么做}) print(Agent 回复, response[output]) # Agent 会先调用知识库工具获取“安全协议中关于密码错误处理”的条款然后结合常识建议重置密码或联系管理员生成回答。 # 测试 3无需工具的一般对话 print(\n 测试 3一般对话 ) response agent.invoke({input: 你好今天天气怎么样}) print(Agent 回复, response[output]) # 因为工具描述不匹配Agent 会判断这是一个普通对话直接使用 LLM 的能力回答不会调用知识库。通过以上测试你可以清晰地看到 Agent 的决策过程。它像一个项目经理根据任务类型需求决定是派专家工具去查资料还是自己直接处理。4. 工程化与优化让系统健壮、可维护一个能跑通的 Demo 和一个可上线的系统之间隔着无数个工程细节。以下是几个必须考虑的优化方向。4.1 知识库的持续更新与版本管理文档不是一成不变的。你需要设计一个流程来处理知识库的更新。增量更新最简单的办法是定期如每天全量重建向量库。但对于大规模知识库成本太高。理想方案是支持增量更新。Qdrant支持通过point_id更新或删除特定向量。你需要建立一套映射关系记录文档块Chunk的源文件 ID 和版本。版本控制可以考虑将文档内容和其对应的向量嵌入存储在同一事务中并带有版本号。当文档更新时先标记旧向量为失效再插入新向量。查询时只检索最新版本。更新策略可以设计一个监听文件夹或 API 的守护进程当有新文档放入或旧文档修改时自动触发解析、分块、向量化并更新数据库。4.2 检索质量监控与评估你怎么知道你的 RAG 系统工作得好不好需要建立监控评估体系。人工评估样本集构建一个包含“问题-标准答案-相关文档”的测试集定期运行评估答案的准确性和相关性。自动评估指标检索阶段计算Hit Rate标准答案文档是否在检索结果中和MRR标准答案文档的排名倒数均值。生成阶段使用ROUGE、BLEU或基于 LLM 的评估器如用 GPT-4 判断生成答案与标准答案的一致性来评估生成质量。日志与分析记录每一次查询的检索结果source_documents、生成的答案、以及用户的反馈如果有。这些数据是迭代优化分块策略、检索模型和提示模板的黄金资料。4.3 Agent 的稳定性与安全性增强让 Agent 自主运行必须考虑约束和安全。工具调用限制为工具调用设置超时和重试机制避免因单个工具挂起导致整个 Agent 卡死。输入输出过滤对用户输入和工具输出进行必要的清洗和过滤防止提示词注入攻击或暴露内部敏感信息。验证与确认对于高风险操作如发送邮件、执行数据库写操作可以让 Agent 生成计划后先向用户确认再执行。迭代次数限制在AgentExecutor中设置max_iterations参数防止 Agent 陷入无限循环的“思考-行动”中。4.4 性能优化与成本控制随着用户量增长性能和成本成为关键。嵌入缓存对相同的文本块进行重复向量化是浪费。可以建立本地嵌入缓存如用Redis或SQLite存储文本 - 向量的映射在向量化前先查缓存。检索优化索引优化向量数据库使用 HNSW 等近似最近邻算法索引在精度和速度间取得平衡。分层检索先使用简单的关键词匹配如 Elasticsearch快速缩小范围再在候选集上做精细的向量检索。LLM 调用优化提示压缩在将检索到的上下文喂给 LLM 前尝试用更小的模型进行摘要压缩减少 token 消耗。流式响应对于长答案使用流式输出提升用户体验。模型路由简单问题用便宜快速的模型如gpt-3.5-turbo复杂问题再用强大模型如gpt-4。5. 常见问题与排查实录在开发和调试过程中我踩过不少坑。这里把一些典型问题和解决方法记录下来希望能帮你节省时间。5.1 检索效果不佳总是找不到相关内容这是 RAG 系统最常见的问题。可以按以下步骤排查问题现象可能原因排查方法与解决方案检索结果完全不相关1. 嵌入模型与领域不匹配2. 文本分块不合理太大或太碎3. 向量数据库索引未正确构建1.检查嵌入计算几个典型问题和其标准答案文档块的余弦相似度看分数是否正常通常应 0.7。如果分数低考虑更换嵌入模型。2.检查分块打印出被检索到的文本块内容看是否是一个完整的语义单元。调整chunk_size和chunk_overlap或改用语义分块。3.检查数据确认文档是否被正确解析和加载没有乱码或大量无关字符。检索到部分相关但关键信息缺失1. 分块割裂了关键信息2. 检索数量k值太小3. 问题表述与文档表述差异大1.优化分块尝试按章节/标题分块或增大chunk_overlap。2.增加召回增大search_kwargs{“k”: 10}然后通过重排序来筛选 Top 3而不是直接只取 Top 3。3.查询扩展对用户问题使用同义词扩展或让 LLM 重写问题再进行检索。英文资料库中文问题检索不到嵌入模型是单语如纯英文使用多语言嵌入模型如text-embedding-3、BGE-m3或paraphrase-multilingual-MiniLM-L12-v2。我的心得分块策略是 RAG 的“地基”。花 30% 的时间优化分块能解决 50% 的检索问题。对于技术文档我强烈推荐先尝试按 Markdown 标题#,##分块效果往往比固定尺寸分块好得多。5.2 Agent 不调用工具或错误调用工具这通常与工具的描述description和 Agent 的类型有关。Agent 完全不调用工具检查工具描述描述必须清晰、具体说明在什么情况下使用此工具。LLM 主要靠描述做判断。例如“用于计算数学表达式”就比“一个计算工具”好。检查 Agent 类型AgentType.ZERO_SHOT_REACT_DESCRIPTION适合简单任务AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION更适合多轮对话。复杂的规划任务可能需要OPENAI_FUNCTIONS或PLANNER类型。开启 verbose 模式这是最重要的调试手段直接看 Agent 的思考链Chain of Thought它为什么决定不调用工具Agent 调用了错误的工具工具描述区分度不够确保每个工具的描述有明确的边界。如果两个工具都描述为“处理用户问题”LLM 就会困惑。给 LLM 更强的指令在初始化 Agent 的agent_kwargs中可以提供更详细的系统提示system_message明确指导它如何选择工具。5.3 响应速度慢用户体验差性能瓶颈可能出现在多个环节。嵌入模型速度慢如果使用本地大模型考虑换成更小的模型如all-MiniLM-L6-v2或使用 GPU 加速。对于生产环境异步处理和缓存是关键。向量检索慢检查向量数据库的索引类型。对于千万级以下数据HNSW 索引通常性能很好。确保查询时使用了合适的搜索参数如ef或search_k。LLM 生成慢这是主要瓶颈。可以考虑使用更快的模型如从gpt-4降级到gpt-3.5-turbo。优化提示词减少不必要的上下文。实现流式输出让用户先看到部分结果。网络延迟如果使用云端 API网络波动会影响速度。需要设置合理的超时和重试机制并考虑使用后端轮询或 WebSocket 向前端推送结果。5.4 如何处理“知识库中没有答案”的情况这是评估 RAG 系统可靠性的重要一点。我们必须在提示模板中明确要求模型“不知道就说不知道”。# 一个更健壮的提示模板示例 robust_prompt_template 你是一个专业的客服助手请严格根据提供的上下文信息来回答问题。 如果上下文中的信息不足以完全回答问题请基于已知部分诚实回答并明确说明哪些信息是缺失的。 如果上下文与问题完全无关请直接说“根据现有资料我无法回答这个问题”。 上下文信息如下 {context} 用户问题{question} 请根据以上上下文给出准确、有帮助的回答同时在检索阶段可以设定一个相似度阈值。当最相关文档块的相似度分数低于某个值如 0.7时直接判定为“未找到相关信息”不将其送入 LLM而是返回预设的“知识库暂无此信息”回复这可以节省 LLM 调用成本并避免误导。搭建这个系统的过程就像在组装一个既有“海量记忆”RAG又有“聪明大脑”Agent的数字生命体。最初的版本可能笨拙但通过持续的迭代——优化分块、调整检索策略、丰富工具集、完善提示——你会亲眼看到它的能力边界一步步拓宽。最让我有成就感的时刻往往是看到它成功地将一个模糊的用户需求通过几次工具调用和逻辑推理转化为一个准确、 actionable 的结果。这不仅仅是技术的实现更是对复杂问题解决流程的一种自动化建模。