☰
向量数据库与图数据库混合检索架构实战:语义召回与关系推理融合
2026/10/11 19:33:15 网站建设 项目流程

1. 为什么要把向量数据库和图数据库放在一起用

1.1 从一个真实需求说起

去年下半年我接手了一个内部知识库的改造项目,需求方给的原话是"让系统能听懂人话,还能顺着线索自己找答案"。听起来像两个需求,实际上是一件事:既要语义层面的模糊匹配,又要关系层面的精确推理。

举个具体例子。用户问"某个型号的设备在高温环境下出现异常振动,可能和哪些部件有关"。这个问题里有两层信息:第一层是"高温""异常振动"这些描述,它们和知识库里的"热应力""共振频率偏移"在字面上完全不重合,必须靠语义相似度去捞;第二层是"哪些部件有关",这需要沿着设备-部件-工况-故障模式的关系链路去走,是典型的图遍历问题。

只用向量数据库,第一层能解决,第二层就抓瞎——它返回的是一堆语义相近的文本片段,但片段之间的因果关系、层级关系、时序关系全丢了。只用图数据库,第二层很漂亮,第一层又不行——图查询依赖精确的实体名和关系类型,用户随口一句"高温下抖得厉害",根本映射不到图里的节点。

所以这套方案的核心思路就一句话:向量库负责"找得到",图库负责"理得清",大模型负责"串起来"。三者各干各最擅长的事,通过一个编排层粘合。

1.2 三个组件各自的角色定位

我把这套架构里的分工拆得比较清楚,避免后期维护时职责混乱:

组件核心职责不负责什么
向量数据库语义召回、模糊匹配、多模态特征检索不做多跳推理、不维护实体关系
图数据库关系存储、多跳遍历、路径推理、约束校验不做语义相似度计算
大模型意图解析、查询改写、结果融合、自然语言生成不做精确的图计算、不承担召回主责

这个分工不是拍脑袋定的。我试过让大模型直接读图数据库的 schema 然后生成查询语句,在小规模图上还行,一旦关系类型超过几十种、节点属性上百个,模型生成的查询准确率断崖式下跌。也试过把图数据全部拍平成文本塞进向量库,结果就是关系信息被稀释,多跳查询基本失效。

提示:不要试图让任何一个组件"全能"。向量库做关系推理、图库做语义匹配、大模型做精确计算,这三件事都是反模式的,踩过一次就知道疼。

1.3 这套架构适合什么场景

不是所有知识检索都需要上这套组合拳。我总结了几条判断标准,满足两条以上才值得投入:

  • 知识之间存在显式的关系结构,比如设备层级、组织架构、工艺流程、法律条款引用
  • 用户查询既有模糊描述又有精确约束,比如"类似XX的案例中涉及哪些供应商"
  • 需要多跳推理才能得到答案,单跳检索覆盖不了
  • 知识会持续更新,且更新时关系需要同步维护

如果只是简单的文档问答,向量库加大模型就够了,硬上图库是过度设计。反过来,如果只是固定几类关系查询,图库加规则引擎也能跑,不必上向量。这套方案的价值区间在"语义+关系"双重不确定性的场景。

2. 核心细节拆解:数据怎么组织、查询怎么走

2.1 数据双写:向量和图谱如何保持一致

这是整个项目最容易出问题的地方。同一份知识,既要进向量库做 embedding,又要进图库建节点和边,两边怎么同步?

我的做法是以图库为主数据源,向量库为派生索引。具体流程是:原始知识先经过抽取管线,产出结构化的三元组(头实体、关系、尾实体)和实体属性,写入图库;然后从图库的实体和关系文本中生成 embedding,写入向量库,并在向量库的元数据里存图库的节点 ID。

这样做的好处是:图库是"真相源",向量库随时可以从图库重建。如果 embedding 模型升级了,只需要重新跑一遍向量化,图结构不动。反过来如果以向量库为主,图关系一旦需要调整,向量库里的元数据就得跟着改,很容易出现不一致。

# 伪代码:双写管线的核心逻辑 def ingest_knowledge(raw_doc): # 第一步:抽取三元组和实体 triples, entities = extractor.extract(raw_doc) # 第二步:写入图库,拿到节点ID node_ids = graph_store.upsert(entities, triples) # 第三步:为每个实体生成embedding,带上图库ID for entity in entities: vec = embed_model.encode(entity.text + entity.context) vector_store.upsert( id=entity.id, vector=vec, metadata={"graph_node_id": node_ids[entity.id], "type": entity.type} )

这里有个细节值得说:实体文本的 embedding 不能只编码实体名。比如"轴承"这个词,单独编码和"高速旋转工况下的轴承"编码,语义向量差别很大。我的做法是把实体名、实体类型、以及它在图里的直接邻居关系拼成一段描述文本再编码,这样向量里天然携带了一部分关系信息,召回时更准。

2.2 查询路由:什么时候走向量,什么时候走图

用户输入一句话进来,系统得先判断该走哪条路。我一开始想用大模型做意图分类,后来发现延迟太高,每次查询多几百毫秒。现在改成规则+轻量分类器的组合:

  • 如果查询里包含明确的实体名或实体类型词,优先走图库做实体链接
  • 如果查询是纯描述性的、没有可识别的实体锚点,先走向量库召回候选实体
  • 如果两者都有,走混合路径:向量召回候选,再用图库做关系过滤

实际跑下来,大概 60% 的查询走混合路径,25% 纯向量,15% 纯图。这个分布和业务场景强相关,不同项目差别会很大,建议上线后埋点统计,动态调整路由阈值。

2.3 混合检索的融合策略

向量召回和图召回各自返回一批结果后,怎么合并排序?我试过三种方案:

方案一:加权分数融合。把向量相似度和图路径得分归一化后加权求和。简单,但权重很难调,不同查询类型最优权重不一样。

方案二:级联过滤。先用向量召回 Top-K,再用图关系做过滤。召回率高但精度受限于向量质量。

方案三:RRF(倒数排名融合)。不依赖分数绝对值,只看排名。这是我现在用的方案,鲁棒性最好。

RRF 的公式很简单:每个文档的最终得分等于它在各召回列表中的排名倒数之和。比如一个实体在向量召回里排第 3,在图召回里排第 1,得分就是 1/3 + 1/1 = 1.33。这个方案的好处是不需要归一化,也不用调权重,对异构召回源的融合特别友好。

注意:RRF 里的常数 k 一般取 60,这是原论文的经验值。我试过调到 10 和 100,效果都不如 60 稳。如果你的召回列表很短(少于 20 条),k 可以适当调小。

3. 实操过程:从零搭一套可跑的检索推理链路

3.1 环境准备与组件选型

向量库我选的是支持元数据过滤的方案,因为混合检索时需要按实体类型、时间范围做前置过滤。图库选的是支持属性图模型的,因为业务里的实体属性比较丰富,纯 RDF 三元组表达起来别扭。

大模型这块,意图解析和查询改写用中等规模的指令微调模型就够了,最终答案生成用大一点的模型。我实测下来,查询改写这个环节用小模型反而比大模型稳,因为大模型容易"过度发挥",把用户的原始意图改偏。

# 依赖安装(示意,具体包名按实际选型替换) pip install vector-client graph-client llm-sdk

3.2 图谱 Schema 设计

Schema 设计是图库这边最关键的一步。我的原则是:实体类型宁少勿多,关系类型宁精勿滥。一开始我设计了 30 多种实体类型,后来合并到 12 种,查询和维护都轻松很多。

核心实体类型包括:设备、部件、工况、故障模式、处置措施、文档。核心关系包括:包含(设备-部件)、发生于(故障-工况)、导致(部件-故障)、处置(故障-措施)、引用(文档-实体)。

{ "entity_types": ["设备", "部件", "工况", "故障模式", "处置措施", "文档"], "relation_types": [ {"name": "包含", "from": "设备", "to": "部件"}, {"name": "发生于", "from": "故障模式", "to": "工况"}, {"name": "导致", "from": "部件", "to": "故障模式"}, {"name": "处置", "from": "故障模式", "to": "处置措施"}, {"name": "引用", "from": "文档", "to": "*"} ] }

Schema 定好后,抽取管线的 prompt 里要把这个 schema 作为约束传进去,让模型只抽这几类实体和关系。不约束的话,模型会抽出各种五花八门的类型,后期图谱会变成一团乱麻。

3.3 查询编排的完整流程

一次完整的查询请求,经过这几个阶段:

第一阶段:意图解析。大模型把用户输入拆成三部分:语义查询串、实体锚点、关系约束。比如"高温下振动的设备可能是什么部件坏了",解析结果是语义串"高温环境振动异常",实体锚点"设备",关系约束"部件-导致-故障"。

第二阶段:并行召回。语义串走向量库召回候选实体,实体锚点走向量库做实体链接,同时图库根据锚点做一跳邻居扩展。

第三阶段:图推理。把向量召回的候选实体作为起点,在图库中做 2-3 跳遍历,找出满足关系约束的路径。这里要设跳数上限,不然在大图上会爆炸。我的经验是 3 跳是性价比拐点,超过 3 跳的路径噪声太大。

第四阶段:结果融合与生成。用 RRF 融合各路结果,取 Top-N 路径和实体,连同原始问题一起喂给大模型生成答案。prompt 里要明确要求模型"只基于给定路径作答,路径之外的信息不要编"。

def hybrid_query(user_input): # 1. 意图解析 parsed = llm.parse_intent(user_input, schema=GRAPH_SCHEMA) # 2. 并行召回 vec_candidates = vector_store.search(parsed.semantic_query, top_k=50) graph_neighbors = graph_store.expand(parsed.entity_anchors, hops=1) # 3. 图推理 paths = graph_store.traverse( start_nodes=[c.graph_node_id for c in vec_candidates], relation_filter=parsed.relation_constraint, max_hops=3 ) # 4. 融合排序 fused = rrf_fuse([vec_candidates, graph_neighbors, paths]) # 5. 生成答案 answer = llm.generate(user_input, context=fused[:10]) return answer

3.4 参数选择的实测记录

几个关键参数我做了对比测试,记录如下:

参数测试值最终选择理由
向量召回 Top-K20/50/1005020 漏召回,100 引入太多噪声,50 是拐点
图遍历最大跳数2/3/434 跳的路径人工评估相关性低于 30%
RRF 常数 k10/60/10060原论文经验值,实测最稳
融合后取 Top-N5/10/20105 信息不足,20 超出模型上下文性价比

这些值不是通用的,不同数据规模下要重新调。但调参的思路可以复用:先定召回率下限,再压精度,最后看生成质量。

4. 踩过的坑与排查技巧

4.1 实体链接失败导致图查询空转

最常见的故障是:用户查询里的实体名和图中的实体名对不上。比如用户说"主轴承",图里存的是"主轴轴承组件"。字面匹配失败,实体链接就断了,后面的图遍历全部空转。

我的解法是双通道实体链接:字面匹配走一路,向量相似度匹配走一路,两路结果取并集。向量匹配时用实体描述文本的 embedding,而不是实体名本身,这样"主轴承"和"主轴轴承组件"的向量距离会比较近。

实操心得:实体链接的阈值不要设太高。我一开始设 0.85,召回率惨不忍睹。后来降到 0.7,配合后续的图约束过滤,整体准确率反而上升了。因为图结构本身就是一个强约束,链接错一两个候选,图遍历会自然把它们过滤掉。

4.2 图遍历的路径爆炸

在关系密集的图上,3 跳遍历可能产生几万条路径。我遇到过一次查询返回了 4 万多条路径,直接把下游的融合排序拖垮。

解决办法有三个,我组合使用:

  • 关系类型白名单:只遍历 schema 里定义的核心关系,忽略辅助关系
  • 路径长度惩罚:越长的路径得分越低,在融合阶段自然沉底
  • 中间节点度数限制:如果某个中间节点的度数超过阈值(比如 500),跳过该节点,避免经过超级节点

超级节点是图数据库的经典问题。一个"文档"节点可能连接了几万个实体,任何经过它的路径都会爆炸。我的做法是在 schema 层面就把这类节点标记为"不可作为中间节点",只允许作为起点或终点。

4.3 大模型"幻觉"引用不存在的路径

生成阶段最怕模型编造路径。我试过在 prompt 里写"不要编造",效果有限。后来改成结构化引用:给每条路径编号,要求模型在答案里用 [P1][P2] 的形式标注来源,然后后处理校验这些编号是否真实存在。

这个改动之后,幻觉率从大概 15% 降到了 3% 以下。剩下的 3% 主要是模型把路径内容理解错了,而不是凭空编造,性质没那么严重。

4.4 常见问题速查表

现象可能原因排查方向
图查询返回空实体链接失败检查实体名匹配和向量相似度阈值
查询延迟高路径爆炸或向量库索引未优化看遍历路径数、检查向量索引类型
答案答非所问意图解析偏差对比解析结果和原始查询
召回结果重复双写不一致校验图库和向量库的节点 ID 映射
新知识检索不到索引未更新检查双写管线的触发机制

4.5 增量更新的处理

知识库不可能一次建完,增量更新是常态。我的做法是图库实时写,向量库准实时写。图库的写入是同步的,保证关系立即可查;向量库的 embedding 生成有延迟,走异步队列,一般秒级到分钟级完成。

这里有个坑:如果向量库更新滞后,用户刚录入的知识可能搜不到。我的缓解方案是在向量库更新完成前,对新增实体做一个短期的"字面匹配兜底",等 embedding 写入后再切换到正常路径。

5. 效果评估与持续优化

5.1 怎么衡量这套系统好不好

单看召回率或准确率都不够,因为这是多阶段管线,每个阶段的误差会累积。我建了一套分层评估指标:

  • 实体链接准确率:人工标注 200 条查询,看链接对的比例
  • 路径相关性:对返回的路径做人工打分,1-5 分
  • 答案忠实度:答案是否只基于给定路径,有无编造
  • 端到端满意度:业务方对最终答案的采纳率

这四个指标里,我最看重答案忠实度。因为前三个再高,如果模型编造内容,业务方就不敢用。忠实度上不去,整套系统的信任就建立不起来。

5.2 持续优化的几个方向

跑了大半年,我总结了几个投入产出比最高的优化点:

第一,扩充实体别名表。这是最土但最有效的办法。把业务里常见的口语化说法、缩写、错别字都收进别名表,实体链接准确率能提升十几个百分点。

第二,优化抽取 prompt。抽取质量直接决定图谱质量。我每两周会抽样检查抽取结果,发现错误就补充到 prompt 的 few-shot 示例里。这个工作很枯燥,但回报很直接。

第三,调整融合权重。RRF 虽然鲁棒,但在特定查询类型下,适当加权能提升效果。比如纯关系查询,图召回的权重可以调高。

第四,缓存高频查询。知识库场景下,查询重复率其实不低。对高频查询做结果缓存,能显著降低延迟和成本。

5.3 一个容易被忽视的点:可解释性

这套系统相比纯向量方案,最大的隐性优势是可解释。向量检索返回的是一堆相似度分数,业务方看不懂为什么返回这些。而图路径是可视化的:A 部件导致 B 故障,B 故障发生于 C 工况,这条链路一目了然。

我在前端把推理路径画成了图,业务方能看到系统"怎么想的"。这个功能上线后,业务方的信任度明显提升,反馈问题的质量也高了——他们会说"这条路径不对,应该是另一条",而不是笼统地说"结果不准"。

6. 一些个人体会

这套架构跑下来,我最大的感受是:技术选型的难点不在单个组件,而在组件之间的边界划分。向量库和图库各自都很成熟,但把它们粘在一起,边界在哪、谁负责什么、数据怎么流转,这些才是真正花时间的地方。

另一个体会是,不要追求一步到位。我一开始想做一个全自动的抽取-建图-检索-推理闭环,结果每个环节都不够稳。后来改成半自动:抽取结果先人工审核一批,图谱质量稳定后再逐步放开。慢是慢了点,但系统能真正用起来,比一个跑不通的"全自动"强得多。

最后说个具体的:如果你的团队刚开始做这类项目,建议先用一个小而完整的场景跑通全链路,哪怕只有几百个实体。全链路的体感,比在单个组件上抠细节重要得多。链路跑通了,再逐步扩规模、加关系、调参数,心里才有底。

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

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

立即咨询