
大语言模型受限于训练数据无法回答训练数据之外的最新信息。本文介绍了RAG检索增强生成技术通过从指定知识库中检索相关依据再基于这些真实文档来回答问题有效解决大模型的幻觉问题。文章详细讲解了RAG的离线索引和在线查询流程包括文档加载、切分、嵌入、入库、检索等关键步骤并对比了2-Step和Agentic两种RAG实现方式帮助读者快速掌握大模型应用的新技术。生产环境里第二种情况是不能接受的。RAG 要解决的问题就是让模型在回答之前先到指定的知识库里查一圈找到相关依据再基于这些真实文档来回答。环境基线Python 3.13、langchain1.3.2另需langchain-text-splitters、langchain-communityLoader、以及一家 Embedding 包下文用langchain-openai举例可换成别家。聊天模型仍从common.models.get_model拿。混合检索调参、大规模评测留给后续 RAG 专栏本篇打通全流程并写清 2-Step / Agentic、溯源 artifact、以及和长期记忆 Store 的差别。一、概述RAGRetrieval-Augmented Generation检索增强生成名字拆开就三步Retrieval检索从知识库找出和问题最相关的文档片段Augmentation增强把片段 用户问题拼进 promptGeneration生成模型基于增强后的上下文写回答RAG 检索 拼接 生成让模型回答训练数据之外的事靠的是真实文档当依据不是指望模型按照概率来推断。有以下一些场景AI可能解决不了需要RAG来优化痛点表现RAG 怎么顶上知识过时训练截止后的政策/新闻它不知道容易瞎编查库库更新即「新知识」没有私有数据内网制度、客户手册不在训练集里私有文档进自己的索引幻觉不知道也硬答有片段才答没有就拒答微调太重改知识还要重训/继续训改知识库即可不必重训模型无法追责答完说不清依据片段带来源可展示、可审计塞不下全文几十万字不能整库进一次 prompt只取 top-k 相关块两句话对照传统用户问题 → LLM → 答案只靠训练记忆RAG用户问题 → 检索 → 拼进 prompt → LLM → 答案先给证据用检索替代微调让模型以较低成本拿到最新、相关、可核对的外部知识。若你已经有 SQL / 文档中台 / 搜索服务不必为了「用 LangChain」重造库可以把现成查询封成工具Agentic或查完把结果当 context 塞进提示2-Step。下面仍从用文件建向量库讲起这是最常见的入门教学。二、2-Step 与 Agentic官方把常见接法分成三类2-Step、Agentic、Hybrid本篇重点讲2-Step 和 Agentic。架构原理控制力灵活度延迟典型场景2-Step RAG每次回答前固定先检索再生成高低相对可预期FAQ、文档问答Agentic RAGAgent 自己决定何时、用哪个检索工具、查几次低高波动大研究助手、多工具混用Hybrid在中间加改写、检索 sufficiency、答案校验等中中视迭代次数要对齐质量门禁的领域问答2-Step流水线写死一定查一次文档。实现简单调用次数上限清楚适合几乎每个问题都该看文档的客服知识库。Agentic检索只是普通tool。闲聊时可以不查文档复杂问题可以改写 query 再查第二遍。代价是多几轮模型调用也更依赖提示约束「没查到就说不知道」。三、入库流水线经典的RAG流程分为两个离线索引与在线查询。阶段什么时候跑干什么离线索引Indexing建库时做一次文档变更后再跑文档 → 加载 → 切分 → 嵌入 → 写入向量库在线查询Query每次用户提问问题嵌入 → 相似度搜索 → Top-K → 拼进 prompt → 生成检索好不好很大程度在离线阶段就定了文档切分好不好、Embedding 对不对很重要前面的做不好在线阶段再调 prompt 也效果不好。在线阶段要精确控制只取相关片段别试图把整库塞进窗口。离线五步可以记成一条链加载Load文件/网页 → 标准Document切分Split长文 → 小块 chunk向量化Embed文本 → 稠密向量入库Store向量 原文 元数据 → 向量库检索器Retrieve查询时近邻搜索返回相关Document这一步挂在「在线」as_retriever往往离线末尾配好在线阶段输入的问题也要变成向量或交给检索器封装然后检索取出 top-k再增强 prompt、生成回答。为了更好的通用性和替换性我们本项目做了很好的封装可以随时换 Loader、换切分策略、换 Embedding 供应商、换 PGVector / Chroma业务代码尽量只依赖接口或工具。Document是全流程的基本单位from langchain_core.documents import Document doc Document( page_content退货须在签收后 7 日内申请。, # 真正被检索和塞进提示的正文 metadata{source: refund_policy.md, section: 时限}, # 溯源、过滤用 )page_content是语义主体metadata不参与默认向量计算但回答时如果要引用出自哪份文件或者需要按目录过滤时还得靠metadata。四、加载与切分1 加载先变成 Document 列表本地纯文本最简单用TextLoaderfrom langchain_community.document_loaders import TextLoader # encoding 按文件实际编码来Windows 上中文常见 gb18030 / utf-8 loader TextLoader(data/faq.txt, encodingutf-8) docs loader.load() # List[Document]通常一个文件一个 Document未切分前其它常见入口装对应依赖即可PyPDFLoaderPDF、WebBaseLoader网页、目录用DirectoryLoader批量加载。Loader 只负责读进来并尽量带上 source 等元数据不管切多细。2 为什么要切块如果一个文档很大几十万字整个丢进 大模型不仅上下文窗口会爆token消耗巨大检索也无法准确命中段落。切块之后检索单位是一小段话生成时只拼 top-k 段成本和噪声都可控。3 RecursiveCharacterTextSplitterLangChain 里最常用的通用切分器按分隔符列表递归切优先在段落边界断开切不干净再退到行、空格、字符。默认分隔符顺序大致是/n/n→/n→ →。from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, # 单块上限默认按字符计不是 token chunk_overlap80, # 相邻块重叠减轻「一句话被砍两半」 add_start_indexTrue, # metadata 里记下在原文的起始偏移方便调试 ) chunks splitter.split_documents(docs) print(len(docs), -, len(chunks))Markdown / 代码还有更贴结构的 splitter中文没有空格时递归仍主要靠换行和标点附近的回退不要照搬英文「按词」的直觉以抽查看块是否完整为准。4 chunk_size 与 chunk_overlap这两个参数最容易混用一张图对齐chunk_size每一块允许的最大长度该 splitter 默认是字符数。文档 10000 字、chunk_size4000大约会切成多块每块不超过 4000实际还会尽量在分隔符处提前断开所以常小于上限。chunk_overlap相邻两块共享的一段尾巴/开头。例如chunk_size1000、chunk_overlap200时第一块若落到 1–1000第二块会从大约 801 起——801–1000 这段两边都有。为什么要重叠语义经常骑在切分边界上上一块结尾半句条件下一块开头半句结论。没有 overlap只命中一块时模型可能看到残句。overlap 太小不管用太大则冗余高、块数变多、索引和检索都变贵。经验参考需根据自己的项目调整场景chunk_sizechunk_overlap短 FAQ、条目清晰300–60040–80制度/手册段落500–100080–150长叙事、需更多上下文1000–1500150–200没有万能值。同一份文档用两三种 size 各建一小库拿 20 个真实问题看「该中的段在不在 top-k」拿实际结果作比较。Overlap 常见取 size 的 10%–20%。切完务必print几块有没有把表格砍碎、把「不适用情形」和「适用情形」拆到两个永远不同时召回的块里。这种问题改切分比改 prompt 管用。五、Embedding、向量库与元数据1 EmbeddingEmbedding 把一段文本变成固定维向量让「意思近」的文本在空间里也近。检索时问题 → 向量在库里找最近的 chunk 向量取回原文。聊天模型get_model()和 Embedding 模型是不一样的不要把 chat 模型的接口硬当成 embed 用。选 Embedding 时优先和文档语言匹配中文语料用中文效果好的模型。维度、价格、是否可本地部署。一经建库换 Embedding 模型通常要整库重嵌选型最好早定。# 示例OpenAI 兼容接口换成 DashScope / 本地 bge 等均可 from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 向量维度由模型决定同一库全程必须用同一 embedding本地可试HuggingFaceEmbeddings如BAAI/bge-small-zh-v1.5公司内网常见「OpenAI 兼容的 embedding HTTP 接口」用OpenAIEmbeddings(base_url...)指过去即可。2 向量库向量库保存向量、对应的page_content、metadata并提供相似度查询。自己做demo或者实验用内存库生产要换 Postgrespgvector、Chroma、Milvus、云检索等。from langchain_core.vectorstores import InMemoryVectorStore # 从 Document 列表一次性构建内部会对 page_content 调 embeddings vector_store InMemoryVectorStore.from_documents( documentschunks, embeddingembeddings, ) # 等价分步先 empty store再 add_documents # vector_store InMemoryVectorStore(embeddings) # vector_store.add_documents(chunks)3 元数据建库前尽量写好 metadata例如source、title、page、product、lang。之后可以回答里写「依据退货政策 §3」。检索时按产品线过滤若向量库支持 metadata filter。评测时统计「错在哪份文档」。切分后 metadata 一般会从父 Document 继承add_start_indexTrue时还能看到块在原文的偏移。4 Retriever向量库可以.similarity_search(query, k3)我们也可以自己封装一个 Retriever类方便以后换成其它实现而不改调用方# 默认相似度 Top-K retriever vector_store.as_retriever( search_kwargs{k: 3}, # 过大噪声多过小易漏FAQ 常 3–5 ) # 可选MMR最大边际相关性——在「相关」和「彼此不重复」之间折中 # fetch_k 先多取一些候选再挑多样性更好的 k 条 mmr_retriever vector_store.as_retriever( search_typemmr, search_kwargs{k: 3, fetch_k: 20}, ) hits retriever.invoke(签收后多久可以退货) for d in hits: print(d.metadata.get(source), d.page_content[:80])检索质量大致由这些因素决定因素太偏会怎样常见方向切分粒度太粗噪声多太细上下文会断第四节的 size/overlap嵌入模型语义对不齐中文语料选中文效果好的如 bge 系搜索算法只追相似易重复similarityvsmmrTop-K少则漏、多则无关内容变多3–5必要时加相似度阈值元数据过滤搜全库干扰大按类型/日期/权限收窄k调完仍然有很多无关文档再靠第八节的重排、压缩来优化。排查时可以先printtop-k 原文确认证据在不在再怪生成。六、2-Step RAG流程固定问题 → 检索 → 拼 context → 提示词约束「只根据上下文」→ 模型生成。控制力强延迟相对稳。把多块正文用空行拼起来是为了让模型把每块当成独立证据而不是粘成一团from langchain_core.prompts import ChatPromptTemplate from common.models import get_model prompt ChatPromptTemplate.from_template( 你是客服助手。只能根据下面「参考资料」回答。 资料不足以回答时直接说「资料中未找到依据」不要编造。 参考资料: {context} 用户问题: {question} ) model get_model(temperature0) def format_docs(docs) - str: 把检索结果收成提示里的 context双换行分隔块。 return /n/n.join(d.page_content for d in docs) def ask_2step(question: str) - str: docs retriever.invoke(question) # LCEL字典进提示 → 模型第 2 篇讲过的 Runnable 组合 chain prompt | model resp chain.invoke( { context: format_docs(docs), question: question, } ) return resp.content print(ask_2step(签收后多久可以退货)) def format_docs_with_source(docs) - str: parts [] for i, d in enumerate(docs, 1): src d.metadata.get(source, unknown) parts.append(f[{i}] 来源: {src}/n{d.page_content}) return /n/n.join(parts)并在模板里加一句「回答末尾用 [编号] 标注依据。」2-Step 的缺点也清楚寒暄也查库多跳问题先要找政策名再找细节不会自动拆查询。这些交给下一节。七、Agentic RAGAgentic RAG 最重要的是 把「查知识库」做成工具交给create_agent。模型在循环里自己判断要不要查、用什么 query、查完够不够、要不要再查一轮。多轮对话若要记住上下文像别的 Agent 一样挂上 checkpointer参考第 8 篇。1 手写toolfrom langchain.agents import create_agent from langchain.tools import tool from langgraph.checkpoint.memory import InMemorySaver from common.models import get_model tool def search_knowledge(query: str) - str: 从公司知识库检索相关段落。 回答制度、流程、产品规格问题时先调用本工具。 返回带过来源标记的文本若为空说明库中无相关内容。 docs retriever.invoke(query) if not docs: return 未检索到相关资料。 # 返回值进 ToolMessage.content供模型下一轮阅读 parts [] for i, d in enumerate(docs, 1): src d.metadata.get(source, unknown) parts.append(f[{i}] 来源: {src}/n{d.page_content}) return /n/n.join(parts) agent create_agent( modelget_model(temperature0), tools[search_knowledge], checkpointerInMemorySaver(), system_prompt( 你是客服助手。涉及公司政策、流程、规格时必须先 search_knowledge 只根据工具结果回答并标明来源编号。 检索为空或无关时明确说不知道禁止编造。 普通寒暄不必查库。 ), ) config {configurable: {thread_id: rag-1}} result agent.invoke( { messages: [ {role: user, content: 签收后多久可以退货依据是什么} ] }, configconfig, ) print(result[messages][-1].content)2create_retriever_tool不想手写拼接时用官方工厂函数把 Retriever 直接封装车成工具。description仍是模型是否调用的关键信号要写清「搜什么、何时用」。from langchain.agents import create_agent from langchain_core.tools.retriever import create_retriever_tool from common.models import get_model # response_formatcontent_and_artifact 时 # content → 给模型看的拼接文本artifact → 原始 Document 列表给应用层 retriever_tool create_retriever_tool( retriever, namesearch_docs, description搜索产品文档获取与问题相关的段落, response_formatcontent_and_artifact, ) agent create_agent( modelget_model(temperature0), tools[retriever_tool], system_prompt政策/规格问题先 search_docs按资料回答无资料则拒答。, )content_and_artifact的好处见下一小节前端溯源不用再解析模型随机返回的「来源」字符串。3 溯源content给模型artifact给前端。ToolMessage可以带两份数据模型只读content完整结构化结果放artifact应用层拿来渲染「来源退货政策 v2.pdf 第 3 页」。手写工具时也可以自己返回(content, artifact)需工具声明对应 response_format或在工具外根据Document.metadata组装from langchain.messages import ToolMessage # 概念示意content 给模型artifact 给 UI / 审计 ToolMessage( content公司退货政策签收后 7 日内可申请……, # 模型据此生成 tool_call_idcall_123, artifact{ # 应用层据此画「来源」卡片默认不进模型注意力 sources: [ {source: 退货政策v2.pdf, page: 3, doc_id: doc_456}, ], }, )用create_retriever_tool(..., response_formatcontent_and_artifact)时artifact 一般是List[Document]你从每条的metadata抽 source/page 即可。这和「把来源写进 content 让模型复述」不冲突content 里仍可带简短引用artifact 负责机器可读的溯源。4 权限过滤知识库不是人人能看全部。第 5、12 篇的context/store可以在检索工具里做过滤先向量召回再按用户允许的doc_id留下去。通过在检索工具里接ToolRuntime实现from langchain.tools import ToolRuntime, tool tool def search_with_context(query: str, runtime: ToolRuntime) - str: 搜索知识库只返回当前用户有权访问的文档。 user_id runtime.context.user_id # 调用 agent 时传入的 context # 从长期记忆 Store 读权限名单没有 Store 时也可查自家权限服务 item runtime.store.get((permissions, user_id), access) allowed set((item.value or {}).get(allowed_docs, [])) if item else set() docs retriever.invoke(query) filtered [ d for d in docs if not allowed or d.metadata.get(doc_id) in allowed ] if not filtered: return 未检索到你有权查看的相关资料。 return /n/n.join(d.page_content for d in filtered[:3])原理向量检索解决「相关」权限过滤解决「能看」。两步都要做只做相关、不做权限等于检索成了旁路泄密。八、失效补救与完整示例1 基础 RAG 常见失效失效可能原因先试什么答非所问切块太碎/太大k 不对Embedding 偏科调切分与 k抽查看 top-k漏答用户说法和文档用词差太远查询改写同义扩展噪声太多k 过大块里广告/目录太多减小 k重排压缩胡编提示未强制依据检索空仍硬答拒答话术无命中直接返回无法追责没 metadata / 没引用format 带 source要求 [编号]下面是一些建议查询改写用户说「那个七天规定」文档写「签收后 7 日内」。生成前让模型把问题改成检索友好句再用新句子invokeretriever。2-Step 里加一个改写节点Agentic 里写进 system「先把问题改写成检索词再调用工具」。Rerank向量召回先取 20再用交叉编码器或小模型按「与问题相关性」重排截断前 3–5 送进生成。救的是「语义近但答不对题」的噪声。上下文压缩块很长时只抽与问题有关的句子再进提示LLMChainExtractor 一类或自己用小模型摘要。救的是窗口与注意力不是召回率本身。2 完整示例建库 两路问答下面用内存向量库跑通一份迷你 FAQ → 切分入库 → 2-Step 与 Agent 各问一次。把faq_text换成TextLoader(xxx.txt).load()即接真实文件。from langchain.agents import create_agent from langchain.tools import tool from langchain_core.documents import Document from langchain_core.prompts import ChatPromptTemplate from langchain_core.vectorstores import InMemoryVectorStore from langchain_openai import OpenAIEmbeddings from langchain_text_splitters import RecursiveCharacterTextSplitter from common.models import get_model # ---------- 1) 准备文档演示用内联生产改 Loader---------- faq_text # 退货政策 顾客可在签收后 7 日内申请退货。商品须未使用、包装完整。 虚拟商品、定制商品不适用 7 日退货。 # 运费 退货原因属于质量问题的运费由商家承担其余情况由顾客承担。 docs [ Document( page_contentfaq_text, metadata{source: faq.md, product_line: mall}, ) ] splitter RecursiveCharacterTextSplitter(chunk_size120, chunk_overlap30) chunks splitter.split_documents(docs) # ---------- 2) 索引 ---------- embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vector_store InMemoryVectorStore.from_documents(chunks, embeddings) retriever vector_store.as_retriever(search_kwargs{k: 3}) def format_docs_with_source(docs: list[Document]) - str: parts [] for i, d in enumerate(docs, 1): src d.metadata.get(source, unknown) parts.append(f[{i}] 来源: {src}/n{d.page_content.strip()}) return /n/n.join(parts) if parts else # ---------- 3) 2-Step ---------- prompt ChatPromptTemplate.from_template( 只根据参考资料回答不足则说「资料中未找到依据」。 回答末尾用 [编号] 标注依据。 参考资料: {context} 问题: {question} ) rag_chain prompt | get_model(temperature0) def ask_2step(question: str) - str: docs_hit retriever.invoke(question) msg rag_chain.invoke( { context: format_docs_with_source(docs_hit), question: question, } ) return msg.content # ---------- 4) Agentic ---------- tool def search_knowledge(query: str) - str: 检索商城 FAQ 知识库返回带来源编号的段落。 docs_hit retriever.invoke(query) text format_docs_with_source(docs_hit) return text or 未检索到相关资料。 agent create_agent( modelget_model(temperature0), tools[search_knowledge], system_prompt( 政策问题必须先 search_knowledge按资料回答并引用 [编号] 无资料则明确拒答。寒暄不必查库。 ), ) if __name__ __main__: q1 签收后多久能退货 q2 今天天气怎么样 # 应拒答或闲聊不应编造政策 print( 2-Step ) print(ask_2step(q1)) print(/n Agent ) r agent.invoke({messages: [{role: user, content: q1}]}) print(r[messages][-1].content) r2 agent.invoke({messages: [{role: user, content: q2}]}) print(r2[messages][-1].content)验收建议q1两路都能答到「7 日」并带来源。把 FAQ 里「7 日」删掉重建索引后应走拒答而不是背训练记忆。打印retriever.invoke(q1)确认命中块里真有退货句——生成错之前先看检索错没错。试chunk_size40与400各建一次看短问「虚拟商品能否退」谁更稳体会切分影响。依赖安装示例pip install langchain langchain-openai langchain-text-splitters langchain-communityEmbedding 与 chat 不必同一家但都要配好各自的 API Key /base_url。最后2026 年一晃已经过半AI 大模型的热潮不仅没有降温反而持续升温金融行业用大模型做风控、医疗依靠 AI 解析影像电商、制造、教育各行各业都在把 AI 融入日常业务。曾经热闹的 “百模大战”早就告别单纯比拼模型参数正式进入落地应用时代。现在企业疯狂紧缺一类人才懂业务、懂 AI、能做出可上线项目的大模型开发工程师岗位缺口大薪资待遇十分可观。风口再好不如手握高薪 offer 实在。行情火热普通人、程序员该怎样从零入门大模型抓住这波机会今天整理好【2026 最新版】AI 大模型全套免费学习资源覆盖零基础入门、项目实战、理论知识、大厂面试从基础一路进阶。所有资料分类归档没有多余杂料无套路免费分享给想要入局 AI 赛道的程序员与零基础小白扫码免费领取全部内容1、大模型系统化完整学习路线2、大模型经典书籍文档3、AI 大模型最新行业研究报告4、企业级实战项目 完整配套源码5、大厂大模型面试真题汇总6、这些资料真的有用吗这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理现任上海殷泊信息科技CEO其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证服务航天科工、国家电网等1000企业以第一作者在IEEE Transactions发表论文50篇获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的技术人员这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】