
做AI大模型应用开发有一段时间的朋友对RAG这个词肯定不陌生。它是“Retrieval-Augmented Generation”的缩写中文叫检索增强生成。我第一次接触这个概念的时候感觉就是“这不就是把资料库搜一搜再喂给大模型吗”但真正动手做过几个RAG项目之后才发现这里面的门道比想象中多得多。从文档怎么切、向量怎么存、检索怎么召回到后来出现的Agentic RAG这种把智能体编排和检索结合起来的玩法每一步都有值得深挖的细节。这篇内容我打算把RAG从原理到实战完整讲一遍。不管你是刚入门大模型开发、想在公司内部搭一个知识库问答系统还是已经在用LangChain、LlamaIndex写RAG但总觉得效果差口气这篇文章都能给你一个相对完整的参考。我会结合实际项目中的坑和经验把概念讲透把代码给全尽量让每个看过的人都能照着搭一套自己的RAG服务。1. RAG到底是什么从大模型的“硬伤”说起很多人在刚接触RAG时容易把它理解成“给大模型加个知识库”。这个说法不算错但不准确。要理解RAG得先理解大模型本身有什么问题。1.1 大模型再强也有三个绕不过去的坑第一个坑是知识截止时间。基座模型训练时用的是某个时间点之前的数据比如GPT-4等商业模型的知识截止到2023年或者2024年之后发生的事情它一概不知道。如果你问它“今年最新的政策是什么”它只能编一个看起来很合理的答案。第二个坑是幻觉。大模型的本质是根据概率预测下一个词它并不知道“事实”是什么只是在生成“最像样的回答”。一旦问题超出它的理解范围或者数据里根本没有相关信息它就会一本正经地胡说八道。第三个坑是私有数据无法访问。企业内部的规章制度、产品文档、客服对话记录这些数据根本不在大模型的训练语料里它连“见过”都没见过更别说回答相关问题了。1.2 从“闭卷考试”到“开卷考试”RAG的核心思想我经常用一个生活化的类比来解释RAG大模型本身是一个“知识渊博但记忆力有限的学生”它在闭卷考试时只能凭记忆作答遇到没背过的题就容易瞎编。RAG做的事情就是允许这个学生“开卷考试”——在回答问题之前先给他一堆参考资料让他翻着资料找答案再结合自己的语言组织能力给出回答。具体到技术层面RAG的整体流程是这样的先把文档库里的内容经过切分、向量化处理后存入向量数据库当用户提出一个问题时系统会把问题也做向量化然后去向量数据库里检索出最相关的文本片段最后把这些片段作为上下文、连同用户问题一起拼进Prompt交给大模型生成最终答案。这个思路看起来很朴素但它解决了一个很本质的问题大模型不需要“记住”所有知识只需要“知道去哪里找”知识。模型负责的是理解和生成知识库负责的是存储和检索两者各司其职。这也是为什么过去两年RAG会成为企业落地大模型应用最主流的技术方案——它不需要重新训练模型只需要外接一个知识库成本低、见效快、好维护。2. RAG的完整流程拆解从文档到答案的四步走如果你查RAG相关资料会看到各种流程图有的复杂到让人头晕。其实剥开来看RAG系统的核心链路只有四个环节数据准备、索引构建、检索召回、生成回答。其中数据准备和索引构建属于离线阶段一般在文档更新时执行检索和生成属于在线阶段每次用户提问都会跑一遍。2.1 离线阶段文档加载、切分与向量化离线阶段的第一步是文档加载。很多初学者在这里就踩坑——以为RAG就是“把PDF直接丢给模型”。实际上模型只能理解文本PDF里的表格、图片、复杂排版都需要先做解析。我常用的方案是pypdf处理纯文本PDFunstructured处理带表格的文档如果需要保留文档结构信息还可以用markdown格式的源文件配合MarkdownHeaderTextSplitter做结构化切分。文档加载完之后就是文本切分chunking。这一步是整个RAG里最容易被低估的环节。切得太粗一个chunk动辄几千字检索出来噪声很大大模型很难定位到精确答案切得太细每个chunk只有一两句话语义不完整检索时又容易漏掉关键信息。我做过对比测试对于企业内部的技术文档chunk_size512、chunk_overlap50是一个比较稳健的起点。注意chunk_overlap不能省它能让相邻chunk之间保留上下文衔接避免关键句子正好被切在边界上导致语义断裂。切分完成后就是向量化。这里需要选择embedding模型把每一段文本转换成一个高维向量。这个向量的意义是把文本的语义映射到多维空间中语义相近的文本在空间中的距离也相近。目前国内最常用的开源embedding模型是BAAI/bge-m3和text2vec-large-chinese效果不错且支持中文。如果预算允许OpenAI的text-embedding-3-large在英文场景下效果更好但中文场景跟bge差距不大。选择embedding模型时要注意检索时的向量模型必须跟入库时的向量模型保持一致否则语义空间对不上检索效果会崩盘。这个错误我见过太多人犯换了模型忘了重建索引结果召回率直接归零。最后把向量和原文存入向量数据库。向量数据库的核心能力是支持“相似度检索”——给定一个查询向量快速找出库中最相似的K个向量并返回对应的原文内容。常用的向量数据库有开源的Milvus、Chroma、Qdrant也有很多人直接用PostgreSQL加pgvector插件这个后面展开讲。2.2 在线阶段查询处理、检索与重排序用户提问时系统会走一条和离线阶段类似的链路。用户的query先经过同样的embedding模型转换成向量然后去向量数据库里做相似度检索。常见的相似度算法有余弦距离、欧氏距离、内积等其中余弦距离最常用它对向量长度不敏感更适合衡量文本语义相似度。拿到TopK召回的chunk之后很多系统会加一个**重排序rerank**环节。这是提升RAG效果非常重要但常常被忽略的一步。原理很简单向量检索本质上是“用向量近似找语义相近”这种“粗排”效率高但精度一般尤其当知识库很大时前几个chunk很可能不是最相关的。重排序的做法是用一个专门的cross-encoder模型把query和每个chunk两两拼接输入模型让模型直接判断它们之间的相关性分数然后按分数重新排序。bge-reranker-v2-m3是常用的开源重排序模型效果提升非常明显。我在这里补充一个重要经验重排序不是一个“可选项”而是在知识库达到一定规模后的“必选项”。如果知识库只有几十个文档、一次检索范围很小不加重排序影响不大但如果知识库里有上万条chunk向量检索召回的TopK可能里面混了不少表面相似但实际不相关的内容这时候一个高质量的重排序模型往往能把top_k50的候选集中到最精准的5个。2.3 生成阶段把检索结果拼进Prompt检索完毕之后核心工作就是把检索到的chunk整合进Prompt。这一步有几个细节要注意。一是给模型清晰的任务指令比如“请根据以下资料回答问题如果资料中找不到答案请直接回答‘抱歉我无法从现有资料中找到答案’”。这个指令能显著降低幻觉。二是标注引用来源比如在每段资料前标注序号要求模型回答时引用对应序号这样后续可以做到可溯源这也是企业级RAG系统必须的能力。三是控制上下文长度别一股脑把大量chunk全部塞进Prompt上下文太长不仅会增加成本、拉高延迟还会稀释关键信息。下面是当时我们项目里用的一个很典型的Prompt模板你是一个企业内部知识库助手。请严格根据以下资料回答用户问题。 资料内容 [1] {chunk_content_1} [2] {chunk_content_2} [3] {chunk_content_3} 要求 1. 如果资料中没有相关信息请回答“抱歉当前资料库中没有找到相关内容”。 2. 回答时标记引用来源例如“……资料[1]”。 3. 不要编造资料中不存在的信息。 用户问题{question}这里面的关键是“明确告诉模型资料是权威依据超出资料范围的信息不要输出”。很多人做RAG时幻觉还是严重往往就是因为Prompt里没有强制约束模型自由发挥的空间太大。3. 从基础RAG到Agentic RAG为什么大家都在谈“智能体”我在热搜词里看到“agentic rag”这个方向确实是最近一年RAG领域最热门的演进方向。所谓Agentic RAG简单说就是从“一次检索、一次生成”的简单流水线演进为“大模型自主决策、多轮检索、动态调整”的智能体系统。3.1 基础RAG的局限性在哪里基础RAG的流程是固定的用户提问 → 向量检索 → 拼接Prompt → 生成回答。这套流程在知识库覆盖得比较好、问题比较直接的情况下效果不错但遇到复杂场景就露馅了。我举一个典型场景。你问“A方案和B方案在成本上的差异以及各自适用的业务规模”如果基础RAG只用一次向量检索它可能只找到A方案的文档或者只找到B方案的文档很难同时把两个方案的完整对比信息都捞到。又比如你问“我们公司去年第四季度的营收为什么会下滑”这需要用检索结果作为线索再去追查渠道数据、产品反馈、竞品动态等多个维度的资料这是一个多步推理的过程一次检索根本解决不了。3.2 Agentic RAG的核心用LangGraph编排“检索决策”Agentic RAG的思路是让大模型自己决定“下一步要做什么”。它不再是被动地接收一堆chunk然后生成答案而是像一个小型工作流引擎大模型在每一步都会判断当前的资料够不够回答用户问题不够的话应该再检索什么是要换一个query继续搜还是直接回答这里就轮到LangGraph上场了。LangGraph是LangChain团队推出的智能体编排框架它的核心是StateGraph——把整个流程定义成一个图状结构节点是“动作”边是“状态转移条件”。大模型在每个节点可以根据当前状态决定去哪个分支这跟传统RAG那种写死的顺序链路完全不同。我当时用LangGraph搭过一个简化版的Agentic RAG核心逻辑是这样graph StateGraph(AgentState) graph.add_node(router, route_query) # 判断问题类型决定走哪条路径 graph.add_node(retriever, retrieve) # 执行向量检索 graph.add_node(grader, grade_docs) # 评估检索结果是否足够 graph.add_node(rewriter, rewrite_query) # 如果答案不够好改写query graph.add_node(generator, generate) # 生成最终答案 graph.set_entry_point(router) graph.add_edge(router, retriever) graph.add_conditional_edges( grader, decide_next, # 检查检索结果是否通过评估 {pass: generator, retry: rewriter} ) graph.add_edge(rewriter, retriever) graph.add_edge(generator, END)没写过完整代码的人可能看不懂这些细节但核心思想很好理解系统先把用户query送入一个路由节点决定是走“直接向量检索”还是“需要多轮检索”的路径检索完把结果喂给一个大模型来做相关性评估如果评估结果是“这些资料不够”就改写query重新检索最多重试几次防止死循环。这就像你查资料时搜了一遍发现不对换个关键词再搜一次直到找到满意的答案。3.3 查询路由、多轮对话和工具调用Agentic RAG带来的另一个能力是查询路由Query Routing。基础RAG对任何问题都用同一套向量检索但实际场景中不同问题需要不同的处理方式。有些问题可以直接调用一个API获取实时数据比如库存数量、天气信息有些问题需要查SQL数据库有些问题才需要走向量检索。查询路由就是让大模型先判断“这个问题应该用哪个数据源”然后调用对应的工具。这种“模型做决策、工具做执行”的模式就是Agentic RAG更大的想象空间。举个例子我们之前做客服系统时用户问“退货流程是什么”这种标准问题走知识库检索就行但如果问“我的订单现在到哪了”就需要调用订单系统的API拉取实时物流信息。这两种情况如果都走向量检索后者永远得不到正确答案。有了路由之后大模型会自己判断该调用哪个工具体验完全不一样。还有一个关键是多轮对话记忆。RAG系统如果只处理“当前这一问”上一轮的上下文就丢了用户问“那它的价格呢”时系统根本不知道“它”指的是什么。做法是把对话历史也拼进检索和生成的上下文里必要时先用一个小模型做“指代消解”把“它”替换成真正的实体再检索。这个细节直接影响对话式RAG的用户体验。4. 选型对比框架、向量库和部署方案怎么选RAG的技术栈选择直接影响开发效率和上线后的维护成本。我在这部分把应用框架、向量数据库和本地部署三条线分别讲清楚结合我的实际使用经验给出选型建议。4.1 应用框架LangChain还是LlamaIndexLangChain是生态最丰富、社区最活跃的RAG开发框架目前已经迭代到0.3/0.4版本API稳定了不少。它的优势是集成了大量模型接口、向量库、工具链几乎你能想到的组件它都有适配器。如果你是第一次做RAG或者团队里对组件化开发比较熟悉直接用LangChain最省事。LlamaIndex则更聚焦在“数据连接”这个方向。它对文档的加载、索引、结构化管理做得比LangChain更深入尤其是处理大量PDF、Notion、数据库等异构数据源时LlamaIndex的数据连接器更好用。我自己的习惯是如果项目以“文档问答”为核心用LlamaIndex更顺手如果后续要做复杂的智能体编排、多工具调用那就选LangChainLangGraph。维度LangChainLlamaIndex核心定位通用LLM应用框架数据索引与检索框架数据连接器丰富非常丰富Agent编排强LangGraph较弱学习曲线中等平缓社区生态最大较大当然框架不是必须的。有很多人直接用openai的SDK加pgvector自己写一个几百行的RAG服务反而比套框架更可控、更好调优。这里我的观点是先想清楚需求再选框架不要为了用框架而用框架。4.2 向量数据库pgvector、Milvus、Chroma怎么选向量数据库是RAG的存储底座选型时主要看数据量、部署运维成本和查询性能。我用过Chroma、Milvus和pgvector简单说下感受。Chroma是最轻量的选择直接pip install chromadb就能用数据存在本地文件里适合原型验证和小型项目。它的缺点是并发能力和数据管理能力较弱数据量超过几百万条之后查询性能会明显下降。Milvus是专业的分布式向量数据库支持百亿级向量、高并发、丰富的索引类型HNSW、IVF等适合生产环境大规模使用但部署和运维成本也高需要单独维护一套集群。pgvector则是个“取巧”的方案——既然很多企业本来就在用PostgreSQL直接在现有数据库上加一个向量扩展不用额外引入新组件数据还可以和业务数据放在一起统一管理。我当时的项目选择是pgvector主要原因是知识库规模在百万条以内PostgreSQL单机加HNSW索引完全能扛住团队本来就熟悉PostgreSQL不需要额外学一套运维最重要的是RAG的检索结果经常需要跟业务数据联查比如用户画像、权限信息等用pgvector直接在SQL层面做关联查询非常方便。如果你的场景是“知识库特别大、并发特别高、专业运维团队齐全”再上Milvus不迟。4.3 本地部署与模型选择从Ollama到LlamaFactory部署策略上很多To B项目要求数据不出内网这时候就要考虑本地部署大模型。Ollama是目前最简单的本地模型运行工具一行命令就能跑起qwen2.5、llama3.1等开源模型。它把模型量化、显存管理、API服务全部封装好了开发阶段用来做验证非常方便。我在一台32G内存的Mac上跑qwen2.5:7b配合RAG做技术文档问答速度跟体验都还不错。不过如果要用在正式生产环境推荐用vLLM或SGLang部署模型服务它们对并发请求的支持、吞吐量比Ollama强不少。另外很多团队会纠结“要不要微调模型”。我明确说能用RAG解决的问题不要一上来就微调。RAG更新知识只需要改知识库成本低、及时性强微调是改变模型本身的行为模式适合“让模型学会某种输出风格或特定任务能力”不适合“让模型记住实时变化的事实数据”。如果确实要做微调LlamaFactory是口碑很好的一站式工具支持LoRA、QLoRA等低成本微调方案在消费级显卡上也能完成7B模型的微调训练。5. 动手实践基于FastAPILangChainpgvector搭一个最小RAG服务讲完这么多理论和选型终究要落到代码上。这部分我把一个可以跑起来的最小RAG服务拆开来讲技术栈就是热搜词里反复出现的那个组合FastAPI LangChain RAG pgvector。5.1 环境准备与依赖安装先把代码跑起来需要安装的内容整理清楚。pip install fastapi uvicorn langchain langchain-community langchain-openai pip install pgvector psycopg2-binary pip install sentence-transformers这里说明一下职责fastapi用来提供HTTP接口langchain负责组装检索链路pgvector负责向量存储和相似度检索sentence-transformers用来加载本地embedding模型。用本地embedding模型的好处是向量化过程不需要调用外部API数据不出内网符合很多企业的数据安全要求。5.2 文档入库构建索引第一步先把文档切分、向量化并写入pgvector。这里以加载一个Markdown文档为例。from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import PGVector # 1. 加载文档 loader TextLoader(docs/员工手册.md, encodingutf-8) docs loader.load() # 2. 切分文档chunk_size512, overlap50 splitter RecursiveCharacterTextSplitter( chunk_size512, chunk_overlap50, separators[\n\n, \n, 。, , , , , , ] ) chunks splitter.split_documents(docs) # 3. 加载embedding模型 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-m3) # 4. 写入pgvector自动创建向量表 PGVector.from_documents( documentschunks, embeddingembeddings, connection_stringpostgresqlpsycopg2://user:passlocalhost:5432/rag_db, collection_nameemployee_handbook, )RecursiveCharacterTextSplitter的separators参数值得多说一句它是“按优先级尝试切分”的先按双换行切不行再按单换行再不行按句号切依次递归。对于中文文档把句号、感叹号、分号都加进去能保证切出来的chunk语义相对完整避免一句话被硬生生劈成两半。5.3 查询接口检索生成链路索引建好之后写一个查询接口。这个接口接收用户问题先检索TopK相关chunk再组装成Prompt调用LLM生成答案。from fastapi import FastAPI from pydantic import BaseModel from langchain_community.vectorstores import PGVector from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate app FastAPI() embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-m3) vectorstore PGVector( connection_stringpostgresqlpsycopg2://user:passlocalhost:5432/rag_db, embedding_functionembeddings, collection_nameemployee_handbook, ) llm ChatOpenAI( modelqwen2.5:7b, base_urlhttp://localhost:8000/v1, # 本地vLLM/Ollama服务地址 api_keyEMPTY, ) prompt ChatPromptTemplate.from_template( 你是一个企业内部知识库助手。请根据以下资料回答问题\n {context}\n 如果资料中没有相关信息请回答抱歉当前资料库中没有找到相关内容。\n 问题{question} ) class QueryBody(BaseModel): question: str app.post(/ask) def ask(body: QueryBody): retriever vectorstore.as_retriever(search_kwargs{k: 5}) docs retriever.invoke(body.question) context \n.join( f[{i1}] {doc.page_content} for i, doc in enumerate(docs) ) answer llm.invoke(prompt.format(contextcontext, questionbody.question)) return {answer: answer.content, sources: [d.metadata for d in docs]}这样一个最简单的RAG服务就搭好了uvicorn main:app --reload启动后请求/ask接口就能问答。整个过程不超过100行代码这也是RAG能快速落地的原因之一——它不像微调那样需要训练资源和数据标注知识库做好就能跑。5.4 给RAG加上“智能”一个简单的Agentic循环示例如果想在这个基础上加一点点“智能”可以用LangGraph实现“先检索、评估、不行再改写query重试”的循环。这里给一个最简版的判断循环演示Agentic RAG的核心思想from langgraph.graph import StateGraph, END from typing import TypedDict, List class RagState(TypedDict): question: str query: str docs: List[str] retries: int def retrieve(state: RagState): docs vectorstore.similarity_search(state[query], k5) return {docs: docs} def check_docs(state: RagState): # 用LLM判断检索结果是否与问题相关 docs_text \n.join(d.page_content for d in state[docs]) response llm.invoke( f问题{state[question]}\n资料{docs_text}\n 这些资料能否回答该问题请回答YES或NO。 ) if YES in response.content: return pass return retry def rewrite_query(state: RagState): new_query llm.invoke( f原始问题{state[question]}请改写一个更利于检索的查询。 ).content return {query: new_query, retries: state[retries] 1} def generate(state: RagState): context \n.join(d.page_content for d in state[docs]) answer llm.invoke(prompt.format(contextcontext, questionstate[question])) return {answer: answer.content} # 构建图 builder StateGraph(RagState) builder.add_node(retrieve, retrieve) builder.add_node(check, check_docs) builder.add_node(rewrite, rewrite_query) builder.add_node(generate, generate) builder.set_entry_point(retrieve) builder.add_edge(retrieve, check) builder.add_conditional_edges( check, lambda state: pass if state[retries] 2 else (lambda s: pass if s pass else retry)(None), {pass: generate, retry: rewrite} ) builder.add_edge(generate, END)代码写得有点粗糙但思想展示得很清楚每次检索后都让LLM判断“资料够不够”不够就改写query再来一轮直到满意或达到最大重试次数。这个“反馈循环”正是RAG从“机械流水线”变成“智能体系统”的关键。6. 常见问题与调优经验实录最后这部分我把做RAG项目实际过程中遇到的高频问题和排查思路整理成一个速查表算是给后来人的避坑指南。每一类问题我都踩过写出来希望能帮你少走弯路。6.1 检索效果差命中的都是“看似相关、实则无关”的内容这是RAG系统最常见的痛点。排查顺序建议是先看chunk切分是否合理有没有把完整段落切碎再看embedding模型选的是不是适合中文再看是否加了重排序环节。我见过一个真实案例chunk_size被设成了2000结果一个chunk包含了好几页内容检索到的片段里关键信息被淹没在大量无关文字里怎么调Prompt都救不回来。后来把chunk_size改成512同样的知识库回答质量立刻上升了一个档次。另外建议用专业的检索评估指标比如Hit Rate和MRR。做法是准备一批“问题标准文档”的测试集运行检索后看正确答案有没有出现在TopK里、排在第几位。没有这个评估手段你只能靠肉眼感觉“好像效果还行”根本没法客观地对比参数调整前后的效果。6.2 回答“读了资料”但还是答错有一种情况是检索结果明明包含正确答案但大模型最后还是答错了。这个时候问题大概率出在Prompt上。你可能没有在Prompt里强调“必须严格根据资料回答”也没有告知“资料中找不到就直说不知道”。大模型的天性就是倾向于给出一个完整、流畅的回答哪怕没有依据也会硬编。在Prompt里把“禁止编造”“以资料为准”用明确的指令写出来能显著减少这种情况。还有一种可能是上下文太长导致模型“迷失在中间”。检索回来的chunk如果太多太长模型注意力会被稀释。建议控制TopK数量一般3到5个chunk同时把最相关的chunk排在前面或者用重排序模型压缩候选范围。6.3 性能瓶颈响应慢、成本高RAG系统的延迟主要来自三个环节向量检索、重排序、LLM生成。向量检索在pgvector加了HNSW索引之后百万级向量也就几十毫秒基本不是瓶颈重排序如果每次都跑模型会增加几百毫秒到秒级的延迟LLM生成是最耗时的尤其大模型回答一长段话要好几秒。优化思路有三个方向。第一给pgvector建HNSW索引设置合理的m和ef_search参数在延迟和召回率之间取平衡。第二对重排序结果做缓存同样的query在短时间内不要重复调用重排序模型。第三把LLM换成更小的模型或者用流式输出让用户先看到部分结果。实测下来7B量级的模型配合RAG做内部知识库问答速度和质量都能满足一般企业的需求。6.4 数据更新了但系统回答还是旧内容这背后是缓存和索引更新机制的问题。向量数据库里存的是文档的“向量快照”如果你的源文档更新了但没有重新走一遍切分和向量化流程库里存的还是旧内容。最简单的做法是在文档更新时监听文件变化触发重新入库如果用私有化部署的文档系统可以通过Webhook回调来触发更新。另外要注意“索引覆盖”问题。如果新文档和旧文档是同一个collection直接调用PGVector.from_documents不会清掉旧数据库里会同时存在新旧两份检索时可能返回过时内容。更稳妥的方案是先按collection_name删除旧向量再写入新向量或者给每个文档增加版本号字段检索时用过滤条件排除旧版本。6.5 独家经验评估你的RAG系统先要建立“黄金测试集”我不知道多少人做RAG项目时会认认真真建评测集但我敢说大部分团队都没有。大家往往是把知识库一传、代码一跑问了几个自己拍脑袋的问题觉得“效果不错”就上线了。这种做法风险很大因为RAG系统是一个由许多环节组成的链路任何一个环节的参数变化都会影响整体效果没有一套固定的评测集你根本无法判断改动是变好了还是变坏了。我建议项目开始第一周就建一个20到40条的评测集覆盖简单的“事实查找型”问题、复盘的“总结对比型”问题、“知识库中没有答案”的越界问题这几类。每条数据包含问题、理想答案的关键点、期望引用的文档ID。评测时可以用LLM自动打分也可以用人工抽检但必须保证每次调参后都在同一套测试集上对比。这个习惯一开始看着麻烦长期看是性价比最高的事。实际用下来还有一个小技巧RAG效果不好的时候先别急着换模型或改代码把具体的badcase拿出来看是“检索没召回”还是“召回了但没答对”。这两类问题的解决路径完全不同——前者要调切分、embedding、TopK、重排序后者要调Prompt、模型参数甚至后处理逻辑。用分类排查的方法定位badcase比盲目调参高效得多。整个RAG技术从2023年开始火起来到现在已经基本成为大模型应用落地的基础设施。它没有想象中那么高深但要做好、做稳、做到生产级需要把每个细节都打磨到位。希望这篇从原理到代码的完整梳理能帮你在做RAG项目的路上少踩几个坑。