RAG 与 Agent:让大模型拥有私有知识和工具执行能力
就搞懂俩事儿:一是 RAG 的完整流程和工程实践,二是 Agent 的核心概念、工具调用、规划执行循环。顺便摸下主流框架 LangChain/LlamaIndex,最后学会在产品里落地 RAG 和 Agent。
- 大模型有几个核心局限:知识截止日期、幻觉、没法访问私有数据、没法执行外部操作
- 深入掌握 RAG 的完整流程:文档切分、向量化、存储、检索、重排序、生成,理解每一步的工程细节
- 就说RAG的质量优化手段:切分策略、混合检索、重排序、查询改写、上下文压缩、评估
- 理解 Agent 的核心概念:规划(Planning)、记忆(Memory)、工具使用(Tool Use)、反思(Reflection)
- 就练 Function Calling 的原理和实现。搞懂 Agent 那套「思考-行动-观察」的循环逻辑。
- LangChain 和 LlamaIndex 这俩框架,得先搞懂各自的定位和能用在啥场景,这样才知道啥时候用框架,啥时候自己写代码。
- 就干一件事:在产品里落地RAG和Agent,别踩常见的工程坑。
大模型的核心局限:为什么需要 RAG 和 Agent
大模型有四个核心局限:知识截止:训练数据有截止日期(每个模型都有自己的知识截止点),不知道之后发生的事;幻觉:会一本正经地胡说八道,编造不存在的事实、引用不存在的文献,因为它是在预测下一个词,不是查询事实;无法访问私有数据:不知道你的公司文档、产品手册、用户数据、课程内容;无法执行外部操作:只能生成文本,不能查数据库、不能调 API、不能发邮件、不能操作软件。RAG 解决前三个局限,让模型能查到你的私有知识,基于事实回答减少幻觉;Agent 解决第四个局限,让模型能调用工具执行操作。
RAG 完整流程:从文档到答案
前面已经明确 RAG(检索增强生成)的作用:回答前先检索私有知识、用检索到的事实约束生成、抑制幻觉。本节从全栈工程落地角度,把它拆成两大阶段:索引阶段(离线,把文档处理好存起来)和查询阶段(在线,用户提问时检索+生成)。
RAG 完整流程七步详解
索引阶段(离线,一次性或增量处理)
第1步:文档加载(Load)
把各种格式的文档加载成纯文本:PDF、Word、Markdown、HTML、网页、邮件、数据库记录。工具:PyPDF2/pdfplumber(PDF)、python-docx(Word)、BeautifulSoup(HTML)、Unstructured(通用文档解析)、LangChain Document Loaders。注意:PDF解析是最容易出问题的环节——扫描版PDF需要OCR,复杂排版(多栏、表格、图片)解析效果差,需要专门处理。
第2步:文档切分(Chunk / Split)
把长文档切成小块(chunk),因为:embedding 模型有输入长度限制(通常 512/8192 token);检索时返回整个文档太长,模型上下文装不下,也会稀释关键信息;小块检索更精准。 切分策略:固定长度切分(按字符数/token数,如 500 token 一块,重叠 50 token)——最简单,适合通用场景;按语义切分(按段落/标题/章节切分)——适合结构清晰的文档(Markdown、有标题的文档);递归切分(先按大结构切,太大再按小结构切)——LangChain RecursiveCharacterTextSplitter,最常用;按内容类型切分(代码按函数切、表格按行切)——适合混合内容。 关键参数:chunk_size(块大小,通常 200-1000 token)、chunk_overlap(块之间重叠,通常 10-20%,保证上下文不被切断)。
# 文档切分示例(Python + LangChain)
# from langchain.text_splitter import RecursiveCharacterTextSplitter
#
# text_splitter = RecursiveCharacterTextSplitter(
# chunk_size=500, # 每块约500字符
# chunk_overlap=50, # 重叠50字符,保证上下文连续
# separators=["\n\n", "\n", "。", "!", "?", ";", ":", " ", ""], # 递归分隔符
# length_function=len,
# )
#
# chunks = text_splitter.split_text(long_document)
# print(f"切成了 {len(chunks)} 块")
# for i, chunk in enumerate(chunks[:3]):
# print(f"--- 块 {i} ---")
# print(chunk[:100] + "...")第3步:向量化(Embed)
把每个文本块转成embedding,就是个高维数值数组,比如1536维、1024维、768维,语义像的文本向量离得近。embedding模型有这些:OpenAI text-embedding-3-small/large、豆包embedding、Cohere embed、BGE(开源,中文好)、GTE(开源)。注意:查询时必须用同一个embedding模型向量化,不能索引用A模型,查询用B模型;embedding模型有输入长度限制,超长块要截断;批量向量化比单次快,API支持批量输入;向量化是索引阶段的一次性成本,查询时只向量化用户问题,成本低。
第4步:存储(Store)
把向量和原文存在向量数据库里,支持相似性搜索。选型:MVP用pgvector(PostgreSQL插件,零运维),大规模用Milvus/Qdrant,全托管用Pinecone,原型用Chroma。每个向量条目通常包含:id、向量、原文(chunk text)、元数据(来源文档、页码、章节、时间、作者等,用于过滤和引用)。
查询阶段(在线,用户每次提问)
第5步:检索(Retrieve)
用户提问时,把问题也向量化,在向量库里找最相似的 Top K 个 chunk(通常 K=3-10)。相似度计算用余弦相似度(Cosine Similarity,最常用)、点积(Dot Product)、欧氏距离(L2 Distance)。检索有三种方式:向量检索(语义检索,能找到意思相近但用词不同的内容)——RAG 的核心;关键词检索(BM25,能精确匹配专有名词、代码、数字)——向量检索的补充;混合检索(向量+关键词,用 RRF 等算法融合结果)——生产环境推荐,因为向量检索可能漏掉精确匹配的内容(如错误码、产品型号)。
第6步:重排序(Rerank)
检索到的Top K个chunk是按向量相似度排的,但向量相似度不等于对回答问题最有用的。重排序(Rerank)用交叉编码器(Cross-Encoder)模型,对「问题+每个chunk」联合打分,重新排序后取Top N,通常N=3-5给大模型。Rerank模型有Cohere Rerank、BGE-Reranker(开源)、Jina Reranker。Rerank能提升RAG答案的相关性,是生产环境的标配。注意:Rerank比向量检索慢,因为要对每个chunk单独打分,所以先向量检索取Top 20-50,再Rerank取Top 3-5,平衡速度和质量。
第7步:生成(Generate)
把重排序后的Top N个chunk作为上下文,和用户问题一起发给大模型,让它基于上下文回答。Prompt模板:「你是一个XX助手。请根据以下参考资料回答用户的问题。如果参考资料中没有答案,请说「根据现有资料无法回答」,不要编造。回答时请引用来源编号。 参考资料:[1] {chunk1}[2] {chunk2}[3] {chunk3} 用户问题:{question} 回答:」。关键:让模型基于上下文答,别编造;要引用来源;上下文不够就说「不知道」,别硬答;可以要求特定输出格式(Markdown、JSON、分点)。
切分是 RAG 质量的根基:切不好,检索就不准,后面再怎么优化都没用。优先按语义/结构切分(标题、段落),不要简单按字符数切;chunk 大小要适中——太小上下文不足,太大检索不精准,通常 300-800 token 比较合适;重叠 10-20%。混合检索+Rerank 是生产标配:纯向量检索会漏掉精确匹配(专有名词、错误码、数字),加 BM25 关键词检索融合;Rerank 能把相关性提升 20-30%,就这么简单。查询改写(Query Rewriting):用户的问题可能模糊、有指代、太简短,用大模型把问题改写成更适合检索的形式(如「它怎么用?」→「XX产品的使用方法和操作步骤」),或生成多个查询角度(多查询检索)。上下文压缩:检索到的 chunk 可能有很多无关内容,用大模型把每个 chunk 压缩成「只保留与问题相关的句子」,减少 token 消耗、提升回答聚焦度。评估是关键:RAG 质量不能靠感觉,要建一个测试集(问题+标准答案+应检索到的文档),量化评估检索准确率(Recall@K)和回答质量(用大模型做评委或人工评估),持续优化。本课程的朋友好学 App 目前没有做 RAG(AI 助手是通用对话,不检索课程内容),如果后续要做「AI 助教基于课程内容回答问题」,就需要 RAG:把所有课程文档向量化,用户提问时检索相关课程内容,让 AI 基于课程回答。
Agent:让大模型能调用工具执行操作
RAG让大模型能查到知识,Agent让大模型能执行操作。Agent的核心是:大模型不光能生成文本,还能决定调用什么工具——查数据库、调API、发邮件、运行代码、搜索网页——然后根据工具返回的结果,继续想下一步做什么,直到完成任务。这是个「思考→行动→观察→再思考」的循环。
Agent 的核心循环(ReAct 模式:Reasoning + Acting)思考(Thought):大模型分析用户需求,决定下一步做什么行动(Action):大模型选择一个工具,生成调用参数观察(Observation):工具执行,返回结果给大模型重复 1-3,直到大模型认为任务完成,给出最终答案 示例:用户问「帮我查一下北京明天的天气,然后用邮件发给我」Thought: 用户需要北京明天的天气,然后发邮件。我先查天气。Action: search_weather(city="北京", date="明天")Observation: 北京明天晴,25-32度,微风Thought: 天气查到了,现在发邮件给用户。Action: send_email(to="user@example.com", subject="北京明天天气", body="北京明天晴,25-32度,微风")Observation: 邮件发送成功Thought: 任务完成,告诉用户。Final Answer: 已查询北京明天天气(晴,25-32度,微风),并发送到你的邮箱。
Function Calling:Agent 的技术基础
Function Calling(函数调用/工具调用)是大模型的一个能力:你在 API 请求里定义一组工具(函数)的名称、描述、参数格式(JSON Schema),大模型会「决定什么时候调用哪个工具、传什么参数」——它不直接执行工具,而是返回一个「调用请求」(tool_call,包含工具名和参数),你的代码执行这个工具,把结果返回给大模型,大模型继续思考。主流模型都支持 Function Calling:GPT-4o、Claude 3.5、豆包、DeepSeek、Qwen 等。
// Function Calling 实现(Python 伪代码)
// tools = [
// {
// "type": "function",
// "function": {
// "name": "search_weather",
// "description": "查询指定城市指定日期的天气",
// "parameters": {
// "type": "object",
// "properties": {
// "city": {"type": "string", "description": "城市名称,如北京、上海"},
// "date": {"type": "string", "description": "日期,如今天、明天、2026-09-10"}
// },
// "required": ["city", "date"]
// }
// }
// },
// {
// "type": "function",
// "function": {
// "name": "send_email",
// "description": "发送邮件",
// "parameters": {
// "type": "object",
// "properties": {
// "to": {"type": "string", "description": "收件人邮箱"},
// "subject": {"type": "string", "description": "邮件主题"},
// "body": {"type": "string", "description": "邮件正文"}
// },
// "required": ["to", "subject", "body"]
// }
// }
// }
// ]
//
// # 第一次调用:把 tools 传给大模型
// response = chat(messages=[{"role":"user","content":"查北京明天天气并发邮件给我"}], tools=tools)
//
// # 如果大模型返回 tool_call,执行工具
// if response.tool_calls:
// for call in response.tool_calls:
// if call.name == "search_weather":
// result = actual_weather_api(call.args.city, call.args.date)
// elif call.name == "send_email":
// result = actual_email_api(call.args.to, call.args.subject, call.args.body)
// # 把工具结果追加到 messages
// messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
// # 第二次调用:把工具结果给大模型,让它继续思考或给出最终答案
// final_response = chat(messages=messages, tools=tools)
// print(final_response.content)Agent 的四大能力
适用场景:多步骤复杂任务(需要查多个数据源、执行多个操作才能完成);需要实时信息/私有数据的任务(搜索、查数据库、调API);需要执行操作的任务(发邮件、建文档、操作软件);数据分析和自动化(读数据→分析→生成报告→发送)。典型产品:ChatGPT Plugins/ GPTs、Claude + Computer Use、AutoGPT、各种 AI 助手(调度、客服、数据分析)。局限:不可靠:Agent 可能选错工具、传错参数、陷入循环、任务做一半就停,需要人工监督和错误处理;慢:每一步都要调用大模型,多步骤任务可能要几十秒甚至几分钟;贵:每一步都消耗 token,复杂任务成本高;安全风险:Agent 能执行操作(删数据、发邮件、转账),必须有权限控制和人工确认,不能让 Agent 完全自主执行高风险操作。生产环境的 Agent 通常是「半自动」:Agent 规划和执行低风险操作,高风险操作需要人工确认。
LangChain vs LlamaIndex:框架还是自己写
做 RAG 和 Agent 有两个主流框架:LangChain 和 LlamaIndex。用不用框架?用哪个?
简单 RAG(文档问答):自己写或用 LlamaIndex,别一上来就 LangChain,太重;复杂 Agent(多工具、多步骤、需要记忆和规划):用 LangChain,生态全,或 LangGraph,LangChain 的 Agent 编排引擎,更可控;生产环境:考虑自己写核心逻辑,框架只用来做数据加载和切分等辅助功能——因为框架版本迭代快,生产环境升级可能 breaking,自己写的代码更可控、更易调试;别为了用框架而用框架:很多 RAG 需求(加载→切分→embedding→存→检索→生成)自己写也就 100 行代码,用框架反而要学一堆概念(Document、Retriever、Chain、Callback);本课程的朋友好学 App 如果做 RAG,建议自己写核心逻辑,因为需求简单:课程内容检索+AI回答,用 OpenAI/豆包 embedding + pgvector,自己实现检索和生成,不依赖 LangChain,避免框架的复杂度和版本问题。
本节小结
大模型有四个局限:知识截止、幻觉、没法访问私有数据、没法执行操作。RAG解决前三个,Agent解决第四个。RAG完整流程七步:加载:各种格式文档解析;切分:按语义/递归切,chunk 300-800 token,重叠10-20%;向量化:embedding模型,索引和查询用同一个模型;存储:向量数据库,MVP用pgvector;检索:向量语义检索+BM25关键词混合检索,Top K;重排序:Rerank交叉编码器,Top 20-50→Top 3-5,生产标配;生成:把检索结果作为上下文,要求基于事实回答、引用来源、不知道就说不知道。RAG质量优化:切分是根基、混合检索+Rerank、查询改写、上下文压缩、量化评估。Agent核心是「思考→行动→观察」循环,技术基础是Function Calling,大模型决定调用什么工具,代码执行工具返回结果,四大能力是规划、记忆、工具使用、反思。Agent适合多步骤复杂任务,但不可靠、慢、贵、有安全风险,生产环境通常半自动,高风险操作需人工确认。框架:简单RAG自己写或LlamaIndex,复杂Agent用LangChain/LangGraph,生产环境考虑自己写核心逻辑。下一节讲Prompt工程与AI产品化——RAG和Agent都做好了,怎么把AI能力做成稳定、高质量、可商业化的产品。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
独立开发者选栈的第一标准是“能不能一个人最快稳定交付”,而不是哪个框架最新。Next.js 这类全栈框架把路由、构建、静态导出、服务端接口收敛到一套工具链里,配合静态导出 + WebView 原生壳,能让一个人同时覆盖 Web 与移动端;选型时先验证最不确定的环节(离线运行、原生能力、AI 流式),跑通原型再全面投入,避免做到一半发现关键能力不成立。