尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AI Agent知识获取管道:稠密嵌入与稀疏嵌入的混合检索实战

发布时间:2026/9/29 18:59:45

资讯中心
01
ARTICLE

AI Agent知识获取管道:稠密嵌入与稀疏嵌入的混合检索实战

AI Agent知识获取管道:稠密嵌入与稀疏嵌入的混合检索实战
1. 为什么知识获取管道是 AI Agent 落地的第一道坎做 AI Agent 开发的人绕不开一个尴尬的现实模型本身很聪明但它对你私有的业务知识一无所知。你问它公司内部的报销流程它只能编一个看起来合理的答案你让它查上周刚更新的产品参数它给出的数字大概率是幻觉。这个问题不解决Agent 就只能停留在演示阶段永远进不了生产环境。知识获取管道要干的事情说白了就是在模型和你的私有数据之间架一座桥。用户提问的时候Agent 先去知识库里把相关的内容捞出来连同问题一起塞给模型模型基于这些真实材料来回答。这套机制就是RAGRetrieval-Augmented Generation检索增强生成。我在实际项目里踩过的坑告诉我RAG 看起来简单——不就是查资料再回答吗——但真正做好管道里每一个环节都有讲究。这篇文章面向的是正在搭建 AI Agent、准备接入私有知识库的开发者。不管你是用 LangChain、Spring AI 还是 AgentScope底层管道的设计思路是相通的。我会把知识获取管道从文档入库到检索召回的完整链路拆开讲重点放在稠密嵌入和稀疏嵌入这两个核心概念上因为它们是决定检索质量的关键也是很多人一知半解的地方。读完你应该能自己搭一条可用的管道并且知道出了问题该从哪里排查。2. 知识获取管道的整体架构与设计取舍2.1 一条完整管道包含哪些环节很多人以为 RAG 就是向量数据库 大模型这个理解太粗糙了。一条能用的知识获取管道至少包含五个环节每个环节都会影响最终效果文档加载把 PDF、Word、网页、数据库记录等各种来源的数据读进来文本切分把长文档切成合适大小的片段chunk这是最容易被低估的一步嵌入生成把每个片段转成向量也就是稠密嵌入和稀疏嵌入的生成过程向量存储把向量和原文一起存进向量数据库建立索引检索召回用户提问时把问题也转成向量去库里找最相似的片段这五个环节串起来才叫管道。我见过太多人只关注用哪个向量数据库结果切分策略一塌糊涂检索出来的内容驴唇不对马嘴然后怪模型不行。管道的短板效应非常明显任何一个环节拉胯整体效果就上不去。2.2 为什么检索环节要区分稠密和稀疏这是本文的核心值得单独说清楚。传统的关键词搜索比如 Elasticsearch 的 BM25靠的是词频统计——你搜报销流程它就找包含报销和流程这两个词的文档。这种方式叫稀疏嵌入sparse embedding因为它的向量维度等于词表大小绝大多数位置是 0只有出现的词对应的位置才有值整体非常稀疏。稀疏嵌入的优点是精确匹配强你搜专有名词、产品型号、错误码它一找一个准。但它的缺点也明显不理解语义。你搜怎么请假它可能找不到写着休假申请流程的文档因为字面上没有重叠。稠密嵌入dense embedding则完全不同。它用神经网络把文本映射成一个固定长度的稠密向量比如 768 维或 1536 维每个维度都有值。这个向量捕捉的是语义信息语义相近的文本向量距离就近。所以搜怎么请假能召回休假申请流程因为它俩在语义空间里挨得近。那是不是稠密嵌入就够了不是。稠密嵌入对专有名词、精确匹配反而不敏感。你搜一个具体的订单号ORD-20240315-8871稠密嵌入可能给你召回一堆语义相似但订单号完全不对的内容。所以生产环境里混合检索hybrid search才是主流做法——稠密和稀疏各取所长把两路结果融合排序。2.3 方案选型从零搭建还是用现成框架这里有个现实问题是自己写管道还是用 LangChain、LlamaIndex、Spring AI 这类框架我的建议是分阶段。学习和验证阶段直接用框架。LangChain 的 RAG 流程封装得很完整几十行代码就能跑通一条管道快速看到效果。Spring AI 对 Java 技术栈的团队很友好如果你本来就在用 Spring Cloud 做微服务接入 RAG 的成本很低。但到了生产阶段我倾向于把关键环节自己控制。原因很简单框架的默认切分策略、默认嵌入模型、默认检索参数几乎不可能刚好适配你的业务。你需要能替换嵌入模型、调整切分规则、自定义重排序逻辑。框架应该当脚手架用而不是当黑盒用。提示不要一上来就追求 Agentic RAG、GraphRAG 这些进阶方案。基础管道跑不通、检索命中率上不去的时候上再复杂的架构都是空中楼阁。先把稠密加稀疏的混合检索做扎实。3. 稠密嵌入与稀疏嵌入的核心细节解析3.1 稠密嵌入是怎么工作的稠密嵌入的背后是一个编码器模型通常是 BERT 类的 Transformer。它把一段文本一般限制在 512 个 token 以内编码成一个固定维度的向量。这个过程你可以理解成模型读完整段话把它压缩成一组数字这组数字代表了这段话的语义坐标。选嵌入模型有几个关键考量。第一是语言支持中文场景必须选中文或多语言模型用纯英文模型处理中文效果会断崖式下跌。第二是维度维度越高表达能力越强但存储和计算成本也越高。第三是最大输入长度如果你的 chunk 切得比较长模型截断了后面的内容就白切了。实际选型时我一般会先看模型在中文检索基准上的表现然后拿自己的业务数据做小规模测试。别迷信排行榜你的数据分布和公开数据集差别可能很大。测试方法很简单准备几十个真实问题每个问题标注好应该召回哪几个片段然后看不同模型的召回率。生成稠密嵌入的代码以常见的调用方式为例from sentence_transformers import SentenceTransformer model SentenceTransformer(your-embedding-model) chunks [片段一的内容, 片段二的内容] embeddings model.encode(chunks, normalize_embeddingsTrue)注意normalize_embeddingsTrue这个参数。归一化之后向量长度都是 1计算余弦相似度就等价于计算点积检索时更快。这是个容易被忽略的优化点。3.2 稀疏嵌入不只是关键词匹配很多人把稀疏嵌入等同于 BM25其实不完全对。BM25 是一种基于统计的稀疏表示方法而现代稀疏嵌入还有基于学习的方法比如 SPLADE。它用神经网络来预测每个词的重要性权重能实现一定程度的语义扩展——比如把请假和休假关联起来同时保持稀疏表示的高效性。BM25 的核心思想是一个词在当前文档里出现得越多它对这个文档越重要但这个词在整个语料库里越常见它的区分度就越低权重应该降低。这个逻辑很符合直觉。你搜流程这个词几乎每个文档都有它就没啥区分度你搜ORD-8871只有一两个文档有那它就能精准定位。稀疏嵌入的存储和检索传统上用倒排索引。倒排索引就是词到文档的映射表词 A 出现在文档 1、3、5 里词 B 出现在文档 2、3 里。检索时把查询词对应的文档列表取交集或并集再算相关性分数。这种结构检索速度极快而且天然支持精确匹配。3.3 两种嵌入的对比与融合策略把两者的特性摆在一起看差异一目了然维度稠密嵌入稀疏嵌入表示方式固定维度稠密向量词表维度稀疏向量语义理解强能捕捉同义改写弱偏字面匹配精确匹配弱专有名词易失准强型号错误码精准存储成本较高每段一个向量较低倒排索引紧凑检索速度依赖近似最近邻算法倒排索引极快典型代表各类语义编码模型BM25、SPLADE融合策略上最常用的是倒数排名融合RRF。它不关心两路检索的具体分数只看排名。某文档在稠密检索里排第 3在稀疏检索里排第 5那它的融合分数就是 1/(603) 1/(605)其中 60 是个平滑常数。把所有文档按融合分数重排取前 K 个。RRF 的好处是鲁棒不需要对两路分数做归一化——因为稠密和稀疏的分数根本不在一个量纲上直接加权求和很容易出问题。我试过直接加权调参调到怀疑人生换成 RRF 之后省心很多。注意融合之后通常还要加一个重排序rerank环节。用一个交叉编码器模型把查询和每个候选片段拼在一起打分精度比向量相似度高不少代价是慢。所以一般是先召回几十个再重排取前几个兼顾速度和精度。4. 从零搭建知识获取管道的实操过程4.1 文档加载与文本切分的关键参数切分是整条管道里最需要动脑子的一步。切得太碎一个完整的语义单元被拆散检索出来的是残缺信息切得太长一个 chunk 里混了好几个主题向量表示被稀释检索精度下降。我的经验参数是这样的中文文本chunk 大小控制在 300 到 500 字重叠overlap设置 50 到 100 字。重叠的作用是防止关键信息刚好卡在切分边界上被切断。比如一句话前半段在 chunk A 结尾后半段在 chunk B 开头有了重叠两个 chunk 都能包含完整信息。但固定长度切分是下策。更好的做法是按语义边界切分优先在段落、标题、句子结束符处切。LangChain 里有 RecursiveCharacterTextSplitter它会按分隔符优先级递归尝试先按段落切段落还太长就按句子切以此类推。这个策略比硬切好很多。对于结构化文档比如带标题层级的 Markdown 或 HTML我强烈建议把标题路径带上。比如一个 chunk 来自第三章 3.2 节 报销标准那这个 chunk 的文本前面应该加上这个路径。这样检索时标题里的关键词也能参与匹配而且模型看到路径能更好理解上下文。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, , , , , ], ) chunks splitter.split_text(document)注意分隔符列表里中文标点的位置。很多默认配置只考虑英文标点中文文档切出来效果很差。这个细节坑过我不止一次。4.2 生成并存储双路嵌入切分完成后对每个 chunk 同时生成稠密和稀疏两种表示。稠密嵌入用编码模型生成稀疏嵌入可以用 BM25 直接算也可以用 SPLADE 这类模型生成。存储方案上如果追求简单可以用支持混合检索的向量数据库比如 Milvus、Qdrant、Weaviate它们内置了稀疏向量字段和混合检索接口。如果团队已经有 Elasticsearch那用 ES 做稀疏检索、配一个向量库做稠密检索也是常见组合。以 Qdrant 为例它支持命名向量可以同时存稠密和稀疏from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, SparseVector client QdrantClient(urlhttp://localhost:6333) points [] for i, chunk in enumerate(chunks): dense_vec dense_model.encode(chunk) sparse_vec compute_sparse_vector(chunk) # 返回 indices 和 values points.append(PointStruct( idi, vector{ dense: dense_vec.tolist(), sparse: SparseVector( indicessparse_vec[indices], valuessparse_vec[values], ), }, payload{text: chunk, source: doc_001}, )) client.upsert(collection_nameknowledge_base, pointspoints)payload 里一定要存原文和来源信息。原文用于最后拼给模型来源用于溯源和调试。我见过有人只存向量不存原文检索出来还得回数据库查白白增加复杂度。4.3 检索召回与结果融合的实现检索阶段把用户问题同样生成稠密和稀疏两种表示分别去库里查然后融合。def hybrid_search(query, top_k10): dense_query dense_model.encode(query).tolist() sparse_query compute_sparse_vector(query) dense_results client.search( collection_nameknowledge_base, query_vector(dense, dense_query), limittop_k * 2, ) sparse_results client.search( collection_nameknowledge_base, query_vector(sparse, SparseVector(**sparse_query)), limittop_k * 2, ) # RRF 融合 scores {} for rank, hit in enumerate(dense_results): scores[hit.id] scores.get(hit.id, 0) 1 / (60 rank) for rank, hit in enumerate(sparse_results): scores[hit.id] scores.get(hit.id, 0) 1 / (60 rank) ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_k]这里每路各召回 2 倍 top_k是为了给融合留出空间。如果两路各只召回 10 个融合后可能凑不满 10 个不重复的结果。多召回一些融合后再截断效果更稳。4.4 把检索结果喂给模型最后一步是把召回的片段拼成提示词连同用户问题一起发给模型。这里有个技巧给每个片段编号并要求模型在回答时标注引用了哪个片段。这样既方便溯源也能抑制幻觉——模型知道自己在引用具体材料编造的概率会降低。提示词模板大致是这样请基于以下参考资料回答问题。如果资料中没有相关信息请明确说明资料中未提及不要编造。 参考资料 [1] {片段一} [2] {片段二} [3] {片段三} 问题{用户问题}资料中未提及这句话很重要。不加这句模型遇到知识库覆盖不到的问题时会倾向于用自己训练时的知识硬答用户根本分不清哪些是真实检索结果、哪些是模型脑补。5. 检索效果调优与常见问题排查5.1 检索命中率上不去的排查思路检索效果差原因可能出在管道任何一环。我一般按这个顺序排查先看切分。把检索出来的 chunk 打印出来读一遍如果发现语义不完整、关键信息被切断那就是切分问题。调整 chunk 大小和重叠或者换成语义切分。再看嵌入模型。如果切分没问题但语义相近的问题就是召不回正确片段可能是嵌入模型不适合你的领域。拿业务数据测几个候选模型选召回率最高的。然后看检索策略。如果精确匹配的词型号、编号召不回说明稀疏检索没起作用检查稀疏向量是否正确生成和存储。如果语义问题召不回检查稠密检索的相似度阈值是不是设太高了。最后看融合和重排。如果两路单独看都能召回融合后反而丢了那是融合参数的问题。RRF 的平滑常数可以调重排序模型的输入长度也要注意别截断。5.2 常见问题速查表现象可能原因排查方向召回内容语义不相关嵌入模型不适配领域换模型用业务数据测召回率专有名词召不回稀疏检索缺失或权重低检查稀疏向量生成提高稀疏路权重关键信息被切断chunk 太小或重叠不足增大 chunk 和 overlap改语义切分检索结果重复度高重叠设置过大适当减小 overlap模型回答编造内容提示词未约束或召回为空加未提及约束检查召回数量检索速度慢候选集太大重排模型太重减小召回数重排模型换轻量版中文切分效果差分隔符未含中文标点补充中文标点分隔符5.3 几个容易踩的坑第一个坑是忽略元数据过滤。生产环境里知识库往往有权限、时效、分类等维度。用户只能看自己有权限的文档检索时必须带上过滤条件。如果先检索再过滤可能把有权限的结果挤掉了。正确做法是在向量检索时就带上 filter 参数。第二个坑是chunk 里丢失上下文。一个片段写着该标准适用于上述情况但上述情况在上一段。这种指代在切分后就成了悬空引用。解决办法是在 chunk 前拼接所属章节的标题路径或者用滑动窗口保留前后文。第三个坑是从不评估。很多人搭完管道就上线从不量化检索质量。至少要维护一个测试集记录每次调整后的召回率和准确率。没有度量调优就是瞎猜。我一般会准备 50 到 100 个真实问题标注好标准答案片段每次改动都跑一遍。第四个坑是嵌入模型和检索模型不一致。入库时用模型 A 生成向量检索时用模型 B 生成查询向量两个模型的向量空间根本不兼容检索结果全是乱的。这个错误听起来低级但换模型时忘记重新入库真的会发生。6. 从基础管道到进阶方案的演进路径基础管道跑通、效果稳定之后可以考虑几个进阶方向。第一个是重排序前面提过用交叉编码器对召回结果精排通常能带来明显的精度提升代价是延迟增加。第二个是查询改写用户的问题往往口语化、有歧义先用模型把问题改写成更适合检索的形式再去做检索。第三个是多路召回除了稠密和稀疏还可以加基于知识图谱的召回这就是 GraphRAG 的思路。但我要泼一盆冷水这些进阶方案都有额外的复杂度和成本。基础管道的召回率如果只有 60%加上重排序可能到 70%但离能用还差得远。这时候该做的是回头检查切分和嵌入模型而不是堆砌高级组件。我见过太多项目架构图画得花里胡哨实际效果还不如一个调好参数的 BM25。真正决定 RAG 效果上限的是知识库本身的质量和切分策略。文档写得清晰、结构规整、切分合理基础管道就能有不错的表现。反过来源文档一团乱麻上什么高级方案都救不回来。所以我的建议始终是把功夫花在数据治理和管道基础环节上那才是投入产出比最高的地方。这套管道我在几个项目里反复打磨过最大的体会是RAG 没有银弹只有对每个环节的耐心调试。稠密嵌入管语义稀疏嵌入管精确两者融合加上合理的切分和提示词约束就能覆盖大部分企业知识问答场景。剩下的就是拿真实数据不断迭代了。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。