1. 为什么要把向量数据库和图数据库放在一起用
1.1 从一个真实需求说起
去年下半年我接手了一个内部知识库的改造项目,需求方给的原话是"想让系统像人一样理解资料之间的关系"。这句话听起来很虚,但拆开看其实很具体:他们有一批技术文档、会议纪要、项目复盘材料,希望检索的时候不只是关键词匹配,而是能理解语义;同时希望系统能回答"这个方案影响了哪些模块""谁在什么背景下提出了这个改动"这类需要顺着关系链条走的问题。
一开始我图省事,直接上了一套向量检索方案,把文档切片、做嵌入、存进向量库,查询的时候做相似度匹配。效果在"找相似内容"这个场景下确实不错,但很快就撞墙了。用户问"和A方案相关的所有决策记录",向量检索返回的是一堆语义相近的片段,但它不知道A方案和某个决策之间是"被采纳""被否决"还是"被替代"的关系,更没法顺着"决策→参与人→其他决策"这条链走下去。
这就是问题的核心:向量数据库擅长的是"像不像",图数据库擅长的是"连没连"。前者解决语义模糊匹配,后者解决实体之间的显式关系推理。单用任何一个,都会在另一类问题上瘸腿。后来我把两套东西拼到一起,用大模型做中间的调度和生成层,才把这个需求真正落地。这篇就把整个实践过程拆开讲清楚。
1.2 两类数据库的能力边界到底在哪
先把概念说透,不然后面的选型没法聊。
向量数据库,本质是存高维向量的,通过近似最近邻算法(比如HNSW、IVF)快速找到和查询向量距离最近的若干条记录。它的输入是一段文本经过嵌入模型转成的向量,输出是语义上最接近的若干片段。它的强项是模糊语义匹配,弱项是精确关系表达——你没法在向量库里表达"张三属于A团队,A团队负责B项目"这种结构化事实。
图数据库,存的是节点和边,节点代表实体,边代表关系,边上还能挂属性。它的强项是多跳关系查询,比如"找出所有和某方案间接相关且时间在某个区间内的决策",用图查询语言几行就能表达。它的弱项是语义模糊性——你没法用图查询表达"找和这段话意思相近的内容",因为图里存的是离散的实体和关系,不是连续语义空间。
大模型在这里扮演的角色,是把用户的自然语言问题翻译成对这两类数据库的调用,再把两边返回的结果融合成一段人话。三者协同,才构成一个完整的"检索+推理"闭环。
1.3 协同架构的整体设计思路
我最终采用的架构分四层,从下往上说。
最底层是存储层,向量库和图库并存。向量库存文档片段的嵌入向量和原文,图库存实体、关系以及实体到文档片段的映射。这里有个关键设计:同一个实体在两边的ID必须能对上,否则融合的时候会断链。
往上一层是索引与抽取层,负责把原始文档处理成两边都能用的数据。这一步用大模型做实体识别和关系抽取,把非结构化文本转成三元组喂给图库,同时把文本切片喂给向量库。
再往上是检索调度层,接收用户问题,判断该走向量检索、图检索还是两者都走,然后并行执行、合并结果。
最顶层是生成层,把检索到的片段和关系路径一起塞进大模型的上下文,生成最终回答。
这个分层的好处是每一层职责单一,出问题好定位。我踩过的坑基本都集中在抽取层和调度层,后面会细说。
2. 核心组件选型与数据建模细节
2.1 向量库和图库的选型考量
选型这块我不打算给具体产品名,因为不同团队的技术栈和运维能力差别太大,给死了反而误导。我说说选型的判断维度。
向量库看几个点:索引类型(HNSW召回快但内存吃得多,IVF省内存但需要训练)、是否支持标量过滤(很多场景要"语义相似且时间在X之后",纯向量库做不了)、写入吞吐(知识库更新频繁的话这点很关键)、分布式能力(数据量上亿之后单机扛不住)。
图库看几个点:查询语言表达力(多跳、路径、聚合是否顺手)、是否支持属性图(边上挂属性对知识图谱很重要)、写入性能(实体抽取是批量写入,慢的话整个流水线会堵)、和现有生态的集成度。
我的经验是,中小规模(千万级向量、百万级节点)优先选运维简单的,别一上来就搞分布式集群,复杂度会把迭代速度拖死。等数据量真的上来了再迁移,迁移成本远低于前期过度设计的维护成本。
2.2 实体与关系的建模方法
这是整个项目里最费脑子的部分。建模建得好,后面查询顺风顺水;建得烂,图库就是一堆查不动的垃圾数据。
我的做法是先定实体类型,再定关系类型,最后定属性。实体类型不要贪多,我一开始定义了十几种,后来发现很多类型边界模糊,抽取的时候模型老是分不清。收敛到五六个核心类型之后,准确率明显上来了。
关系类型同理,宁可少而准,不要多而乱。我最终保留的关系类型控制在十种以内,每种都有明确的语义定义和抽取示例。这里有个技巧:给每种关系写三到五个正例和反例,作为抽取时的few-shot提示,能显著降低误抽率。
属性方面,时间属性一定要有。知识库里的信息很多是有时效性的,没有时间维度,后面做"某时间点之前的状态"这类查询就抓瞎。
2.3 向量与图数据的映射对齐
这是协同的关键,也是最容易出问题的地方。
我的方案是给每个文档片段分配一个全局唯一的chunk_id,给每个实体分配一个全局唯一的entity_id。在向量库里,每条记录存chunk_id、原文、向量,以及这段文本里出现的entity_id列表。在图库里,每个实体节点存entity_id和实体名,同时存一个source_chunk_ids属性,记录这个实体是从哪些片段抽出来的。
这样两边就通过entity_id和chunk_id双向打通了。查询的时候,向量检索返回chunk_id,可以顺着拿到相关实体;图检索返回entity_id,可以顺着拿到原始文本片段。这个双向映射是整个协同架构的地基,建的时候一定要保证一致性,我见过因为ID对不上导致检索结果残缺的案例,排查起来非常痛苦。
提示:映射关系建议单独存一张表或者一个轻量级存储里,不要只依赖两个库各自的字段,方便做一致性校验和修复。
3. 从原始文档到双库数据的完整流水线
3.1 文档预处理与切片策略
原始文档五花八门,PDF、Word、Markdown、网页都有。第一步是统一转成纯文本,这一步用现成的解析库就行,但要注意表格和列表的处理,很多解析器会把表格拍平成一坨,丢失结构信息。我的做法是表格单独抽出来,转成结构化的键值对,作为独立片段处理。
切片策略直接影响检索质量。我试过固定长度切片、按段落切片、按语义切片三种。固定长度最简单但会切断语义;按段落切分保留了自然边界但长度不均;语义切片效果最好但需要额外模型调用,成本高。
最终我用的是混合策略:先按标题层级切大块,大块超过阈值再按段落切,段落还超就按句子切,同时设置重叠窗口(我用的重叠比例是15%左右)。重叠是为了避免关键信息正好落在切分点上被割裂。这个比例不是拍脑袋定的,我做过对比实验,10%以下容易漏,20%以上冗余太多影响检索精度,15%是个比较平衡的点。
3.2 用大模型做实体关系抽取
抽取这一步我用的是"提示工程+结构化输出"的方案。给大模型一段文本,要求它输出符合预定义schema的JSON,包含实体列表和关系列表。
提示词的设计有几个要点。第一,schema要写死在提示里,包括实体类型、关系类型、每种类型的定义和示例。第二,要求模型输出置信度,低置信度的抽取结果可以人工复核或者直接丢弃。第三,要求模型标注证据片段,也就是这个实体或关系是从哪句话抽出来的,方便溯源。
抽取的准确率不可能100%,我的实测是在规范文档上能到85%左右,在口语化的会议纪要上会掉到70%以下。所以一定要有后处理:对实体做归一化(同义词合并、大小写统一),对关系做冲突检测(同一对实体出现矛盾关系时标记出来)。
这里有个省钱的技巧:不是所有片段都值得抽取。可以先做一轮粗筛,只对包含潜在实体信号的片段调用抽取模型,能省下不少token成本。
3.3 双库写入与一致性保障
抽取完之后,向量库和图库要分别写入。
向量库写入相对简单,把片段文本过一遍嵌入模型,拿到向量,连同chunk_id、原文、实体列表一起写进去。注意嵌入模型要和查询时用的保持一致,换模型意味着所有向量都要重算,这个坑我踩过,血的教训。
图库写入复杂一些,因为要处理实体去重和关系合并。同一个实体在不同片段里可能表述不同,需要先做实体链接,把它们归并到同一个entity_id。关系合并的时候,如果同一对实体之间有重复关系,可以累加权重或者保留多条带不同来源的记录。
一致性保障方面,我加了一个校验任务,定期扫描两个库,检查entity_id和chunk_id的映射是否完整、是否有孤儿记录。发现问题就触发修复。这个任务看起来多余,但实际运行中确实抓到过几次写入失败导致的映射缺失。
4. 检索调度与关联推理的实现
4.1 查询意图的识别与路由
用户的问题进来,第一步是判断该走哪条路。
我把它分成三类:纯语义查询("找和X相似的资料")、纯关系查询("X和Y是什么关系")、混合查询("找出所有和X相关且涉及Y的决策")。前两类分别走向量库和图库,第三类两边都走。
意图识别我用的是小模型分类加规则兜底。小模型负责粗分类,规则负责处理一些明显的模式,比如问题里出现"关系""关联""影响"这类词就倾向图检索,出现"相似""类似""相关内容"就倾向向量检索。规则兜底很重要,因为小模型在边界case上不稳定,规则能兜住大部分明显情况。
路由错了的代价很大,走向量库的关系查询基本返回一堆无关片段,所以我在路由层加了一个置信度阈值,低于阈值的时候两条路都走,宁可多查不可漏查。
4.2 向量检索与图检索的并行执行
确定路由之后,两条检索并行跑。
向量检索这边,查询文本过嵌入模型拿向量,然后做近似最近邻搜索,返回top-k片段。k值我一般设10到20,太少召回不够,太多噪声大。如果支持标量过滤,会把时间、类型等条件带上。
图检索这边,把自然语言问题转成图查询语句。这一步我用的是大模型生成查询模板再填充参数的方式,而不是让模型直接生成查询语句,因为直接生成容易出语法错误和注入风险。模板覆盖常见的几类查询模式,模型只负责填实体名和条件。
并行执行用异步任务,两边都设超时,避免一边卡住拖垮整个请求。超时的那边返回空结果,另一边正常返回,生成层会说明"部分信息未能获取"。
4.3 结果融合与关联路径推理
两边结果回来之后要融合。融合不是简单拼接,而是按实体对齐。
具体做法是:向量检索返回的片段里带着entity_id列表,图检索返回的路径里也带着entity_id。以entity_id为键做join,把同一个实体相关的片段和关系聚到一起。这样生成层拿到的就是"某个实体的原文描述+它在图里的关系网络",信息是完整的。
关联路径推理是这套架构的亮点。图检索可以返回多跳路径,比如"A方案→被B决策采纳→B决策由C提出→C还提出了D决策"。这条路径本身就是推理结果,生成层把它翻译成人话,就能回答"这个方案的影响链条"这类问题。多跳的深度要控制,我一般限制在3跳以内,再深的话路径爆炸,而且相关性会急剧下降。
5. 大模型在协同架构中的三重角色
5.1 作为抽取器:非结构化到结构化
前面提过,大模型在索引阶段负责实体关系抽取。这里补充几个实操细节。
批量处理比单条处理效率高得多。我一开始一条一条调,速度慢成本高。后来改成批量,一次塞5到10个片段,让模型分别输出,吞吐量上去了,成本也降了。但批量不能太大,超过模型的上下文窗口或者让模型"串味"(把不同片段的信息混在一起)就得不偿失。
输出格式要强约束。我用的是JSON schema校验,模型输出不符合格式就重试,重试两次还不行就丢弃。这个机制能过滤掉大部分格式错误。
抽取结果要留痕。每条实体和关系都记录来源片段和置信度,方便后续人工审核和问题追溯。
5.2 作为调度器:自然语言到查询语句
查询阶段,大模型负责把用户问题翻译成对两类数据库的操作。
这里的关键是给模型足够的上下文。我会把图库的schema(有哪些实体类型、关系类型)和向量库的字段说明一起塞进提示,让模型知道有哪些"工具"可用。然后要求模型输出一个结构化的查询计划,包含走哪条路、用什么参数。
模型有时候会"自作主张"生成不存在的查询模式,所以我在后面加了一层校验,检查生成的查询计划是否符合预定义的模式,不符合就回退到默认策略。
5.3 作为生成器:检索结果到自然语言
最后一步是把检索结果生成回答。
提示词里我会明确要求:只基于检索到的内容回答,不要编造。检索结果里包含原文片段和关系路径,模型要做的就是把它们组织成连贯的回答,并标注信息来源。
引用来源很重要,一方面是可信度,另一方面方便用户核查。我要求模型在回答里用[来源:chunk_id]的格式标注,前端渲染的时候可以做成可点击的链接。
如果检索结果不足以回答问题,模型要明确说"根据现有资料无法回答",而不是硬编。这个约束能大幅降低幻觉。
6. 实操中踩过的坑与排查技巧
6.1 抽取质量不稳定的排查思路
抽取质量波动是最常见的问题。我的排查顺序是:先看输入文本质量,再看提示词,最后看模型。
输入文本如果本身格式混乱、错别字多,抽取质量肯定差。这种情况先做文本清洗。提示词的问题通常是schema定义不清或者示例不够,补充示例往往能解决。模型的问题相对少见,但如果换了模型版本后质量下降,要考虑回滚。
我整理了一个速查表:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 实体漏抽严重 | 提示词未覆盖该类型 | 补充类型定义和示例 |
| 关系方向搞反 | 关系定义有歧义 | 明确方向语义,加反例 |
| 同一实体多个ID | 未做实体链接 | 加归一化步骤 |
| 抽取结果格式错 | 输出约束不够 | 加schema校验和重试 |
| 特定文档质量差 | 文本本身问题 | 先做文本清洗 |
6.2 检索结果不相关的调优方法
检索不相关,先分清楚是向量检索的问题还是图检索的问题。
向量检索不相关,通常是嵌入模型不适合当前领域。通用嵌入模型在专业领域上表现会打折,可以考虑用领域数据微调,或者换一个在该领域表现更好的模型。另一个原因是切片粒度不对,切片太大语义被稀释,太小信息不完整。
图检索不相关,通常是查询模板覆盖不够或者图数据本身稀疏。前者补充模板,后者要回头检查抽取环节是不是漏了太多关系。
6.3 双库数据不一致的修复经验
数据不一致的表现是:向量库里有某个片段,但图库里找不到对应的实体;或者反过来。
我的修复流程是:先跑一致性校验脚本,定位不一致的记录;然后判断是写入失败还是抽取失败;写入失败就重写,抽取失败就重新抽取。修复的时候要注意幂等,避免重复写入造成新的不一致。
预防方面,我在写入流程里加了事务性保障:两个库的写入要么都成功,要么都回滚。虽然实现起来麻烦点,但比事后修复省心得多。
7. 性能优化与成本控制的实战经验
7.1 检索延迟的优化手段
延迟主要花在三个地方:嵌入计算、向量检索、图查询。
嵌入计算可以缓存,相同文本的向量算一次存起来。向量检索的延迟主要看索引类型和参数,调小搜索范围能降延迟但会牺牲召回,需要权衡。图查询的延迟看查询复杂度,多跳查询要控制跳数,必要时加索引。
我实测下来,一个中等规模的知识库(百万级片段、十万级节点),端到端延迟能控制在两秒以内,其中大模型生成占了大头。如果对延迟敏感,可以考虑用更小的生成模型或者流式输出。
7.2 大模型调用成本的压缩策略
成本主要花在抽取和生成两个环节。
抽取环节,批量处理和粗筛是两个有效的降本手段。生成环节,控制上下文长度很关键,检索结果不要一股脑全塞进去,按相关性排序取top-n。
还有一个技巧是缓存常见问题的回答。知识库里很多问题是重复的,缓存命中能省下大量调用。
7.3 增量更新与全量重建的取舍
知识库是活的,文档会不断更新。全量重建成本高但一致性好,增量更新成本低但容易积累不一致。
我的策略是增量为主、定期全量。日常更新走增量,每周或每月做一次全量重建,把增量过程中积累的碎片整理掉。全量重建安排在低峰期,避免影响线上服务。
增量更新的时候要特别注意删除和修改的处理。文档删了,对应的向量和图数据也要删;文档改了,旧数据要失效。这块我一开始没处理好,导致检索返回已删除的内容,后来加了软删除标记才解决。
8. 这套架构适合什么场景,不适合什么场景
8.1 高价值适用场景
企业知识管理是这套架构最典型的应用。文档多、关系复杂、需要语义检索和关系推理,三个条件都满足。
技术文档问答也很合适。API文档、架构说明、故障复盘之间有关联关系,用户的问题往往需要跨文档推理。
合规与风控审查场景下,需要顺着实体关系追查影响范围,图检索的价值特别明显。
8.2 需要谨慎评估的场景
纯关键词检索能满足的场景,上这套架构是杀鸡用牛刀,成本和复杂度都不划算。
数据量极小(几千条以内)的场景,用简单的方案就行,没必要引入两套数据库。
对延迟极度敏感(毫秒级)的场景,大模型生成这一环会成为瓶颈,需要重新评估。
8.3 后续可扩展的方向
一个方向是多模态扩展,把图片、表格里的信息也纳入进来,向量库支持多模态嵌入,图库支持多模态实体。
另一个方向是时序推理,给关系加上时间维度,支持"某时间点的状态"和"状态变化过程"的查询。
还有一个方向是主动学习,把用户的反馈(哪些回答有用、哪些没用)收集起来,反哺抽取和检索的优化,形成闭环。
我在实际项目里最先做的是时序推理这一块,因为业务方对"什么时候发生了什么变化"这类问题需求很强烈。实现上就是在关系边上加时间戳,查询的时候带上时间条件,图查询语言对这类过滤支持得还不错。多模态那块我还在摸索,主要是嵌入模型和图数据模型的适配比较麻烦,等有成熟方案了再展开讲。