GraphRAG:基于 Neo4j 知识图谱的六步智能检索管线

GraphRAG:基于 Neo4j 知识图谱的六步智能检索管线

当用户问"Sprint 1 中有哪些未关闭的缺陷?",传统的向量检索无能为力——它需要理解"迭代-缺陷"的关系并生成图查询语句。本文解析一个完整的 GraphRAG 实现。


一、为什么需要 GraphRAG?

在研发管理场景中,很多查询本质上是关系型查询

  • "张伟负责了哪些任务?" → 需要 (Task)-[:ASSIGNED_TO]->(Member) 关系
  • "Sprint 2 的缺陷修复率?" → 需要 (Bug)-[:IN_SPRINT]->(Sprint) 关系 + 状态聚合
  • "登录模块相关的 Bug 涉及哪些需求?" → 需要 (Bug)-[:RELATES_TO]->(Story) 跨实体关联

这些查询用传统向量检索(RAG)做不了,因为 RAG 只能找到"语义相近的文本片段",无法做关系推理和多跳查询。
GraphRAG 的核心思路是:让 LLM 生成 Cypher 查询语句,在 Neo4j 中执行结构化检索

二、六步检索管线

整个 GraphRAG 流程分为六个步骤,每一步都有明确的输入输出:

用户提问 → 标签路由 → 节点检索 → Cypher生成 → Cypher验证 → Cypher校正 → 执行查询

2.1 标签路由:识别入口节点

用户说"张伟负责的 Bug 有哪些",系统首先需要确定查询的入口节点:Member("张伟")Bug

标签路由使用 LLM 的 Structured Output(Function Calling)机制,强制输出为 JSON 数组:

class RouteItem(BaseModel):
    label: str = Field(..., description='节点类型,比如"Bug"')
    entity: str = Field(..., description="实体文本")

class RouteOutput(BaseModel):
    outputs: list[RouteItem]

Prompt 中列出所有可选的节点标签(Member、Project、Sprint、Story、Task、Bug、Label),让 LLM 从用户输入中识别相关的标签和实体。

一个容错设计:如果模型不支持 Function Calling 或调用失败,降级为普通文本调用,然后用正则从 LLM 输出中提取 JSON 部分(处理 ````json 代码块、` 标签等干扰内容)。

def _extract_json_from_text(self, text: str) -> str:
    text = re.sub(r'<think>[\s\S]*?</think>', '', text).strip()
    if text.startswith("[") or text.startswith("{"):
        return text
    code_block_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
    if code_block_match:
        return code_block_match.group(1).strip()
    # ...

2.2 节点检索:混合检索定位入口

确定了入口标签和实体后,需要找到具体的 Neo4j 节点。对于不同标签采用不同策略:

Member 节点:直接 Cypher 精确查询(WHERE m.memberId = $id OR m.name = $name

其他节点:使用 Neo4j 的混合检索(HybridRetriever),同时执行向量检索和全文检索,合并排序:

retriever = HybridRetriever(
    self.driver,
    vector_index_name=label.lower() + "_vector",
    fulltext_index_name=label.lower() + "_fulltext",
)

其中全文检索需要先对中文实体进行分词处理(使用 jieba),将"登录接口返回500"分词为"登录 OR 接口 OR 返回 OR 500",作为全文检索的查询文本。

向量检索则使用嵌入模型(bge-base-zh-v1.5 或微调后的模型)将实体编码为 768 维向量,在 Neo4j 的向量索引中做余弦相似度匹配。

多个标签的检索任务通过 asyncio.gather 并发执行,避免串行等待。

2.3 Cypher 生成:LLM 编写查询语句

将用户原始问题、入口节点信息和 Neo4j Schema 一起传给 LLM,让它生成 Cypher 查询语句:

generate_cypher_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(
        "你是一个Cypher专家,根据入口节点和schema生成Cypher查询语句。"
        "**注意:查询结果中不可以包含嵌入向量等多余属性**\n"
        "仅返回Cypher语句。\nschema:\n{schema}"
    ),
    HumanMessagePromptTemplate.from_template(
        "入口节点:\n{entry_nodes}\n\n用户输入:\n{query}\n\nCypher语句:"
    ),
])

Schema 的获取有两种方式:

  1. APOC 插件:通过 Neo4jGraph(enhanced_schema=True) 自动获取
  2. 降级模式:如果 APOC 不可用,使用手动定义的 Schema 字符串

2.4 Cypher 验证:双重校验

生成的 Cypher 可能语法错误或逻辑不对,验证分两层:

语法验证:在 Cypher 前加 EXPLAIN 关键字,让 Neo4j 检查语法而不实际执行:

try:
    self.driver.execute_query(f"explain {cypher}")
except CypherSyntaxError as e:
    errors.append(str(e))

逻辑验证:再次调用 LLM,让它审查 Cypher 是否符合用户意图、关系方向是否正确、变量是否定义完整:

validate_prompt = """检查以下内容:
* Cypher是否需要包含用户信息作为过滤条件?
* 是否有语法错误?
* 关系方向是否符合schema?
* 是否漏定义了变量?
* 检索结果能否回答用户问题?
以列表格式输出错误信息。"""

2.5 Cypher 校正:闭环修复

如果验证发现错误,将错误信息和原始 Cypher 传给 LLM 进行校正:

correct_prompt = """根据schema和错误信息更正Cypher语句。
仅返回Cypher语句。"""

校正后还有一道保险:使用 LangChain 提供的 CypherQueryCorrector 校验关系方向。它会检查 Cypher 中每个关系是否在 Schema 中存在(包括反向关系),如果都不合法则返回空字符串,阻止执行。

2.6 执行查询:结构化结果转自然语言

Cypher 执行后返回的是 Neo4j Record 对象,需要转换为 LLM 和用户都能理解的文本:

def _format_record_as_text(self, record: dict) -> str:
    field_name_map = {
        'm.name': '成员姓名', 'st.title': '标题',
        'b.bug_id': '缺陷ID', 'priority': '优先级',
        # ...
    }
    parts = []
    for key, value in record.items():
        friendly_name = field_name_map.get(key, key)
        if value is not None:
            parts.append(f"{friendly_name}: {value}")
    return ",".join(parts)

最终每条查询结果封装为 SearchResult 对象,包含文本内容和数据来源(如"缺陷信息"、"任务信息"),供 EnterpriseSearchPolicy 进一步交给 LLM 生成最终回复。

三、Schema 降级策略

APOC(Awesome Procedures On Cypher)是 Neo4j 的官方扩展插件,Neo4jGraph.schema 依赖它自动获取图谱 Schema。但很多环境中 APOC 未安装或版本不兼容。

为此设计了完整的降级策略:

try:
    neo4j_graph = Neo4jGraph(url, user, password, enhanced_schema=True)
    self.neo4j_schema = neo4j_graph.schema
except Exception:
    logger.warning("降级为手动schema模式")
    self.neo4j_schema = self._build_fallback_schema()

手动 Schema 是一个多行字符串,列举了所有节点属性、关系定义,与 APOC 自动生成的格式一致。CypherQueryCorrector 使用的 Schema 对象也有对应的降级构建方法。

四、技术选型考量

4.1 为什么用 qwen3-coder 而不是通用模型?

Cypher 生成和校验本质上是"写代码"任务。通义千问的 Coder 系列模型在代码生成、语法检查上比通用模型更准确,因此 GraphRAG 的 LLM 调用使用 qwen3-coder-plus,而对话理解和 NLG 仍使用通用模型。

4.2 为什么嵌入模型要和索引构建用同一个?

向量检索的准确性取决于查询向量和索引向量在同一语义空间中。如果索引用 bge-base-zh 构建,但查询用微调后的模型编码,两者向量空间不一致,检索效果会大幅下降。因此 create_indexing.py(建索引)和 information_retrieval.py(检索)通过 EMBEDDING_MODEL 环境变量统一配置。

五、完整调用链路示例

用户在 IM 中问:"F10.10跌代中有哪些未关闭的缺陷?"

1. 标签路由 → [Sprint("Sprint 1")]
2. 节点检索 → Sprint节点: {sprint_name: "Sprint 1", sprint_id: "SP001"}
3. Cypher生成 →
   MATCH (b:Bug)-[:IN_SPRINT]->(s:Sprint {sprintId: "SP001"})
   WHERE b.status <> "closed"
   RETURN b.bugId, b.title, b.status, b.severity
4. Cypher验证 → 语法正确,逻辑正确(空错误列表)
5. Cypher校正 → 跳过(无错误)
6. 执行查询 → [
     {缺陷ID: BUG-012, 标题: 登录超时未提示, 状态: confirmed, 严重程度: major},
     {缺陷ID: BUG-015, 标题: 列表分页异常, 状态: in_progress, 严重程度: critical}
   ]

结果传回 EnterpriseSearchPolicy,LLM 将其组织为自然语言回复给用户。

六、总结

GraphRAG 的核心挑战不在于"让 LLM 写 Cypher",而在于构建一个可靠的闭环:生成 → 验证 → 校正 → 执行,每一步都有容错机制。这种多层校验的设计思路可以推广到任何"LLM 生成结构化查询"的场景(如 Text-to-SQL、Text-to-Elasticsearch)。

在实际使用中,Cypher 生成的准确率很大程度上取决于 Schema 的清晰度和 Prompt 的质量。保持 Schema 与图谱同步更新、在 Prompt 中明确约束输出格式("仅返回 Cypher 语句"),是提升准确率最有效的两个手段。

posted @ 2026-06-07 20:50  黄忠  阅读(121)  评论(0)    收藏  举报