尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI Agent知识获取管道设计:RAG混合检索与重排序实战

AI Agent知识获取管道设计:RAG混合检索与重排序实战 1. 为什么知识获取管道是 AI Agent 的分水岭做 AI Agent 开发的人绕不开一个尴尬的现实模型本身很聪明但它不知道你公司上个月刚改的报销制度不知道你手里那份三百页的设备维护手册写了什么更不知道你昨天刚跟客户敲定的那版报价细节。大模型的参数里装的是公开世界的通用知识而真正让 Agent 产生业务价值的恰恰是那些散落在文档、数据库、工单系统里的私有知识。知识获取管道要解决的就是把这部分知识在正确的时机、以正确的形式喂给模型。RAG也就是检索增强生成是目前这条管道上最主流也最务实的方案。它的核心思路不复杂用户提问时先从知识库里把最相关的片段捞出来再把问题和这些片段一起交给大模型让模型基于给定材料作答。听起来像是给模型开卷考试但真正落地时会发现从文档切分、向量化、索引构建到检索排序每一步都有大量细节决定最终效果。我见过太多项目Demo 阶段效果惊艳一上真实数据就原形毕露答非所问、胡编乱造、检索不到关键信息问题几乎都出在管道设计上。这篇内容适合正在搭建 AI Agent 知识库的开发者、需要给现有产品接入私有知识的工程师以及想搞清楚 RAG 到底怎么回事的技术负责人。我会从整体设计思路讲到具体实操把稠密嵌入、稀疏嵌入这些概念用大白话拆开把检索命中率这个最让人头疼的指标掰扯清楚也会分享一些踩过的坑和排查技巧。读完你至少能明白一条靠谱的知识获取管道长什么样每个环节该做什么选择以及为什么有些 RAG 项目注定会失败。2. 知识获取管道的整体设计与方案选型2.1 从需求倒推管道架构动手写代码之前先想清楚你的 Agent 到底要回答什么问题。这个看似废话的步骤实际上决定了后面所有技术选型。我习惯把知识获取需求分成三类第一类是精确查找比如XX 型号设备的额定电压是多少答案就在某个文档的某一行要求检索必须准第二类是综合理解比如我们产品的退换货政策在哪些情况下不适用答案分散在多个段落甚至多个文档里要求检索覆盖面广第三类是开放对话比如帮我分析一下这份季度报告的重点需要模型对整篇文档有全局把握。这三类需求对应的管道设计完全不同。精确查找适合用稀疏嵌入加关键词匹配综合理解需要稠密嵌入做语义召回开放对话则要考虑长上下文窗口或者分层摘要。很多团队一上来就套用某个开源 RAG 框架的默认配置结果发现效果怎么调都不对根本原因就是没搞清楚自己的需求属于哪一类。我的建议是先拿二十个真实问题做标注看看标准答案在原文里的分布形态再决定管道怎么搭。2.2 稠密嵌入与稀疏嵌入的分工逻辑稠密嵌入和稀疏嵌入这两个词听起来唬人其实用生活化的方式很好理解。稠密嵌入像是把每段文字压缩成一组坐标比如用 768 个数字表示一段话的语义位置语义相近的段落坐标就接近。它的优势是能捕捉同义替换和语义关联你搜怎么退款它能找到写着申请退货流程的段落。但稠密嵌入有个毛病对专有名词、型号、编号这类精确信息不敏感因为训练时这些低频词被平滑掉了。稀疏嵌入则是另一套逻辑它更像传统的关键词匹配升级版。每个词对应一个维度一段文字里出现了哪些词、出现多少次就形成一个大维度但大部分为零的向量。BM25 就是稀疏检索的经典代表。它的优势是精确你搜XJ-2000它绝不会给你返回XJ-3000的段落。缺点是不懂语义电脑和计算机在它眼里是两个完全不同的词。实际生产环境里我几乎不会只用一种。主流做法是混合检索稠密和稀疏各跑一遍然后用倒数排名融合或者加权分数把结果合并。这样既能召回语义相关的内容又不会漏掉精确匹配的关键信息。有些团队还会加一路基于元数据的过滤比如按文档类型、时间范围先筛一遍再在子集里做向量检索这对知识库规模大的场景特别有效。2.3 分块策略被低估的关键决策文档切分是 RAG 管道里最容易被忽视、却对效果影响最大的环节。我见过太多项目直接用固定长度切分比如每 500 个字符一刀切结果把一句话拦腰截断或者把表格拆得七零八落。模型拿到这种残缺的片段要么理解不了要么基于错误信息作答。合理的分块策略要结合文档结构来定。对于 Markdown 或 HTML 文档优先按标题层级切分每个小节作为一个块块内再按段落细分。对于 PDF 里的表格最好单独提取成结构化数据不要跟正文混在一起切。对于代码文档按函数或类来切分比按行数切分合理得多。块的大小也有讲究太小则信息不完整太大则检索精度下降且浪费上下文窗口。我的经验值是中文 300 到 500 字英文 200 到 400 词同时设置 10% 到 20% 的重叠区域避免关键信息正好落在切割线上。还有一个进阶技巧是给每个块加上下文头。比如在块的开头附上它所属的章节标题和文档标题这样即使块本身没提到主题检索时也能通过上下文头匹配到。这个改动成本很低但对召回率的提升立竿见影。2.4 向量数据库选型别被参数迷惑向量数据库这两年卷得厉害各种产品都在拼 QPS、拼召回率、拼分布式能力。但落到实际项目里选型的核心考量其实就几条数据规模、查询延迟要求、运维成本、以及跟现有技术栈的契合度。数据量在百万级以下用 FAISS 或者 pgvector 这类轻量方案完全够用部署简单跟 PostgreSQL 集成也方便。千万级以上再考虑 Milvus、Qdrant 这类专业向量库。如果团队已经在用 Elasticsearch它的向量检索能力也足够应付大多数场景还能顺便复用现有的运维体系。我特别想提醒一点不要为了追求所谓的最新技术而引入一个团队没人会维护的组件向量数据库挂了导致整个 Agent 不可用这种事故在真实项目里并不少见。索引类型的选择同样重要。HNSW 索引查询快、召回率高但内存占用大IVF 索引省内存但需要训练且召回率略低。对于知识库这种读多写少的场景HNSW 通常是更好的选择多花点内存换稳定性和速度是值得的。3. 核心细节解析与实操要点3.1 文档解析脏数据是万恶之源知识获取管道的第一道关卡是文档解析也是最多坑的地方。PDF 里的双栏排版、扫描件里的 OCR 错误、Word 文档里的隐藏批注、Excel 里的合并单元格每一项都能让后续环节崩溃。我的原则是宁可解析慢一点也要保证输出干净。对于 PDF优先用能保留版面结构的解析器比如 PyMuPDF 或者 pdfplumber它们能识别出标题、段落、表格的边界。扫描件必须走 OCR但 OCR 结果一定要人工抽检特别是数字和专有名词识别错误会直接导致检索到错误信息。对于 Word 和 Markdown直接用对应的解析库提取纯文本和结构信息即可。HTML 文档要小心导航栏、页脚这些噪音内容最好用正文提取算法先过滤一遍。解析完之后建议加一个清洗步骤去掉多余空白、统一标点符号、修正明显的 OCR 错误、把全角半角统一。这些看似琐碎的操作对后续检索的稳定性帮助很大。3.2 嵌入模型选择中文场景要特别小心嵌入模型决定了稠密检索的上限。选模型时不能只看排行榜上的分数要结合你的语言和领域来评估。中文场景下很多英文榜单上表现优异的模型直接拿来用效果会打折扣因为中文的分词、语义表达跟英文差异很大。我的做法是先圈定几个候选模型然后用自己业务里的真实问题构造一个小型评测集人工标注哪些段落是相关的再对比各模型的召回率。这个过程大概花半天时间但能避免选错模型后返工的巨大成本。对于垂直领域比如医疗、法律、工业通用嵌入模型往往不够用这时候可以考虑用领域数据做微调或者直接用支持领域适配的模型。还有一个容易被忽略的点是嵌入维度。维度越高表达能力越强但存储和计算成本也越高。768 维和 1024 维在实际效果上的差距往往没有宣传的那么大但存储成本可能差一倍。对于知识库规模大的场景我倾向于选维度适中的模型把省下来的资源用在混合检索和重排序上整体收益更高。3.3 检索环节的参数调优检索环节有几个关键参数需要反复调试。第一个是返回结果数量 top_k。返回太少可能漏掉关键信息返回太多则会把噪音塞进上下文干扰模型判断。我的经验是先用 10 到 20 做初筛再用重排序模型精选出 3 到 5 个最相关的块送给大模型。第二个是相似度阈值。低于某个分数的结果直接丢弃避免把不相关的内容硬塞给模型。这个阈值需要根据你的嵌入模型和数据类型来定没有通用值。我通常会在评测集上画一条召回率与准确率的曲线找到平衡点。第三个是混合检索的权重。稠密和稀疏各占多少比例直接影响最终结果。对于专有名词多的场景稀疏权重要调高对于口语化提问多的场景稠密权重要调高。这个权重也可以做成动态的根据查询里是否包含数字、型号等特征来调整。3.4 重排序小投入大回报重排序是检索管道里性价比最高的环节。它的思路是先用向量检索快速召回一批候选再用一个更精细的交叉编码器模型对候选逐一打分重新排序。交叉编码器会把查询和文档拼在一起输入模型能捕捉到更细粒度的语义关联效果通常比单纯的向量相似度好很多。重排序模型的推理速度比嵌入模型慢所以只适合用在候选集上不能对整个知识库做。一般召回 50 到 100 个候选重排序后取前 5 个这个流程在延迟和效果之间取得了很好的平衡。开源的重排序模型有不少选择中文场景下建议选在中文语料上训练过的版本。3.5 上下文组装别让模型看废话检索出来的块怎么拼进提示词也有讲究。我见过直接把所有块拼接在一起的做法结果模型被无关信息干扰回答质量反而下降。合理的做法是按相关性排序把最相关的放前面每个块加上来源标注方便模型引用块之间用清晰的分隔符隔开总长度控制在模型上下文窗口的合理比例内留出空间给系统提示和用户问题。如果检索到的块之间有重叠内容要去重后再拼接。如果块的内容明显不完整比如一句话被截断要么补全要么丢弃。这些细节处理好了模型的回答准确率会有肉眼可见的提升。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把基础环境搭起来。Python 版本建议 3.10 以上避免一些库的兼容性问题。核心依赖包括文档解析库、嵌入模型库、向量数据库客户端和重排序库。下面是一个典型的依赖清单pip install pymupdf pdfplumber python-docx beautifulsoup4 pip install sentence-transformers FlagEmbedding pip install qdrant-client pip install rank-bm25 jieba如果你用的是 LangChain 或 LlamaIndex 这类框架它们已经封装了大部分流程但我的建议是核心环节自己实现一遍这样出问题时才知道去哪里排查。框架能加速开发但也会隐藏细节而 RAG 的效果恰恰藏在细节里。4.2 文档解析与分块实现假设我们有一批 PDF 和 Markdown 文档先写一个统一的解析入口。PDF 用 PyMuPDF 提取文本和版面信息Markdown 直接按标题层级解析。解析后的内容统一成带元数据的块结构元数据包括来源文件、章节标题、页码等。import fitz from langchain.text_splitter import RecursiveCharacterTextSplitter def parse_pdf(path): doc fitz.open(path) blocks [] for page_num, page in enumerate(doc): text page.get_text(text) blocks.append({ text: text, source: path, page: page_num 1 }) return blocks def chunk_blocks(blocks, chunk_size400, overlap60): splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapoverlap, separators[\n\n, \n, 。, , , , , ] ) chunks [] for block in blocks: for piece in splitter.split_text(block[text]): chunks.append({ text: piece, source: block[source], page: block[page] }) return chunks这里的分隔符顺序很关键优先按段落切再按句子切最后才按字符切。中文标点要单独列出来否则切分器不认识中文句号会把整段当成一句话。4.3 嵌入生成与索引构建嵌入生成这一步我建议批量处理并加缓存。同一段文本不要重复计算嵌入既浪费算力又浪费时间。用 SentenceTransformers 加载模型后把文本分批编码每批 32 到 64 条根据显存调整。from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-large-zh-v1.5) def embed_texts(texts, batch_size32): embeddings model.encode( texts, batch_sizebatch_size, normalize_embeddingsTrue, show_progress_barTrue ) return embeddings注意normalize_embeddingsTrue这个参数它把向量归一化到单位长度这样余弦相似度就可以用点积快速计算检索速度会快不少。索引构建时把向量和元数据一起写入向量数据库元数据用于后续的过滤和溯源。4.4 混合检索的完整实现混合检索的核心是把稠密和稀疏两路结果融合。稀疏检索用 BM25需要先对语料做分词和统计。中文分词用 jieba把每个块切成词列表构建 BM25 索引。import jieba from rank_bm25 import BM25Okapi tokenized_corpus [list(jieba.cut(chunk[text])) for chunk in chunks] bm25 BM25Okapi(tokenized_corpus) def sparse_search(query, top_k20): tokenized_query list(jieba.cut(query)) scores bm25.get_scores(tokenized_query) top_indices sorted(range(len(scores)), keylambda i: scores[i], reverseTrue)[:top_k] return [(chunks[i], scores[i]) for i in top_indices]稠密检索直接查向量数据库拿到相似度最高的若干块。然后做融合我常用的是倒数排名融合它对分数尺度不敏感实现简单且效果稳定。def rrf_fusion(dense_results, sparse_results, k60): scores {} for rank, (chunk, _) in enumerate(dense_results): key chunk[text][:50] scores[key] scores.get(key, 0) 1 / (k rank 1) for rank, (chunk, _) in enumerate(sparse_results): key chunk[text][:50] scores[key] scores.get(key, 0) 1 / (k rank 1) sorted_keys sorted(scores, keyscores.get, reverseTrue) return sorted_keys4.5 重排序与最终上下文组装融合后的候选集交给重排序模型精排。用 FlagEmbedding 里的重排序模型把查询和每个候选块拼成对模型输出相关性分数。from FlagEmbedding import FlagReranker reranker FlagReranker(BAAI/bge-reranker-large, use_fp16True) def rerank(query, candidates, top_n5): pairs [[query, c[text]] for c in candidates] scores reranker.compute_score(pairs, normalizeTrue) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) return [c for c, _ in ranked[:top_n]]最后把精选出的块组装成提示词。每个块前面加上来源标注块之间用分隔线隔开整体控制在 2000 到 3000 字以内。提示词里明确告诉模型只基于给定材料回答材料里没有的信息不要编造回答时标注引用来源。4.6 效果评测与迭代管道搭好之后必须有一套评测机制来验证效果。我通常准备 50 到 100 个真实问题每个问题标注标准答案和对应的原文出处。评测指标包括检索命中率也就是标准答案所在的块是否被召回回答准确率模型回答是否与标准答案一致引用准确率模型标注的来源是否真的支持它的回答。这三个指标里检索命中率是基础。命中率上不去后面再怎么优化提示词都是白搭。我一般会先盯着命中率调把分块、嵌入、检索参数都调到一个满意的水平再去看回答质量。评测集要定期更新把线上发现的新问题加进去形成闭环。5. 常见问题与排查技巧实录5.1 检索命中率低的排查路径检索命中率低是最常见的问题排查要按管道顺序来。先看分块是否合理把没命中的问题对应的原文找出来看看它被切成了什么样。如果关键信息被切散了调整分块大小和重叠。如果分块没问题再看嵌入模型是否适合你的领域拿几个典型问题手动算一下查询和原文块的相似度看看分数是否合理。如果嵌入没问题检查检索参数。top_k 是不是太小相似度阈值是不是设得太高混合检索的权重是不是偏了。我遇到过一个案例团队把稀疏检索的权重设得很低结果所有包含型号的查询都召回不到正确文档调高权重后问题立刻解决。最后才考虑重排序模型是否合适有些重排序模型对中文支持不好反而会把正确结果排下去。5.2 模型胡编乱造的抑制方法模型基于检索结果胡编乱造通常有三个原因检索到的内容本身不相关模型只能靠自己的知识补检索到的内容相关但不完整模型自行脑补了缺失部分提示词没有明确约束模型自由发挥。对应的解法第一提高检索质量宁可不答也不要答错第二在提示词里明确要求如果材料不足以回答问题直接说根据现有资料无法回答第三要求模型逐句标注引用来源这样即使它想编也会因为找不到来源而收敛。我还会在提示词里加一句不要使用材料之外的任何知识实测对抑制幻觉有明显效果。5.3 性能瓶颈的定位与优化RAG 管道的延迟主要花在三个地方嵌入计算、向量检索、大模型生成。嵌入计算如果每次查询都实时做延迟会很高可以考虑用缓存或者更小的模型。向量检索的延迟跟索引类型和数据量有关HNSW 索引在百万级数据上通常能控制在几十毫秒。大模型生成是最大头如果用的是 API延迟取决于服务商如果是本地部署要考虑显存和批处理。优化思路上我一般先做查询缓存相同或相似的问题直接返回缓存结果。然后做嵌入缓存热门文档的嵌入预先算好。再考虑把重排序模型换成更轻量的版本或者只对前 20 个候选做重排序。最后才是换更快的向量数据库或大模型。优化要有优先级先做收益大成本低的。5.4 常见问题速查表问题现象可能原因排查方向解决建议检索不到关键信息分块不合理或嵌入模型不匹配检查原文切分形态和相似度分数调整分块参数更换领域适配的嵌入模型返回大量无关内容top_k 过大或阈值过低查看召回结果的相似度分布降低 top_k提高相似度阈值专有名词检索失败稀疏检索权重过低检查混合检索权重配置调高稀疏权重或加元数据过滤模型回答与材料不符提示词约束不足检查提示词是否明确要求基于材料加强约束要求标注引用响应延迟高嵌入实时计算或重排序过重分段计时定位瓶颈加缓存换轻量重排序模型多文档综合问题答不全召回数量不足检查 top_k 和重排序后保留数增加召回数量优化融合策略5.5 几个踩过的坑第一个坑是过度依赖框架默认配置。很多 RAG 框架的默认分块大小和嵌入模型是针对英文通用场景调的直接用在中文垂直领域效果很差。我的建议是框架可以用但关键参数一定要自己调评测集一定要自己建。第二个坑是忽略元数据。元数据不只是用来溯源的它还能大幅提升检索精度。比如用户问2024 年的政策如果每个块都带了年份元数据检索时先按年份过滤命中率会高很多。我现在的习惯是解析文档时尽可能多地提取元数据标题、章节、日期、作者、文档类型能提的都提。第三个坑是不做评测就上线。RAG 的效果很依赖数据同一个管道在不同数据集上表现可能天差地别。没有评测集你根本不知道改动是变好了还是变坏了。我见过团队凭感觉调了一周参数结果发现还不如初始配置。评测集是 RAG 项目的生命线越早建越好。第四个坑是忽视查询改写。用户的提问往往很口语化跟文档里的书面表达差距很大。在检索前加一步查询改写把口语化问题转成更适合检索的形式或者生成多个查询变体分别检索再融合对召回率提升很明显。这一步可以用小模型来做成本不高但收益可观。6. 从基础 RAG 到 Agentic RAG 的演进思路基础 RAG 跑通之后你会发现它有几个天然局限只能做单轮检索不能根据中间结果决定下一步查什么只能查一个知识库不能跨数据源不能对检索结果做推理和验证。这些局限正是 Agentic RAG 要解决的。Agentic RAG 的思路是把检索变成 Agent 的一个工具让 Agent 自己决定什么时候检索、检索什么、检索几次。比如用户问一个复杂问题Agent 可以先拆解成子问题分别检索再综合结果如果发现检索结果矛盾可以再查一次做验证如果知识库里没有可以调用其他工具去查数据库或 API。实现上这需要给 Agent 配备规划能力和工具调用能力。规划能力让 Agent 知道先做什么后做什么工具调用能力让它能实际执行检索。LangChain 和 LlamaIndex 都提供了 Agent 相关的抽象但我的建议是先把基础 RAG 的每个环节都吃透再往上叠 Agent 能力。基础不牢Agent 只会把问题放大。还有一个方向是 GraphRAG把知识库构建成图结构实体和关系作为节点和边检索时沿着图遍历。这对需要多跳推理的问题特别有效比如跟 A 公司有合作关系的供应商里哪些也跟 B 公司有业务往来。但 GraphRAG 的构建成本高需要实体抽取和关系抽取适合知识结构清晰、关系复杂的场景。普通文档问答用基础 RAG 加混合检索就够了不必为了追新而过度设计。我在实际项目里的体会是RAG 的效果提升没有银弹每一个百分点的提升都来自对细节的打磨。分块策略、嵌入模型、检索参数、重排序、提示词每个环节优化一点累积起来就是质的飞跃。与其追逐最新的框架和论文不如把基础管道的每个环节都做到扎实把评测集建好把排查流程理顺。这套基本功打好了后面无论技术怎么演进你都能快速跟上。
返回列表