一、从“扁平检索”到“关系推理”

传统RAG系统通过向量检索从文档库中召回相关片段,在处理事实性问答时表现尚可,但面对需要多跳推理和关系理解的问题时往往力不从心。例如,当用户问“糖尿病患者应该避免哪些高糖食物?”时,传统RAG需要分别检索“糖尿病”和“高糖食物”的相关文档,再让LLM进行跨文档推理——这个过程既低效又容易出错。

GraphRAG(图检索增强生成) 的解决方案是用知识图谱显式编码实体间的语义关系,让检索从“找相似的文本块”升级为“沿着关系路径推理”。Neo4j作为最成熟的图数据库,在这一架构中扮演着知识存储与关系查询的核心角色。

两者的关系可以这样理解:Neo4j负责“存关系、查路径”,LLM负责“理解问题、生成答案” 。当用户提出复杂问题时,系统先在Neo4j中沿着关系遍历找到相关实体和路径,再将结构化的检索结果作为上下文交给LLM生成回答。


二、系统架构:从向量到图谱的双路检索

2.1 GraphRAG的典型工作流程

一个完整的Neo4j + RAG系统包含以下核心环节:

简单事实查询

复杂关系推理

用户Query

查询分析与意图识别

路由决策

向量检索
Chroma/Milvus

图谱检索
Neo4j Cypher

重排序Rerank

上下文构建

LLM生成答案

2.2 环境配置

以医疗知识图谱问答系统为例,需要配置Neo4j和LLM服务:

# 使用Docker启动Neo4j
docker run -d --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/your_password \
  neo4j:latest

# 安装Python依赖
pip install neo4j langchain openai chromadb

环境变量配置(.env文件):

NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password
OPENAI_API_KEY=sk-xxx

三、核心实现:Neo4j知识图谱构建与检索

3.1 知识图谱构建

从结构化数据或文档中抽取实体和关系,构建知识图谱。以下是一个医疗领域知识图谱的构建示例:

from neo4j import GraphDatabase
import json

class KnowledgeGraphBuilder:
    def __init__(self, uri, user, password):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))
    
    def create_entity(self, tx, entity_type, name, properties=None):
        """创建实体节点"""
        query = f"""
        CREATE (e:{entity_type} {{
            name: $name,
            {', '.join([f'{k}: ${k}' for k in (properties or {}).keys()])}
        }})
        RETURN e
        """
        params = {"name": name, **(properties or {})}
        result = tx.run(query, **params)
        return result.single()[0]
    
    def create_relationship(self, tx, from_name, to_name, rel_type, from_type=None, to_type=None):
        """创建实体间关系"""
        query = f"""
        MATCH (a {{name: $from_name}})
        MATCH (b {{name: $to_name}})
        CREATE (a)-[:{rel_type}]->(b)
        """
        tx.run(query, from_name=from_name, to_name=to_name)
    
    def build_from_json(self, json_path):
        """从JSON数据批量构建图谱"""
        with open(json_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        with self.driver.session() as session:
            for item in data:
                # 创建疾病节点
                disease = session.execute_write(
                    self.create_entity, 
                    "Disease", 
                    item["disease"],
                    {"symptom": item.get("symptom", "")}
                )
                # 创建关联实体
                for drug in item.get("drugs", []):
                    drug_node = session.execute_write(
                        self.create_entity, "Drug", drug
                    )
                    session.execute_write(
                        self.create_relationship,
                        disease["name"], drug, "recommand_drug"
                    )

# 使用示例
builder = KnowledgeGraphBuilder(
    uri="bolt://localhost:7687",
    user="neo4j",
    password="your_password"
)
builder.build_from_json("./medical_data.json")

3.2 混合检索实现

结合向量检索的“语义相似”和图检索的“关系推理”,实现更精准的召回:

from langchain.embeddings import OpenAIEmbeddings
from chromadb import Client as ChromaClient
from neo4j import GraphDatabase

class HybridRetriever:
    def __init__(self, neo4j_uri, neo4j_user, neo4j_password):
        self.neo4j_driver = GraphDatabase.driver(
            neo4j_uri, auth=(neo4j_user, neo4j_password)
        )
        self.chroma_client = ChromaClient()
        self.embeddings = OpenAIEmbeddings()
        self.vector_collection = self.chroma_client.get_or_create_collection(
            name="knowledge_chunks"
        )
    
    def vector_search(self, query: str, top_k: int = 5):
        """语义向量检索"""
        query_embedding = self.embeddings.embed_query(query)
        results = self.vector_collection.query(
            query_embeddings=[query_embedding],
            n_results=top_k
        )
        return results['documents'][0] if results['documents'] else []
    
    def graph_search(self, query: str, top_k: int = 5):
        """图谱关系检索"""
        # 用LLM将自然语言转为Cypher查询
        cypher_query = self._nl_to_cypher(query)
        
        with self.neo4j_driver.session() as session:
            result = session.run(cypher_query)
            return [record.data() for record in result[:top_k]]
    
    def hybrid_search(self, query: str):
        """混合检索:向量 + 图谱双路召回后融合排序"""
        vector_results = self.vector_search(query)
        graph_results = self.graph_search(query)
        
        # 融合去重(简化版:按来源加权合并)
        combined = vector_results + graph_results
        # 实际生产环境可使用Reranker精排
        
        return self._build_context(combined)

3.3 智能路由:何时走图谱,何时走向量

根据查询复杂度自动路由到不同检索策略,是GraphRAG系统的核心能力:

from langchain.chat_models import ChatOpenAI

class QueryRouter:
    def __init__(self):
        self.llm = ChatOpenAI(temperature=0)
    
    def analyze_query(self, query: str) -> dict:
        """用LLM分析查询类型和复杂度"""
        prompt = f"""
        分析以下查询的特征,输出JSON格式:
        - complexity: 0-1,1表示需要多跳推理
        - entities: 提到的实体数量
        - needs_relation: 是否需要关系推理

        查询:{query}
        """
        response = self.llm.predict(prompt)
        # 解析响应(简化)
        return {"complexity": 0.7, "needs_relation": True}
    
    def route(self, query: str) -> str:
        analysis = self.analyze_query(query)
        if analysis["needs_relation"] and analysis["complexity"] > 0.5:
            return "graph"      # 走图谱检索
        return "vector"         # 走向量检索

# 集成到RAG Pipeline
router = QueryRouter()
retriever = HybridRetriever(...)

def graph_rag_answer(query: str):
    route = router.route(query)
    if route == "graph":
        context = retriever.graph_search(query)
    else:
        context = retriever.vector_search(query)
    # 调用LLM生成最终答案
    return generate_answer(query, context)

四、实战案例:医疗知识图谱问答系统

医疗领域是对回答准确性要求最高的场景之一。通过Neo4j构建的疾病知识图谱,可以支撑精准的医学问答。

4.1 知识图谱规模

实体类型 数量 示例
疾病 8,807 糖尿病、高血压
症状 5,998 多饮、多尿、头晕
药品 3,828 胰岛素、二甲双胍
食物 4,870 西瓜、苦瓜
检查项目 3,353 血糖检测

4.2 典型问答流程

def medical_qa(query: str):
    # Step 1: 实体识别
    entities = extract_medical_entities(query)  # 如["糖尿病", "高糖食物"]
    
    # Step 2: 图谱检索
    with neo4j_driver.session() as session:
        # 查询糖尿病患者应避免的食物
        cypher = """
        MATCH (d:Disease {name: $disease})
        MATCH (d)-[:no_eat]->(f:Food)
        RETURN f.name AS food, f.reason AS reason
        """
        result = session.run(cypher, disease=entities[0])
        food_list = [record["food"] for record in result]
    
    # Step 3: LLM生成回答
    context = f"根据医学知识图谱,{entities[0]}患者应避免以下高糖食物:{', '.join(food_list)}"
    return llm.generate(f"{context}\n用户问题:{query}")

五、总结与最佳实践

5.1 核心优势

传统RAG Neo4j增强的GraphRAG
扁平检索,难以处理多跳推理 图遍历支持多跳关系推理
实体关系隐式存在于文本中 关系显式建模,可追溯推理路径
无法回答“A和B有什么关系” 通过图谱直接查询关系

5.2 避坑指南

常见问题 解决方案
知识图谱构建成本高 先用LLM从文档自动抽取实体关系,再人工校验
Cypher查询生成不稳定 使用Few-shot提示,提供多个示例约束LLM输出格式
向量与图谱结果难以融合 使用Reranker模型(如Cohere Rerank)统一排序
Neo4j性能瓶颈 创建索引(包括向量索引),对复杂查询使用APOC优化

5.3 适用场景

Neo4j+RAG的GraphRAG方案特别适合以下场景:

  • 医疗/法律/金融:对回答准确性要求高,且实体关系密集
  • 企业内部知识库:文档间存在大量交叉引用和层级关系
  • 教育/科研领域:概念之间的依赖和递进关系需要显式建模

GraphRAG不是要替代传统RAG,而是为“需要关系推理”的问题提供更好的解决方案。在实践中,混合检索 + 智能路由往往是最优策略——让简单查询走向量路,让复杂推理走图谱路,各取所长。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐