十万个why:为什么 RAG 系统向量库解决不了的难题,用知识图谱轻松解决?

十万个why:为什么 RAG 系统向量库解决不了的难题,用知识图谱轻松解决?
一开始我们试过用分类模型来判断 query 该走哪条路径后来发现用几条简单的规则比如 query 里有明确的人名/组织名就走知识图谱否则走向量检索效果反而更稳定。工程上的简单往往比算法上的精巧更可靠。不是二选一的问题而是看你的数据长什么样、用户怎么问。我做过三个 RAG 项目分别用过纯向量检索、纯知识图谱、以及两者混合的方案。最后得出的经验是选错了技术路线后面怎么调优都很难救回来但选对了很多问题根本不用调就自然解决了。先搞清楚你的数据和查询模式讨论技术选型之前先回答两个问题第一你的知识库里实体之间的关系重要吗如果做的是企业内部的制度问答用户问出差报销的流程是什么这类问题的答案基本就是一段文本实体之间的关系不复杂。这种场景向量数据库就够用了把文档切块、做 embedding、检索最相似的几个 chunk、扔给 LLM 生成答案流程清晰工程量也不大。但如果做的是医疗领域的问答用户问某个药物和某个药物能不能一起吃这个问题的答案不在某一段文本里而是分散在药物说明书、临床指南、不良反应数据库等多个来源中需要把这些关系串起来才能回答药物A → 作用于 → 靶点X 药物B → 抑制 → 酶Y 酶Y → 代谢 → 药物A这种场景知识图谱的优势就很明显了。第二你的用户是怎么提问的如果用户的问法比较模糊、口语化比如那个之前开会提到的关于降本的方案具体内容是什么来着这种查询靠关键词匹配几乎不可能命中但向量检索的语义理解能力可以很好地处理。如果用户的问法是结构化的、精确的比如张三在 2024 年 Q3 负责了哪些项目这种查询需要精确匹配实体张三和关系负责知识图谱的图查询会比向量检索更准。向量数据库适合什么场景向量数据库的核心能力是语义相似度匹配。把一段文本转成一个高维向量查询的时候把用户的问题也转成向量然后在向量空间里找最近的几个邻居。它的优势在于对非结构化文本友好文档、邮件、聊天记录、会议纪要这些东西很难提取出干净的实体关系但做 embedding 很方便。语义理解能力强用户说降本增效和文档里写的成本优化措施虽然字面不一样但 embedding 之后的向量距离是很近的。工程落地快现在主流的向量数据库Milvus、Pinecone、Weaviate、Qdrant都比较成熟配合 LangChain 或 LlamaIndex一两天就能搭出一个可用的 RAG pipeline。# 一个典型的向量检索 RAG pipeline伪代码 from langchain.vectorstores import Milvus from langchain.embeddings import OpenAIEmbeddings # 1. 文档切块 embedding chunks text_splitter.split_documents(documents) vectorstore Milvus.from_documents(chunks, OpenAIEmbeddings()) # 2. 检索最相似的 chunk query 出差报销的流程是什么 relevant_chunks vectorstore.similarity_search(query, k5) # 3. 扔给 LLM 生成答案 answer llm.generate(cnotallowrelevant_chunks, questinotallowquery)但它有几个明显的不足我在项目里踩过一个大坑向量检索对关系型问题几乎无能为力。举个真实例子。我们做了一个企业知识库里面有很多项目文档。用户问A 项目的负责人同时还负责了哪些其他项目这个问题需要两步推理第一步找到 A 项目的负责人是谁第二步找到这个人还负责了哪些项目。向量检索能做的是把这个问题的 embedding 和所有文档 chunk 的 embedding 做比较召回和 A 项目语义最相近的几段文本。但这些文本里大概率只提到了 A 项目本身的信息并不会包含这个负责人同时还管着 B 项目和 C 项目这种跨文档的关联信息。我们当时试了几种补救措施增大召回数量top-k 从 5 调到 20HyDE先让 LLM 生成假设性回答再检索Multi-query把原始问题拆成多个子查询分别检索有一定改善但终究是在语义匹配的框架里打补丁治标不治本。另一个坑是数值和时间的精确匹配。向量检索本质上是模糊匹配用户问2024 年第三季度的销售额它可能召回 2023 年或者第二季度的内容因为这些文本在语义上确实很相近。我们后来的做法是在向量检索之外加了一层metadata filter把年份、季度这些结构化字段单独提取出来做精确过滤再在过滤后的结果集里做向量排序。这个方案管用但等于你自己又手工补了一部分结构化检索的能力。# 加了 metadata filter 的向量检索 results vectorstore.similarity_search( query第三季度的销售额, k5, filter{ year: 2024, quarter: Q3 } )知识图谱适合什么场景知识图谱的核心是把知识表示成三元组实体-关系-实体然后通过图查询语言做多跳推理。它的强项在于多跳推理A 项目的负责人还负责了哪些项目这种问题在图上就是两跳查询结果精确且完整// Cypher 查询两跳推理 MATCH (p:Project {name: A项目})-[:负责]-(person)-[:负责]-(other:Project) WHERE other.name A项目 RETURN person.name, collect(other.name) AS other_projects关系的可解释性。向量检索返回的是最相似的文本片段用户看不到推理过程。知识图谱返回的是一条清晰的推理路径张三 → 负责 → A项目 张三 → 负责 → B项目 张三 → 负责 → C项目可解释性强很多。在金融、医疗、法律这些合规要求高的领域这个特性有时候是硬需求。数据更新的精确性。如果某个项目换了负责人在知识图谱里改一条边就行了。在向量数据库里你得找到所有提到这个关系的 chunk重新做 embedding还得确保旧的 chunk 被正确删除或更新操作上容易出错。知识图谱的门槛但知识图谱的门槛也不低最大的成本在知识抽取。原始数据大概率是非结构化的文档要把它们变成三元组需要做实体识别NER、关系抽取RE、实体消歧。这三步里每一步都可能引入误差。我们做过一个尝试用 GPT-4 从技术文档里自动抽取三元组。效果怎么说呢简单的关系比如某人属于某部门抽得还行准确率大概在 85% 左右。但复杂一点的关系比如某系统在某条件下依赖某服务的某个接口准确率直接掉到 60% 以下。# 用 LLM 自动抽取三元组伪代码 prompt 从以下文档中抽取实体和关系输出为三元组格式 (实体1, 关系, 实体2) 文档内容 {document_text} # 简单关系(张三, 属于, 技术部) → 准确率 ~85% # 复杂关系(订单系统, 在高并发场景下依赖, 支付网关的回调接口) → 准确率 60%抽取质量不行后面的图查询结果自然也不可靠。我们最后的做法是半自动化LLM 先做初步抽取然后由领域专家 review 和修正。这个人工成本是不低的一万篇文档大概需要两个人花三周时间做质量审核。所以如果你的知识库规模大、更新频繁纯知识图谱的方案在维护成本上可能会让你吃不消。还有一个问题是schema 设计。知识图谱需要预先定义实体类型和关系类型这个 schema 设计得不好后面很难改。我们在一个项目里一开始没把临时负责和正式负责区分成两种关系类型结果后面业务方要查正式负责人是谁的时候发现数据里混着一堆临时代理的关系只能回去改 schema 重新导数据。混合方案说实话我做的三个项目最终都走向了某种程度的混合方案区别只在于混合的方式不同。方案一向量检索为主知识图谱做补充适合知识库以非结构化文档为主、但有一部分核心实体关系需要精确查询的场景。具体做法是先用向量检索召回相关文档片段同时从用户 query 中提取实体去知识图谱里查相关的结构化信息比如实体的属性、关联实体然后把两部分结果一起塞给 LLM 做生成。# 方案一向量检索为主 知识图谱补充伪代码 def hybrid_retrieve(query): # 1. 向量检索召回相关文档 vector_results vectorstore.similarity_search(query, k5) # 2. 实体提取从 query 中抽取实体 entities extract_entities(query) # e.g., [A项目] # 3. 图谱查询查关联的结构化信息 graph_results [] for entity in entities: related knowledge_graph.query( fMATCH (n)-[r]-(m) WHERE n.name{entity} RETURN n,r,m ) graph_results.extend(related) # 4. 合并上下文交给 LLM context merge(vector_results, graph_results) return llm.generate(cnotallowcontext, questinotallowquery)我们在一个内部知识库项目里用的就是这个方案。向量检索负责回答这个东西是什么、怎么用这类问题知识图谱负责回答谁负责的、依赖了哪些组件、上下游是什么这类关系型问题。路由层根据 query 的类型决定走哪条路径或者两条都走然后合并结果。实际效果是相比纯向量检索整体的答案准确率提升了很多主要提升来自于那些关系型问题之前纯向量方案基本答不对的加了知识图谱之后大部分能答对了。方案二知识图谱为主向量检索做兜底适合领域知识已经有比较完善的本体结构、但又不可能把所有知识都塞进图谱的场景比如医疗、金融。用户 query 先尝试转成图查询如果图谱里能查到结果直接返回如果查不到可能是图谱覆盖不全或者 query 太模糊没法转成图查询再 fallback 到向量检索。# 方案二知识图谱为主 向量检索兜底伪代码 def graph_first_retrieve(query): # 1. 尝试将自然语言转成 Cypher cypher text_to_cypher(query, schemagraph_schema, few_shotsexamples) # 2. 语法校验 ifnot validate_cypher(cypher): return fallback_to_vector(query) # 3. 执行图查询 graph_results knowledge_graph.execute(cypher) # 4. 结果为空则兜底 ifnot graph_results: return fallback_to_vector(query) return llm.generate(cnotallowgraph_results, questinotallowquery)这个方案的核心难点在Text-to-Cypher这一步。让 LLM 把自然语言问题翻译成Cypher查询对 schema 的理解能力要求很高而且很容易生成语法正确但语义错误的查询。我们的做法是给 LLM 提供完整的schema定义和几个few-shot example同时对生成的 Cypher 做语法校验和结果合理性检查比如返回结果为空的时候自动 fallback。方案三Graph RAG这是最近比较热的一种思路微软开源的 GraphRAG 项目把这个概念带火了。核心思想是用 LLM 从文档中自动构建社区级别的知识图谱然后在检索时利用图的社区结构做多层次的摘要和检索。我试过这个方案说说真实感受全局性问题效果好比如这个代码库整体的架构是怎样的它能把分散在多个文档里的信息通过图的社区结构汇总起来比纯向量检索强缺点也很明显构建索引的成本极高token 消耗是纯向量方案的几十倍而且对于细节性的点查询某个函数的参数是什么效果反而不如简单的向量检索所以GraphRAG 更适合需要跨文档总结和全局视角的场景不太适合做精确的事实查询。怎么选型一张表说清楚几个容易踩的坑最后说几个我自己踩过的具体的坑1. 别在向量检索上死磕多跳推理我见过有团队用很复杂的 prompt chain 试图在纯向量检索的基础上实现多跳推理先检索一次拿到中间结果把中间结果再作为 query 检索第二次如此反复。能跑通但召回的噪音会随着跳数指数级放大三跳以上基本不可用。这种场景就老老实实用知识图谱。2. 知识图谱的冷启动比你想象的慢如果你的领域没有现成的本体结构可以复用从零开始设计 schema、抽取三元组、做实体消歧光前期准备工作就可能要一两个月。别低估这个时间。3. 向量数据库的 embedding 模型选型很关键我们早期用的是通用的 sentence-transformers 模型后来换成了在领域数据上 fine-tune 过的 embedding 模型召回率提升不少。如果你的领域有大量专业术语通用模型的效果可能会打折扣。4. 混合方案里的路由层不要过度设计一开始我们试过用分类模型来判断 query 该走哪条路径后来发现用几条简单的规则比如 query 里有明确的人名/组织名就走知识图谱否则走向量检索效果反而更稳定。工程上的简单往往比算法上的精巧更可靠。