您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
AI辅助企业网络安全&治理
6月11-12日 北京+线上
基于模型的数据治理与数据中台
6月16-17日 北京+线上
Spec Driven Development 工程化实践
6月12-13日 北京+线上
     
   
 订阅
Neo4j 知识图谱:实体建模+Cypher查询+LangChain接入

 
作者:Jameszyh
 
  19   次浏览      3 次
 2026-6-8
 
编辑推荐:
本文主要介绍了Neo4j 知识图谱:实体建模+Cypher查询+LangChain接入相关内容。希望对你的学习有帮助。
本文来自于微信公众号James的成长日记,由火龙果软件Alice编辑,推荐。

大家好,上一篇我们用 ES 全文检索 + 向量检索拼出了混合召回,现在是时候把第三条腿补上——图检索。

搭 RAG 系统时,很多同学到了"多跳推理"这一关就卡死了。问题是这样的:你有一份公司知识库,用户问「负责 payment 模块的工程师最近在做哪个项目?」

向量检索给你的是语义最相近的几段文字,但没有任何一段同时包含"工程师-模块-项目"这三个维度的信息。因为这个问题本身就不是语义相似度能回答的——它需要跨两层关系推理:人 → 负责模块 → 参与项目。

关系型数据库也不好使,你得写三张表的 JOIN,性能一塌糊涂,还没法扩展到任意深度的路径查询。

这就是 Neo4j 的主场:用图结构存储实体和关系,用 Cypher 做任意深度的路径遍历,再通过 LangChain 的 Neo4jGraph 让 LLM 自动生成查询——整套打通,效果碾压纯向量。

本文使用版本:

  • TypeScript:langchain@1.4.1 · @langchain/openai@1.4.6 · @langchain/community@1.4.2(含 Neo4j 支持)

  • Python:langchain>=0.3.x · langchain-openai>=0.3.x · langchain-community>=0.3.x

01 为什么选图数据库:关系密集型查询的天然优势

先说清楚图数据库解决什么问题,不然很多同学会觉得"用向量也能查,何必搞这个"。

关系型数据库的痛在于多表关联查询。比如「找出所有在 2025 年参与过 payment 相关项目的工程师的直属上级」,SQL 需要三张表 JOIN,数据量大时索引失效,慢到用不了。

图数据库的答法:

成MATCH (m:Manager)<-[:REPORTS_TO]-(e:Engineer)
      -[:WORKED_ON]->(p:Project)
WHERE p.name CONTAINS 'payment' AND p.year = 2025
RETURN DISTINCT m.name

一句 Cypher,无论关系链多深,图引擎都走原生指针遍历,不做 JOIN,时间复杂度和关系深度呈线性增长而非指数增长。

三种数据库的横向对比:

维度 关系型(PostgreSQL) 向量数据库(Milvus) 图数据库(Neo4j)
核心优势 精确查询、事务 语义相似度 关系路径遍历
查询方式 SQL ANN 近似最近邻 Cypher 路径匹配
多跳关联 JOIN,性能指数恶化 不支持 原生支持,线性增长
模糊查询 LIKE,效率低 语义向量,效果强 弱(需配合向量)
最适合场景 结构化事务数据 语义检索 知识图谱、关系网络

结论:三者不是替代关系,而是互补关系。本文重点是 Neo4j,下一篇讲怎么把三者拼在一起。

02 核心概念:Node、Relationship、Property 三分钟建立直觉

Neo4j 的数据模型只有三个概念:

Node(节点):实体。一个工程师、一个项目、一项技术,都是节点。节点上有 Label(标签)表示类型,有 Property(属性)存储数据。示例:(e:Engineer {id: "E001", name: "张三", level: "P7"})

Relationship(关系):两个节点之间的边,有方向、有类型、可带属性。关系是一等公民,不是外键,而是真实的存储单元。示例:(e:Engineer)-[:WORKED_ON {since: "2024-01", role: "lead"}]->(p:Project)

Property(属性):节点和关系都可以有属性,存 key-value,支持字符串、数字、布尔、列表。

用 ASCII 图示意实际结构:

(张三:Engineer) ──[WORKED_ON]──> (payment服务:Project)
       |                                  |
[REPORTS_TO]                      [USES_TECH]
       ↓                                  ↓
(李总:Manager)              (TypeScript:Technology)

这就是知识图谱:把零散知识变成有关系的网络,查询时沿着边走就行。

03 环境搭建:Docker 启动 Neo4j + TypeScript 连接

用 Docker 起 Neo4j 最快:

docker run \
  --name neo4j -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/your-password \
  -v $PWD/neo4j-data:/data \
  -d neo4j:5.18

7474 是 Browser 可视化端口,7687 是应用程序用的 Bolt 协议端口。起来之后打开 http://localhost:7474 就能看到可视化界面。

安装依赖并连接:

import neo4j from "neo4j-driver";

// npm install neo4j-driver @langchain/community @langchain/openai
const driver = neo4j.driver(
  "bolt://localhost:7687",
  neo4j.auth.basic("neo4j""your-password")
);

const serverInfo = await driver.getServerInfo();
console.log("Connected to:", serverInfo.address);
console.log("Neo4j version:", serverInfo.agent);

 

from neo4j import GraphDatabase

# pip install neo4j langchain-community langchain-openai
driver = GraphDatabase.driver(
    "bolt://localhost:7687",
    auth=("neo4j""your-password")
)

with driver.session() as session:
    result = session.run("RETURN 1 AS n")
    print("Connected! Result:", result.single()["n"])

server_info = driver.get_server_info()
print("Connected to:", server_info.address)
print("Neo4j version:", server_info.agent)

04 Cypher 实战:六个高频模式全覆盖

Cypher 的语法设计非常直观——它用 ASCII 画图来描述你想查什么。

模式一:MERGE(防重写入,生产必备)

const session = driver.session();

// MERGE = 存在则更新,不存在则创建。生产中永远用 MERGE,不用 CREATE
await session.run(`
  MERGE (e:Engineer {id: $id})
  ON CREATE SET e.name = $name, e.level = $level, e.createdAt = datetime()
  ON MATCH SET e.level = $level, e.updatedAt = datetime()
  RETURN e
`
, { id"E001"name"张三"level"P8" });

 

with driver.session() as session:
    # MERGE = 存在则更新,不存在则创建。生产中永远用 MERGE,不用 CREATE
    result = session.run(
        """
        MERGE (e:Engineer {id: $id})
        ON CREATE SET e.name = $name, e.level = $level, e.createdAt = datetime()
        ON MATCH SET e.level = $level, e.updatedAt = datetime()
        RETURN e
        """
,
        id="E001", name="张三", level="P8"
    )
    print(result.single())

 

模式二:路径查询(多跳遍历)

// *1..2 表示路径长度 1 到 2 跳
const result = await session.run(`
  MATCH (e:Engineer {id: $id})-[:WORKED_ON*1..2]->(p:Project)
  RETURN DISTINCT p.name AS project, p.status
`
, { id"E001" });

// 找参与过 payment 项目的工程师的所有协作者
const collab = await session.run(`
  MATCH (e:Engineer)-[:WORKED_ON]->(p:Project {name: 'payment服务'})
        <-[:WORKED_ON]-(colleague:Engineer)
  WHERE e.id <> colleague.id
  RETURN e.name AS engineer, collect(DISTINCT colleague.name) AS colleagues
`
);

 

with driver.session() as session:
    # *1..2 表示路径长度 1 到 2 跳
    result = session.run(
        """
        MATCH (e:Engineer {id: $id})-[:WORKED_ON*1..2]->(p:Project)
        RETURN DISTINCT p.name AS project, p.status
        """
,
        id="E001"
    )
    for record in result:
        print(record["project"], record["status"])

    # 找参与过 payment 项目的工程师的所有协作者
    collab = session.run(
        """
        MATCH (e:Engineer)-[:WORKED_ON]->(p:Project {name: 'payment服务'})
              <-[:WORKED_ON]-(colleague:Engineer)
        WHERE e.id <> colleague.id
        RETURN e.name AS engineer, collect(DISTINCT colleague.name) AS colleagues
        """

    )
    for record in collab:
        print(record["engineer"], record["colleagues"])

 

模式三:UNWIND 批量写入(比单条循环快 10x+)

// UNWIND 展开数组,批量写入减少网络往返
await session.run(`
  UNWIND $engineers AS eng
  MERGE (e:Engineer {id: eng.id})
  ON CREATE SET e.name = eng.name, e.level = eng.level
`
, {
  engineers: [
    { id"E002"name"李四"level"P6" },
    { id"E003"name"王五"level"P8" },
  ]
});

// 批量建立关系
await session.run(`
  UNWIND $assignments AS assign
  MATCH (e:Engineer {id: assign.engineerId})
  MATCH (p:Project {id: assign.projectId})
  MERGE (e)-[r:WORKED_ON {role: assign.role}]->(p)
  SET r.since = assign.since
`
, {
  assignments: [
    { engineerId"E001"projectId"P001"role"lead"since"2024-01" },
    { engineerId"E002"projectId"P001"role"backend"since"2024-03" },
  ]
});

 

with driver.session() as session:
    # UNWIND 展开数组,批量写入减少网络往返
    session.run(
        """
        UNWIND $engineers AS eng
        MERGE (e:Engineer {id: eng.id})
        ON CREATE SET e.name = eng.name, e.level = eng.level
        """
,
        engineers=[
            {"id""E002""name""李四""level""P6"},
            {"id""E003""name""王五""level""P8"},
        ]
    )

    # 批量建立关系
    session.run(
        """
        UNWIND $assignments AS assign
        MATCH (e:Engineer {id: assign.engineerId})
        MATCH (p:Project {id: assign.projectId})
        MERGE (e)-[r:WORKED_ON {role: assign.role}]->(p)
        SET r.since = assign.since
        """
,
        assignments=[
            {"engineerId""E001""projectId""P001""role""lead""since""2024-01"},
            {"engineerId""E002""projectId""P001""role""backend""since""2024-03"},
        ]
    )

 

05 LangChain 接入:让 LLM 自动生成 Cypher 查询

这是本篇最核心的一节。手写 Cypher 门槛高,但 LangChain 的 GraphCypherQAChain 可以让 LLM 根据图的 schema 自动生成查询语句。

初始化 + QA Chain + 自定义 Prompt:

import { Neo4jGraph } from "@langchain/community/graphs/neo4j_graph";
import { ChatOpenAI } from "@langchain/openai";
import { GraphCypherQAChain } from "@langchain/community/chains/graph_qa/cypher";
import { PromptTemplate } from "@langchain/core/prompts";

// 1. 初始化图连接,刷新 schema 让 LLM 了解结构
const graph = await Neo4jGraph.initialize({
  url"bolt://localhost:7687",
  username"neo4j",
  password"your-password",
});
await graph.refreshSchema();
// schema 输出类似:
// Node properties: Engineer {id: STRING, name: STRING, level: STRING}
// Relationships: (:Engineer)-[:WORKED_ON]->(:Project)

// 2. 定制 Cypher 生成 Prompt,提升准确率
const cypherPrompt = PromptTemplate.fromTemplate(`
你是 Neo4j 专家,根据以下图 schema 生成精确 Cypher 查询。

Schema: {schema}

规则:
1. 只生成 READ 查询(MATCH/RETURN),禁止写入操作
2. 关系方向严格按 schema,不随意反向
3. 字符串比较用 CONTAINS,避免大小写问题
4. 结果必须加 LIMIT 50

用户问题:{question}
Cypher 查询:
`
);

// 3. 构建 Chain
const chain = GraphCypherQAChain.fromLLM({
  llmnew ChatOpenAI({ modelName"gpt-4o"temperature0 }),
  graph,
  cypherPrompt,
  verbosetrue,   // 开发时打开,能看到生成的 Cypher
  returnDirectfalse,
});

// 4. 自然语言查询
const result = await chain.invoke({
  query"负责 payment 服务的工程师有哪些?他们的 level 是什么?"
});
console.log(result.result);
// → "负责 payment 服务的工程师有:张三(P7)、李四(P6)。其中张三担任 lead 角色。"

 

from langchain_community.graphs import Neo4jGraph
from langchain_community.chains.graph_qa.cypher import GraphCypherQAChain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate

# 1. 初始化图连接,刷新 schema 让 LLM 了解结构
# pip install langchain-community langchain-openai neo4j
graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="your-password",
)
graph.refresh_schema()
# schema 输出类似:
# Node properties: Engineer {id: STRING, name: STRING, level: STRING}
# Relationships: (:Engineer)-[:WORKED_ON]->(:Project)

# 2. 定制 Cypher 生成 Prompt,提升准确率
cypher_prompt = PromptTemplate.from_template("""
你是 Neo4j 专家,根据以下图 schema 生成精确 Cypher 查询。

Schema: {schema}

规则:
1. 只生成 READ 查询(MATCH/RETURN),禁止写入操作
2. 关系方向严格按 schema,不随意反向
3. 字符串比较用 CONTAINS,避免大小写问题
4. 结果必须加 LIMIT 50

用户问题: {question}
Cypher 查询:
"""
)

# 3. 构建 Chain
chain = GraphCypherQAChain.from_llm(
    llm=ChatOpenAI(model="gpt-4o", temperature=0),
    graph=graph,
    cypher_prompt=cypher_prompt,
    verbose=True,   # 开发时打开,能看到生成的 Cypher
    return_direct=False,
)

# 4. 自然语言查询
result = chain.invoke({"query""负责 payment 服务的工程师有哪些?他们的 level 是什么?"})
print(result["result"])
# → "负责 payment 服务的工程师有:张三(P7)、李四(P6)。其中张三担任 lead 角色。"

 

背后 LLM 自动生成的 Cypher(verbose 模式可见):

MATCH (e:Engineer)-[r:WORKED_ON]->(p:Project)
WHERE p.name CONTAINS 'payment'
RETURN e.name, e.level, r.role
LIMIT 50

06 生产级建模:以技术团队知识图谱为例

光看语法没感觉,来一个完整例子:把技术团队的人员、项目、技术栈全部关联起来。

建模原则:先列实体(名词),再列关系(动词),关系属性描述“怎么发生的”。

 

实体节点:
- Engineer:id, name, level, joinDate
- Project:id, name, status, startDate
- Technology:name, category
- Team:id, name, department

关系(主体 → 客体,统一方向):
- (Engineer)-[:REPORTS_TO]->(Manager)
- (Engineer)-[:BELONGS_TO]->(Team)
- (Engineer)-[:WORKED_ON {role, since}]->(Project)
- (Project)-[:USES_TECH]->(Technology)
- (Engineer)-[:SKILLED_IN {level}]->(Technology)

 

完整初始化脚本:

async function buildKnowledgeGraph(driver: neo4j.Driver) {
  const session = driver.session();
  try {
    // 创建唯一约束(相当于唯一索引,防重复 + 加速查询)
    await session.run(`CREATE CONSTRAINT IF NOT EXISTS
      FOR (e:Engineer) REQUIRE e.id IS UNIQUE`
);
    await session.run(`CREATE CONSTRAINT IF NOT EXISTS
      FOR (p:Project) REQUIRE p.id IS UNIQUE`
);

    // 一次性批量写入工程师 + 项目 + 关系
    await session.run(`
      UNWIND $engineers AS eng
      MERGE (e:Engineer {id: eng.id}) SET e += eng
    `
, {
      engineers: [
        { id"E001"name"张三"level"P7"joinDate"2022-03" },
        { id"E002"name"李四"level"P6"joinDate"2023-06" },
      ]
    });

    await session.run(`
      UNWIND $assignments AS a
      MATCH (e:Engineer {id: a.eid}), (p:Project {id: a.pid})
      MERGE (e)-[r:WORKED_ON {role: a.role}]->(p)
      SET r.since = a.since
    `
, {
      assignments: [
        { eid"E001"pid"P001"role"lead"since"2024-01" },
        { eid"E002"pid"P001"role"backend"since"2024-03" },
      ]
    });
  } finally {
    await session.close();
  }
}

 

async function buildKnowledgeGraph(driver: neo4j.Driver) {
  const session = driver.session();
  try {
    // 创建唯一约束(相当于唯一索引,防重复 + 加速查询)
    await session.run(`CREATE CONSTRAINT IF NOT EXISTS
      FOR (e:Engineer) REQUIRE e.id IS UNIQUE`
);
    await session.run(`CREATE CONSTRAINT IF NOT EXISTS
      FOR (p:Project) REQUIRE p.id IS UNIQUE`
);

    // 一次性批量写入工程师 + 项目 + 关系
    await session.run(`
      UNWIND $engineers AS eng
      MERGE (e:Engineer {id: eng.id}) SET e += eng
    `
, {
      engineers: [
        { id"E001"name"张三"level"P7"joinDate"2022-03" },
        { id"E002"name"李四"level"P6"joinDate"2023-06" },
      ]
    });

    await session.run(`
      UNWIND $assignments AS a
      MATCH (e:Engineer {id: a.eid}), (p:Project {id: a.pid})
      MERGE (e)-[r:WORKED_ON {role: a.role}]->(p)
      SET r.since = a.since
    `
, {
      assignments: [
        { eid"E001"pid"P001"role"lead"since"2024-01" },
        { eid"E002"pid"P001"role"backend"since"2024-03" },
      ]
    });
  } finally {
    await session.close();
  }
}

 

07 常见坑

坑 1:关系方向写反,一条数据都查不到

Cypher 的关系方向严格区分。-[:WORKED_ON]-> 和 <-[:WORKED_ON]- 是两个方向,建图时随手写,查询时方向对不上,结果为空还以为数据没导进去。

解决:建模时统一约定方向,写进 schema 注释,查询严格对照。紧急排查可以用无向 -[:WORKED_ON]-(不加箭头)临时绕过,但性能略差。

坑 2:用 CREATE 重跑脚本导致数据爆炸

CREATE 每次都创建新节点,脚本跑两遍就出现两个一模一样的节点。生产上永远用 MERGE,不用 CREATE(除非明确需要允许重复,如事件日志)。

坑 3:LLM 生成的 Cypher 关系名大小写错误

Neo4j 关系类型大小写敏感,WORKED_ON 和 worked_on 是两个不同关系。LLM 有时自作主张改大小写,导致查询为空。解决:在 cypherPrompt 里明确写出所有关系类型(全大写)。

坑 4:忘记创建索引,全图扫描

不加索引时,MATCH (e:Engineer {name: '张三'}) 会扫所有 Engineer 节点。数据量上万就明显变慢。常用查询字段都要建索引:

CREATE INDEX IF NOT EXISTS FOR (e:Engineer) ON (e.name);
CREATE INDEX IF NOT EXISTS FOR (p:Project) ON (p.name);

坑 5:LIMIT 缺失导致全图返回

LLM 生成的 Cypher 偶尔没有 LIMIT。复杂图里一次全局匹配可能返回几万条,内存直接打满。在 cypherPrompt 硬性要求加 LIMIT 50,并在 session 层设置超时。

坑 6:把大段文本塞进节点属性

图数据库的职责是存关系结构,大段文章内容应存向量数据库,节点只存文档 ID 做关联。Neo4j 存「锚点」,向量库存「内容」,查完再合并——这才是正确姿势。

总结

这篇从头到尾把 Neo4j 知识图谱的核心链路打通了:

  • 图 vs 关系型:多跳关系查询是图的主场,JOIN 堆叠是关系型的死穴,两者互补不替代

  • 三要素:关系是一等公民,带方向、带类型、带属性,这是图数据库的根本设计

  • Cypher 三大高频模式:MERGE 防重写入、UNWIND 批量写入、*1..2 路径深度控制,生产必会

  • LangChain GraphCypherQAChain:LLM 自动生成 Cypher,temperature=0 + 定制 prompt 是准确率关键

  • 建模原则:图存关系结构,向量库存文本内容,两边各司其职

  • 六个真实坑:关系方向、MERGE 代替 CREATE、索引缺失、LIMIT 缺失——上线前逐项核查
   
19   次浏览       3 次
相关文章

基于图卷积网络的图深度学习
自动驾驶中的3D目标检测
工业机器人控制系统架构介绍
项目实战:如何构建知识图谱
 
相关文档

5G人工智能物联网的典型应用
深度学习在自动驾驶中的应用
图神经网络在交叉学科领域的应用研究
无人机系统原理
相关课程

人工智能、机器学习&TensorFlow
机器人软件开发技术
人工智能,机器学习和深度学习
图像处理算法方法与实践

最新活动计划
AI辅助企业网络安全与治理 6-11[北京]
基于模型的数据治理 6-16[北京]
Spec 驱动开发(SDD)实战 6-12[北京]
具身智能技能与实践 6-11[厦门]
AI智能体开发技术实践 6-24[上海]
AI辅助软件测试方法与实践 6-26[在线]
 
 
最新文章
AIGC技术与应用全解析
详解知识图谱的构建全流程
大模型升级与设计之道
自动驾驶和辅助驾驶系统
ROS机器人操作系统底层原理
最新课程
人工智能,机器学习和深度学习
人工智能与机器学习应用实战
人工智能-图像处理和识别
人工智能、机器学习& TensorFlow+Keras框架实践
人工智能+Python+大数据
成功案例
某综合性科研机构 人工智能与机器学习
某银行 人工智能+Python+大数据
北京 人工智能、机器学习& TensorFlow
某领先数字地图提供商 Python数据分析
中国移动 人工智能、机器学习和深度学习