☰
RAG原理与实战:从检索增强生成到Agentic RAG应用开发
2026/10/3 15:44:57 网站建设 项目流程

做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处理纯文本PDF,unstructured处理带表格的文档,如果需要保留文档结构信息,还可以用markdown格式的源文件配合MarkdownHeaderTextSplitter做结构化切分。

文档加载完之后就是文本切分(chunking)。这一步是整个RAG里最容易被低估的环节。切得太粗,一个chunk动辄几千字,检索出来噪声很大,大模型很难定位到精确答案;切得太细,每个chunk只有一两句话,语义不完整,检索时又容易漏掉关键信息。我做过对比测试,对于企业内部的技术文档,chunk_size=512、chunk_overlap=50是一个比较稳健的起点。注意,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_k=50的候选集中到最精准的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还是LlamaIndex

LangChain是生态最丰富、社区最活跃的RAG开发框架,目前已经迭代到0.3/0.4版本,API稳定了不少。它的优势是集成了大量模型接口、向量库、工具链,几乎你能想到的组件它都有适配器。如果你是第一次做RAG,或者团队里对组件化开发比较熟悉,直接用LangChain最省事。

LlamaIndex则更聚焦在“数据连接”这个方向。它对文档的加载、索引、结构化管理做得比LangChain更深入,尤其是处理大量PDF、Notion、数据库等异构数据源时,LlamaIndex的数据连接器更好用。我自己的习惯是:如果项目以“文档问答”为核心,用LlamaIndex更顺手;如果后续要做复杂的智能体编排、多工具调用,那就选LangChain+LangGraph。

维度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. 动手实践:基于FastAPI+LangChain+pgvector搭一个最小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", encoding="utf-8") docs = loader.load() # 2. 切分文档,chunk_size=512, overlap=50 splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(docs) # 3. 加载embedding模型 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") # 4. 写入pgvector,自动创建向量表 PGVector.from_documents( documents=chunks, embedding=embeddings, connection_string="postgresql+psycopg2://user:pass@localhost:5432/rag_db", collection_name="employee_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_name="BAAI/bge-m3") vectorstore = PGVector( connection_string="postgresql+psycopg2://user:pass@localhost:5432/rag_db", embedding_function=embeddings, collection_name="employee_handbook", ) llm = ChatOpenAI( model="qwen2.5:7b", base_url="http://localhost:8000/v1", # 本地vLLM/Ollama服务地址 api_key="EMPTY", ) 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"[{i+1}] {doc.page_content}" for i, doc in enumerate(docs) ) answer = llm.invoke(prompt.format(context=context, question=body.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"], k=5) 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(context=context, question=state["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项目的路上少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询