尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

RAG知识库从Demo到企业级落地:检索、重排与工程化实践

RAG知识库从Demo到企业级落地:检索、重排与工程化实践 RAG知识库在企业内部落地时真正难的不是把大模型 API 接进来而是让“查得到、答得准、更新得动”。很多团队第一版用向量数据库加一个开源 Embedding 模型就能跑通 Demo但一旦放上几千篇文档、面对不同业务提问、还要处理权限和数据更新就会出现检索结果混乱、答案依旧幻觉、链路日志缺失、调优无从下手等问题。这篇文章围绕“检索、召回、重排、评估、工程化”这条主线从最小可运行案例讲到企业级知识库的系统设计适合正在做 RAG 项目、准备做技术选型、或者已经把 Demo 跑通但不知道下一步怎么优化的开发者阅读。1. RAG知识库系统到底解决什么问题1.1 大模型幻觉和知识时效问题大模型回答内容不可控通常来自两个原因一是模型训练数据有截止时间新的业务资料、内部制度、产品文档不在训练集中二是模型在回答不确定问题时倾向于“补全”用高概率的词补出看似合理的答案这就是幻觉。RAGRetrieval-Augmented Generation检索增强生成的核心思路不是重新训练模型而是在模型回答前接入一个检索系统从企业知识库中找出于问题相关的文档片段把片段连同问题一起交给大模型生成答案。这样模型不需要记住企业私有知识只需要依据检索到的证据回答。RAG 解决的不只是“不懂新知识”的问题更重要的是让回答有来源可查。检索到的片段可以作为引用依据用户能看到答案来自哪份文档这对企业内部知识管理、客服问答、合规审计场景非常关键。1.2 RAG的完整工作链路一个完整 RAG 知识库系统可以分为两个阶段索引阶段和查询阶段。索引阶段要做的事文档解析把 PDF、Word、Markdown、HTML、表格等格式转成纯文本或结构化文本。文本切片按段落、标题、固定长度切分文档。向量化用 Embedding 模型把文本片段转换成向量。写入向量库把向量和原文、元数据一起存到向量数据库。查询阶段要做的事问题向量化把用户问题用同一个 Embedding 模型转成向量。召回从向量库检索 TopK 相似片段或者用关键词检索和向量检索做混合召回。重排对召回结果做更精细的相关性排序。生成把排序后的片段拼成上下文结合问题调用大模型生成。很多初学者只做到“向量化 TopK 相似度 塞进 Prompt”就认为完成了 RAG。实际上这只是一个最简链路也是问题最多的一条链路。后面的重排、混合检索、元数据过滤、多轮对话管理才是决定效果能否达到生产要求的关键。1.3 从Demo到系统还差哪些能力把 RAG 当做一个“Hello World”跑通很快但要支撑企业级知识库还需要补齐这些能力数据更新文档被修改后旧切片如何识别、替换或删除。权限控制不同部门、不同角色能看到的文档片段不能同时进入上下文。可观测性一次问答检索到了哪些片段、模型何时调用、耗时多少、成本多少。评估机制用什么指标判断系统改好了还是改坏了。缓存与降级高频问题是否有缓存向量库或模型接口异常时如何兜底。因此这篇教程先解决“怎么跑通”再逐步讨论“怎么优化”和“怎么落地”。2. 技术选型与开发环境准备2.1 RAG各环节组件选型对比RAG 涉及的组件较多选型时不能只盯某一个环节要看整体维护成本和多组件兼容性。下表是一个相对常见的选型对比适合做第一版环节可选方案特点文档解析pypdf、unstructured、textract、markerpypdf 简单但表格解析弱unstructured 支持格式多切片工具LangChain TextSplitter、LlamaIndex NodeParser、自研切分自研切分适合有明确章节结构的内部文档Embedding模型OpenAI text-embedding-3-small、bge-m3、bge-large-zh、m3e中文场景优先考虑 bge 系列开源可私有化部署向量数据库Milvus、Qdrant、Weaviate、Elasticsearch、Chroma生产优先 MilvusDemo 可以用 Chroma关键词检索Elasticsearch、OpenSearch、SQL LIKE和向量检索做混合召回时关键词检索不能少重排模型bge-reranker-v2-m3、BCE-reranker-base_v1、cohere rerank跨语言和中文场景优先 bge-reranker大模型本地部署 Qwen 系列、DeepSeek或商用 API企业内部敏感数据建议私有化部署RAG框架LangChain、LlamaIndex、Dify、RAGFlow框架降低编码成本但排查链路时要理解底层实现这里要特别强调不要把 RAG 框架当成黑盒。Dify、RAGFlow 能快速搭建知识库流水线但生产环境出现问题后如果不知道检索日志在哪看、索引怎么刷新、重排模型怎么替换排查会非常困难。2.2 开发环境与依赖准备下面示例使用 Python 3.10 环境使用 Milvus 作为向量数据库LangChain 作为编排工具bge-m3 作为 Embedding 模型bge-reranker-v2-m3 作为重排模型。这套组合在中文场景下比较适合且可以完全私有化部署。先准备基础依赖python -m venv venv source venv/bin/activate pip install langchain langchain-community langchain-milvus pip install pymilvus pymupdf unstructured pip install sentence-transformers pip install FlagEmbedding pip install elasticsearch需要注意版本匹配。LangChain 版本升级很快旧版本load_qa_chain、VectorStoreRetriever的引用方式可能不同。落地前先确认实际安装版本pip show langchain langchain-community langchain-milvus注意本文代码只用于演示思路实际项目要根据自己的包名、路径、依赖版本和目录结构调整不要直接复制到生产环境。2.3 向量数据库准备Milvus 支持多种部署方式。本地学习使用 Docker 启动 standalone 模式比较快docker compose up -d简单启动可以这样写docker run -d --name milvus \ -p 19530:19530 \ -p 9091:9091 \ milvusdb/milvus:latestMilvus 默认端口 19530 是 gRPC9091 是 HTTP 健康检查和管理端口。启动后可以用 Python 客户端确认连接from pymilvus import connections connections.connect(aliasdefault, hostlocalhost, port19530) print(milvus connected)如果使用 Elasticsearch 做关键词检索和向量检索可以直接用 ES 的 kNN 能力避免维护两套存储。但推荐在企业规模较大时使用 Milvus 存向量配合 ES 或 OpenSearch 做关键词召回再在应用层合并结果这样每个组件都能独立扩展。2.4 项目目录结构一个适合学习和扩展的 RAG 项目目录可以这样组织rag-project/ ├── config/ # 配置项 │ └── settings.yaml ├── data/ # 原始文档 │ └── docs/ ├── src/ │ ├── loader/ # 文档加载 │ ├── splitter/ # 切片 │ ├── vectorize/ # embedding │ ├── store/ # 向量库和ES操作 │ ├── retrieve/ # 召回与重排 │ └── llm/ # 大模型调用 ├── tests/ # 测试与评估 └── app.py # 主入口目录分离的意义在于后续优化某一环节时不需要改其他地方。比如重排逻辑从 bge-reranker 换成其他模型只改retrieve目录下的代码。3. 从零实现一个最小RAG知识库3.1 文档加载与规范化先用一个简单的本地文档加载示例。这里以 PDF 为例使用 PyMuPDF 提取文本import fitz def load_pdf(path: str) - list[str]: doc fitz.open(path) pages [] for page in doc: pages.append(page.get_text()) doc.close() return pagesPDF 提取文本时要考虑表格和扫描件。PyMuPDF 对文字版 PDF 效果好但扫描件需要 OCR。企业落地时建议先统计文档类型分布大多数是文字版 PDF还是包含大量扫描件、图片、复杂表格然后再选择解析工具。如果直接使用 unstructured 库可以同时处理多种格式from unstructured.partition.auto import partition elements partition(data/docs/员工手册.pdf) text \n.join([e.text for e in elements if e.text])unstructured 的优点是格式覆盖广缺点是解析耗时和依赖体积较大。大批量文档清洗时推荐按文档类型写独立解析逻辑不要一股脑全走通用解析。3.2 切片策略不能只看固定大小切片是 RAG 中最容易被低估的环节。切片太小语义不完整切片太大检索后塞进 Prompt 的噪声多模型注意力被分散。常见切片方式固定长度切片按字符数或 token 数切实现简单但会把语义完整的段落切开。按结构切片根据 Markdown 标题、PDF 章节、换行符切保留语义边界。父子切片父级切片保存完整上下文子级切片用于匹配检索到子片后返回父片给模型。递归字符切分先按段落切再按句号、逗号逐级缩小。LangChain 的递归文本切分器是常用方案from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , , , , ], ) chunks text_splitter.split_text(text)这里的关键参数是chunk_size、chunk_overlap和separators。chunk_size决定切片的 token 上限太小语义不完整太大检索不精准。中文场景下500 到 800 字符左右是一个比较常见的起点但要结合文档类型调整。chunk_overlap让相邻切片保留重叠部分避免一个完整句子刚好被切成两段后两端都不包含句子的完整语义。separators的优先级很重要先按段落切再按句子切尽量不让一个句子被拦腰截断。还要考虑一点切片和后续重排、Prompt 长度必须一起设计。如果切片是 1000 字符TopK 召回 5 个切片Prompt 里就有 5000 字符再加原问题和历史对话很容易超出模型上下文窗口。所以切片大小、召回数量和重排后的保留数量要放在一起权衡。3.3 向量化与入库使用 sentence-transformers 加载 bge-m3 模型进行向量化。bge-m3 支持中文、英文和跨语言检索并且可以同时输出稠密向量和稀疏向量适合做混合检索。from sentence_transformers import SentenceTransformer embedder SentenceTransformer(BAAI/bge-m3) embeddings embedder.encode(chunks, normalize_embeddingsTrue)使用 bge 系列模型时在编码文档和查询时可能要加入对应的指令前缀。不同版本要求不同需要看模型卡说明。比如有些 bge 模型要求查询侧加上“为这个句子生成表示以用于检索相关文章”这样的指令文档侧不加。如果加错侧召回效果会明显下降。Milvus 建集合时需要指定向量维度。bge-m3 默认输出维度是 1024如果使用其他模型维度可能不同。from pymilvus import ( connections, CollectionSchema, FieldSchema, Collection, DataType, ) connections.connect(aliasdefault, hostlocalhost, port19530) fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idTrue), FieldSchema(namedoc_id, dtypeDataType.VARCHAR, max_length128), FieldSchema(namecontent, dtypeDataType.VARCHAR, max_length8000), FieldSchema(namemetadata, dtypeDataType.JSON), FieldSchema(namevector, dtypeDataType.FLOAT_VECTOR, dim1024), ] schema CollectionSchema(fields, descriptionrag_knowledge_base) collection Collection(nameknowledge_base, schemaschema) index_params { index_type: HNSW, metric_type: COSINE, params: {M: 16, efConstruction: 200}, } collection.create_index(field_namevector, index_paramsindex_params)字段设计上有几点要注意doc_id用于关联数据源文档后续做更新删除时按doc_id删除旧切片。metadata用 JSON 类型保存文档标题、部门、标签、上传时间等检索时可以做过滤。content必须存原文因为向量库本身不能反推出准确文本生成阶段需要原文拼 Prompt。写入数据from pymilvus import Collection collection Collection(knowledge_base) collection.insert([ [doc_ids], [contents], [metadatas], [embeddings.tolist()], ]) collection.flush()flush()确保数据落盘。大批量写入时可以分批写每批 1000 到 5000 条根据机器性能调整。3.4 检索生成链路完成索引后查询阶段的核心流程是问题向量化 - 查询向量库 - 组装上下文 - 调用大模型。question 公司年假政策是什么 question_vector embedder.encode([question], normalize_embeddingsTrue)[0].tolist() collection.load() results collection.search( data[question_vector], anns_fieldvector, param{metric_type: COSINE, params: {ef: 64}}, limit5, output_fields[content, doc_id, metadata], )召回结果中的distance在 COSINE 度量下越高表示越相似而使用 L2 时越低越好。不同向量库、不同度量方式排序语义不同写后处理逻辑时要先确认这一点。组装 Promptdef build_prompt(question: str, contexts: list[str]) - str: context_text \n\n.join( f[片段{i1}]\n{text} for i, text in enumerate(contexts) ) return f请根据以下知识库片段回答问题。 如果片段中没有足够信息请直接说明“知识库中没有找到相关内容”不要编造。 知识库片段 {context_text} 问题{question} 回答这里有一个容易被忽略的细节Prompt 中要明确告诉模型“没有答案时不要编造”。RAG 只是给模型提供证据模型仍然可能强行从片段中总结出一个不存在的结论。加限制指令不能完全消除幻觉但能明显降低编造概率。接入大模型时可以使用 OpenAI SDK 的兼容接口也可以本地部署 vLLM 后通过 OpenAI 协议调用from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY, ) response client.chat.completions.create( modelQwen/Qwen2.5-7B-Instruct, messages[ {role: user, content: build_prompt(question, contexts)} ], temperature0.1, ) print(response.choices[0].message.content)temperature在知识库问答场景一般设置较低比如 0.1 到 0.3让模型更稳定、少自由发挥。但也不能完全设为 0某些情况下模型会为了追求确定性而忽略上下文细节。3.5 运行验证要看的不是单一答案跑通最小链路后不要只问一句“你好”或“介绍一下自己”这类和知识库无关的问题。要针对知识库中的具体内容准备多组测试问题并检查三个层面的输出第一检索层是否召回了正确的片段。可以把召回的片段内容打印出来人工判断相关性。第二生成层是否忠实引用了片段。对比回答内容和召回片段看模型是否把片段中的信息表达正确。第三拒绝能力。问一个知识库中完全没有答案的问题看模型是否会坦诚说不知道而不是强行回答。建议先建立一个回归问题集后续每次调整参数都用同一组问题对比才能知道改动是正向还是负向。4. 混合检索与重排优化4.1 为什么仅靠向量检索不够向量检索擅长语义匹配比如用户问“请假流程”知识库中有一篇“员工休假管理办法”即使字面上没有“请假”两个字向量也能捕捉语义关联。但向量检索也有明确短板精确数字和专有名词召回弱。员工问“报销额度 5000 元”向量检索可能返回语义相近但数字不同的其他片段。新词、缩写容易失配。比如内部代号“MMP”在文档里出现少向量表征不稳定。相似文本可能淹没关键片段。如果知识库中有大量模板相似、细节不同的文档向量距离很难区分。这时需要引入关键词检索。BM25 是经典的稀疏检索算法对词频和文档长度有建模能弥补向量检索在精确匹配上的不足。4.2 BM25与向量混合检索混合检索的常见做法是同一份文档同时写入 Milvus 和 Elasticsearch查询时分别做向量召回和关键词召回再把两个结果按加权分数合并。使用 Elasticsearch 做 BM25 检索from elasticsearch import Elasticsearch es Elasticsearch(http://localhost:9200) body { query: { match: { content: question } }, size: 10 } es_result es.search(indexknowledge_base_es, bodybody)合并时要解决分数不可直接相加的问题。向量库返回的是余弦相似度BM25 返回的是词频加权分数两者量纲不同。常见做法是做归一化比如按当前 TopN 结果的最大最小值缩放到 0 到 1再给不同检索方式分配权重。def normalize(scores: list[float]) - list[float]: if not scores: return scores min_score min(scores) max_score max(scores) if max_score min_score: return [1.0 for _ in scores] return [(s - min_score) / (max_score - min_score) for s in scores] vector_weight 0.7 bm25_weight 0.3 final_score vector_weight * normalized_vector bm25_weight * normalized_bm25权重 0.7 和 0.3 只是初始值要通过测试集调优。有些场景下精确关键词更重要BM25 权重可以更高有些场景下用户提问口语化严重向量权重更高。注意混合检索的效果必须用标准问题集验证不要用几个手工选出来的例子决定权重。不同文档集合的统计特性差异很大。4.3 Rerank重排模型接入混合检索召回 20 到 50 个候选片段后直接全部塞入 Prompt 会导致上下文过长。重排模型的作用是把候选片段和用户问题一起输入一个交叉编码器模型输出更精确的相关性分数然后保留 TopK 个片段。交叉编码器重排比双塔向量模型更精准缺点是推理耗时长因为每一对“问题-片段”都要过一次模型。所以重排通常只用于精排少量候选。使用 FlagEmbedding 加载 bge-reranker-v2-m3from FlagEmbedding import FlagReranker reranker FlagReranker(BAAI/bge-reranker-v2-m3, use_fp16True) pairs [[question, text] for text in candidate_texts] scores reranker.compute_score(pairs, normalizeTrue) ranked sorted(zip(candidate_texts, scores), keylambda x: x[1], reverseTrue) top_contexts [text for text, score in ranked[:3]]use_fp16True能降低显存占用但在 CPU 环境可能不支持。normalizeTrue会把分数映射到有界范围便于和阈值比较。重排的最佳实践召回阶段多召回一些比如 20 到 50 个重排阶段只保留 3 到 5 个避免遗漏。重排后的第二个片段和第三个片段得分可能差距不大不要只看分数绝对值要看排序。对强相关文档可以在重排阶段设置一个最低分数阈值低于阈值的片段即使排在前几名也不使用。重排对最终效果的影响通常非常明显。很多项目在未加重排时看起来是“模型答得不对”实际上问题出在检索阶段把无关片段排在了前面导致模型被噪声误导。加上重排后上下文质量提升生成效果自然改善。4.4 Java技术栈下的混合检索与重排非 Python 项目也可以完成 RAG 落地关键在于生态选择。Java 技术栈常见组合是 LangChain4j 加 Milvus配合 HTTP 服务调用重排模型。Maven 依赖示例dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-embeddings-bge-m3/artifactId version0.35.0/version /dependencyLangChain4j 的 MilvusEmbeddingStore可以和其EmbeddingModel、EmbeddingStoreRetriever配合。混合检索时可以同时使用一个关键词搜索引擎和向量检索器然后对结果做合并排序。EmbeddingModel embeddingModel new BgeM3EmbeddingModel(); EmbeddingStoreTextSegment store new MilvusEmbeddingStore( localhost, 19530, knowledge_base, 1024); EmbeddingStoreRetriever vectorRetriever EmbeddingStoreRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(20) .build(); ListTextSegment embeddings vectorRetriever.findRelevant(question);Java 项目中的重排模型通常很难直接用 Java 实现更实用的方式是封装一个 Python 重排服务通过 HTTP 接口暴露给 Java 调用。可以在 Python 侧使用 FastAPI 包装 FlagRerankerJava 侧通过 HTTP 请求获取重排后的片段顺序。这样既保留了重排模型的效果又不必把整个项目改成 Python。5. 效果评估指标与优化路径5.1 检索层指标很多人调 RAG 参数时只看“回答像不像”这不够。检索层需要用离线指标客观衡量常见指标包括指标含义使用场景RecallKTopK 结果中相关文档占全部相关文档的比例判断是否漏召回Hit RateTopK 结果中是否至少有一个相关文档判断系统能否找到答案MRR第一个相关文档排名的倒数平均值判断最相关结果是否靠前NDCGK考虑排名的累计增益评价排序质量忠实度回答是否基于检索片段不包含未支持的细节判断幻觉程度答案相关度回答与问题是否匹配是否解决用户需求评价交互结果离线评估阶段重点先看 RecallK、Hit Rate 和 MRR这三个指标能快速发现检索层问题。重排优化后主要看 MRR 和 NDCG 是否提升。5.2 构造评估数据集离线评估需要一个基准数据集包含三类信息测试问题尽量来自真实用户提问覆盖不同业务场景。相关文档项每个问题对应知识库中哪几个片段或哪篇文档。期望答案要点可以不写完整答案但需要列出必须出现的关键信息。构造方式有两种一是人工标注。让业务人员从知识库中抽选典型问题标记正确答案片段。成本高但质量可靠。二是半自动生成。把文档片段输入大模型让模型生成问题再保存“问题 - 文档片段”的对应关系。这种方式成本低但生成的问题偏简单通常只能作为初筛集。评估脚本可以这样组织questions [ {question: 产假可以休多少天, gold_content_ids: [doc-032]}, {question: 加班补贴标准是什么, gold_content_ids: [doc-017]}, ] def evaluate(questions, retriever): hit_count 0 mrr_sum 0.0 for item in questions: q item[question] hits retriever.retrieve(q, top_k10) for rank, hit in enumerate(hits, start1): if hit.doc_id in item[gold_content_ids]: hit_count 1 mrr_sum 1.0 / rank break hit_rate hit_count / len(questions) mrr mrr_sum / len(questions) return {hit_rate: hit_rate, mrr: mrr}这个脚本只是示例真实项目可以把结果输出到表格或数据库方便对比多个版本。5.3 指标驱动的优化路径拿到指标后按下面的顺序排查先看 Hit Rate。如果 Top10 中完全没有相关文档说明召回层有问题可能原因包括 Embedding 模型选择不合适、切片粒度不对、混合检索权重失衡、元数据过滤条件写错。再看 MRR。如果 Hit Rate 高但 MRR 低说明相关文档存在但排得太靠后。这时候优先加 Rerank其次检查向量距离计算方式是否正确。最后看忠实度和答案相关度。如果检索层已经很准但模型答案仍然差问题可能在 Prompt 设计、模型能力或上下文截断策略上。不同指标对应不同优化手段不能一种方法调到底。5.4 在线评估也要纳入系统设计离线评估不能覆盖所有问题。上线后需要记录用户反馈比如“点赞”“点踩”“复制回答”“重新提问”等行为定期抽取低分问答分析下面的趋势检索到的片段确实不相关属于召回或重排问题。片段相关但答案表述错误属于生成或 Prompt 问题。片段相关且答案正确但格式不满足业务要求属于输出后处理问题。在线反馈是 RAG 系统长期优化的数据基础。6. 工程化落地的核心问题6.1 数据更新与索引同步知识库系统上线后最快遇到的问题就是文档更新。如果每次更新都全量重建索引大文档库会非常耗时。推荐做法是按doc_id做增量更新。更新链路检测到文档变更记录变更的doc_id。删除向量库中该doc_id下全部旧切片。重新解析文档、切片、向量化。写入新切片。同步更新 Elasticsearch 索引。如果不删除旧切片同一个文档的新旧内容会同时被检索到回答可能出现新旧规范并存的情况这是企业内部知识库的严重问题。很多 RAG 框架自带数据更新界面比如 Dify 和 RAGFlow 都支持文件重新上传后重建索引。但如果是自研系统一定要有数据版本概念。建议在元数据中保存version、updated_at字段方便排查“这个问题为什么引用了旧文档”。6.2 权限与安全企业知识库最大的安全风险不是模型被攻击而是检索阶段把用户无权限的文档内容送入大模型。权限控制需要在检索层做而不是在生成层做每个文档片段写入时在metadata中保存允许访问的角色或部门列表。用户提问时系统从用户身份中获取权限标记。Milvus 查询时使用 filter 表达式Elasticsearch 查询时使用租户过滤条件。Milvus 查询带权限过滤的写法from pymilvus import Collection collection Collection(knowledge_base) results collection.search( data[question_vector], anns_fieldvector, param{metric_type: COSINE, params: {ef: 64}}, limit10, exprdepartment in [HR, ALL] and tenant_id company_a, output_fields[content, doc_id], )如果权限过滤条件复杂建议在写入阶段就按权限范围拆分集合或分区避免每次查询都带上复杂的expr。权限和检索性能要一起设计。另外不要把所有公司文档无差别灌入知识库。大模型的上下文窗口只能接收有限片段不相关的敏感文档越多越容易被误检索到。6.3 日志、监控与反馈闭环RAG 系统比常规 CRUD 系统更依赖日志因为一个问题可能经过检索、重排、生成三个阶段任何一个阶段出错都会导致最终答案不可用。每个请求建议记录以下信息请求 ID 和用户标识。原始问题。检索阶段召回的片段 ID、分数、来源文档。重排阶段的排序变化。最终送入模型的上下文片段列表。模型名称、参数、输入输出 token 数。各阶段耗时。异常信息。有了这个日志结构用户反馈“答案不对”时才可以精确定位是检索没找到、排序不对还是模型生成错。监控指标至少包括检索平均耗时。重排平均耗时。大模型接口响应耗时。向量库查询错误率。输入输出 token 数和成本。知识库文档数量和更新频率。这些指标可以按业务线拆分避免个别业务的大文档拖慢整体链路。6.4 部署形态与资源评估RAG 系统的部署形态取决于数据敏感性和预算完全公有云模式模型、向量库都使用云服务适合非敏感场景。混合部署向量库和重排模型部署在内网大模型使用商用 API适合部分敏感场景。全私有化部署Embedding、重排、大模型、向量库全部部署在内部服务器适合金融、政务、研发机密等场景。全私有化时资源评估要有数。一个 7B 参数的生成模型使用 FP16 推理大约需要 14GB 以上显存量化到 INT8 会明显降低显存占用。Embedding 模型和重排模型相对较小但重排模型的 latent 高并发下也需要独立 GPU。向量数据库如果是 Milvus建议单独部署不要和生产应用抢占资源。部署时优先考虑以下顺序先把检索、重排列为大模型之外的高频服务确保性能大模型可以按并发需求横向扩展向量库要注意索引内存占用HNSW 索引在大数据量时内存增长较快。7. 常见问题排查与最佳实践7.1 检索结果明显不相关现象用户问题明确但召回的片段和问题没有关系。按顺序检查确认 Embedding 模型加载是否正确是否在查询侧添加了指令前缀。bge 系列模型对指令敏感加错或不加都会影响效果。检查向量库度量方式。存向量时使用 COSINE查询时也使用 COSINE不要混用。检查切片长度。如果切片过长一个片段包含多个主题向量会被稀释。检查是否有元数据过滤条件。权限过滤条件写错可能把所有结果都过滤掉。测试直接调用同一个 Embedding 模型计算问题和候选片段的相似度如果分数低说明问题不在向量库而在模型或切片。7.2 答案还是幻觉现象检索片段相关但模型回答中出现了片段里没有的信息。处理方式Prompt 中明确要求“只能使用提供的知识库片段回答片段中不存在的信息要说明未知”。降低temperature。把召回的片段编号和标题暴露给模型让模型在回答中引用片段来源降低自由发挥概率。检查送入模型的上下文是否被截断如果 TopK 片段过长后面的片段可能被 truncate模型没看到完整内容。确认重排后是否保留了一些低相关片段低相关片段会引入噪声模型可能从噪声中“学到”错误信息。如果以上都调过仍出现幻觉要考虑生成模型本身能力不足或者问题超出了知识库覆盖范围。7.3 性能慢和资源占用高RAG 链路耗时可大致拆为问题向量化 向量检索 重排 大模型生成。实践中常见瓶颈在重排和大模型生成。重排是串行计算如果问题召回了 50 个片段每个片段都要和问题组成一对跑一次模型50 个片段可能接近一秒。优化思路召回阶段先根据分数截断比如只对 Top20 做重排。重排模型批次处理不要一个 pair 一个 pair 地调用。高并发场景给重排服务单独部署并配置 GPU。对常见高频问题增加缓存相同或相似问题直接返回缓存结果不用重复走全链路。大模型生成耗时只能通过并发、模型量化、选择更小的模型来控制。如果场景对实时性要求高考虑在重排后只保留 3 到 5 个片段减少 prompt 长度降低生成延迟和成本。7.4 常见坑汇总常见坑错误表现推荐做法切片固定长度不设 overlap句子被切断语义丢失使用递归切分并设置 10% 左右 overlap用 query 的 embedding 和文档向量直接算 L2但建索引用 COSINE排序异常统一度量方式只做向量检索不做关键词召回数字、缩写、精确词匹配不到接入 BM25 混合检索召回后不重排直接塞 Prompt检索噪声进入上下文使用交叉编码器重排保留 Top3 到 5更新文档只加不删新旧内容同时被召回按 doc_id 先删后插日志只记录最终答案问题难定位记录每一阶段的输入输出、耗时、分数把权限过滤放到生成后敏感信息已进入模型在检索阶段通过 filter 排除无权限片段权重和参数靠拍脑袋效果波动无法解释构造离线测试集用 Hit Rate 和 MRR 驱动调参7.5 可复用的版本上线检查清单RAG 知识库系统每次更新或上线建议过一遍这个清单[ ] 离线测试集是否覆盖新增业务场景Hit Rate 和 MRR 是否相比旧版本没有回退。[ ] 新文档是否已经按doc_id增量更新旧切片是否清理。[ ] 权限过滤条件是否已经用测试账号验证无权限用户能否检索到该文档片段。[ ] 检索日志是否完整每个问题能查到召回片段、重排结果和最终上下文。[ ] 大模型 API 或本地推理服务的超时和重试策略是否配置。[ ] 高频问题缓存是否开启缓存失效策略是否正确。[ ] 知识库文档变更是否走正式发布流程避免开发环境误连到生产索引。[ ] 重排模型和 Embedding 模型版本是否固定最好记录模型指纹或 hash。[ ] 资源和成本是否评估新增文档量对索引内存和查询耗时的影响是否有监控。[ ] 降级方案是否可用比如向量库异常时是否能切换到关键词检索或直接提示用户。7.6 进阶方向RAG 的下一个阶段通常不是继续调切片或者换一个 Embedding 模型而是把检索链路和业务系统更深入地结合。可以优先考虑这么几个方向一是 Agentic RAG。把单轮“检索-生成”扩展成多步决策让模型根据问题判断是否需要调用知识库、调用哪个知识库、一次检索不够就再检索一次。适合复杂问题拆分、多跳问答场景。二是结构化数据接入。RAG 不只处理文档企业内部大量数据在 MySQL、PostgreSQL、CRM、ERP 中。把关系数据库中的数据加工成模型能理解的形式比如表格摘要、字段说明、SQL 生成结果再和文档检索结合问答覆盖面会大很多。三是可观测性完善。在框架之上自建链路追踪和版本对比系统让每一次参数调整都能在评估数据上体现。对于刚接触 RAG 的开发者最重要的练习不是追求复杂的框架而是亲手把最小链路跑通再把测试集和日志补上。有了可以量化的评估和可以追溯的日志后续每一个优化动作才有依据。
返回列表