LangChain.js 与 Neo4j 图数据库集成指南:图谱、向量检索与对话记忆实战
【免费下载链接】langchainjsThe agent engineering platform项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs
本篇指南围绕@langchain/neo4j集成包展开,介绍如何在 LangChain.js 应用中接入 Neo4j(及兼容 Bolt 协议的 Memgraph)图数据库:包括图数据库封装Neo4jGraph、基于向量索引的Neo4jVectorStore、会话消息历史存储Neo4jChatMessageHistory,以及"自然语言 → Cypher → 答案"的问答链GraphCypherQAChain。读完本文,你将掌握从安装、初始化、写入图数据、混合检索到搭建完整图问答应用的完整方案。
包概览与适用场景
@langchain/neo4j是 LangChain.js 官方提供的 Neo4j 图数据库集成包,源码位于 libs/providers/langchain-neo4j,核心入口统一从 src/index.ts 导出。它同时兼容 Memgraph(一款同样使用 Bolt 协议的内存图数据库)。该包提供的组件可分为四类:
| 组件 | 类型 | 典型用途 |
|---|---|---|
Neo4jGraph/MemgraphGraph | 图数据库封装 | 连接管理、Schema 自省、Cypher 查询、结构化图文档写入 |
Neo4jVectorStore | 向量存储 | 基于 Neo4j 向量索引的相似度检索、混合检索、元数据过滤 |
Neo4jChatMessageHistory | 聊天历史存储 | 将会话消息持久化到图数据库,支持滑动窗口 |
GraphCypherQAChain | 问答链 | 面向图数据的自然语言问答(LLM 生成 Cypher 查询) |
从 package.json 可以看到,该包将neo4j-driver(^6.2.0)作为直接依赖自动安装,将@langchain/core作为必要 peer dependency、@langchain/classic作为可选 peer dependency(仅GraphCypherQAChain需要)。因此按文档安装时只需显式安装两个包即可:
npm install @langchain/neo4j @langchain/core如需使用GraphCypherQAChain,额外安装@langchain/classic。运行时要求 Node.js >= 20(见 package.json)。
Neo4jGraph:图数据库的基础封装
Neo4jGraph是对 Neo4j 驱动的轻量封装,提供连接管理、Schema 自省和查询执行三类能力。
初始化与基本用法
import { Neo4jGraph } from "@langchain/neo4j"; const graph = await Neo4jGraph.initialize({ url: "bolt://localhost:7687", username: "neo4j", password: "password", database: "neo4j", // 可选,默认 "neo4j" }); // 获取数据库 schema console.log(graph.getSchema()); // 执行 Cypher 查询 const results = await graph.query("MATCH (n:Person) RETURN n.name LIMIT 10"); // 关闭连接 await graph.close();Neo4jGraph.initialize的完整配置项定义在 src/graphs/neo4j_graph.ts:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | string | 必填 | Bolt 连接地址,如bolt://localhost:7687 |
username/password | string | 必填 | 数据库认证凭据 |
database | string | "neo4j" | 目标数据库名(Memgraph 默认为"memgraph") |
timeoutMs | number | 无 | 事务超时时间(毫秒),透传给驱动的事务配置 |
enhancedSchema | boolean | false | 是否启用增强 Schema 自省 |
从源码看,initialize是一个异步工厂方法,内部执行三步:创建驱动实例(若 URL/凭据非法会抛出 "Could not create a Neo4j driver instance..." 错误)→verifyConnectivity()(调用driver.getServerInfo()验证连通性)→refreshSchema()刷新 Schema 缓存,随后将实例返回。其中refreshSchema依赖 Neo4j 的 APOC 插件(apoc.meta.data()),若插件缺失会抛出明确提示,要求安装 APOC 并在配置中放行该过程。
query方法返回记录数组(内部会把 Neo4j 的Int整数、Node、Relationship、Path等类型递归转换为普通 JS 对象,方便直接消费),默认使用WRITE路由,可通过第三参数指定READ路由。
Enhanced Schema:更细粒度的元数据自省
默认情况下getSchema()返回的是格式化后的精简 Schema 字符串(如Person {name: STRING, age: INTEGER})。开启enhancedSchema: true后,初始化过程会进一步调用apoc.meta.graphSample()并逐标签/关系类型执行统计分析,产出更丰富的属性信息:
const graph = await Neo4jGraph.initialize({ url: "bolt://localhost:7687", username: "neo4j", password: "password", enhancedSchema: true, });从 src/graphs/neo4j_graph.ts 的实现看,增强模式的行为由几个关键阈值控制:
DISTINCT_VALUE_LIMIT = 10:字符串属性的去重值数量小于等于 10 时,Schema 中展示完整的Available options: ...取值列表;超过 10 则只给出一个Example示例值,避免 Schema 过长;EXHAUSTIVE_SEARCH_LIMIT = 10000:标签对应节点数小于 1 万时走穷举统计(收集去重值、min/max、distinct count),否则只采样前 5 个节点,控制自省开销;LIST_LIMIT = 128:LIST 类型属性的最小尺寸超过 128 时跳过,不纳入 Schema;- 数字类型(INTEGER/FLOAT/DATE 等)输出
Min/Max与去重计数;BOOLEAN、POINT、DURATION属性不参与增强统计。
增强 Schema 还支持利用数据库已有 RANGE 索引,通过apoc.schema.properties.distinct快速获取去重值,避免全表扫描。
向图数据库写入结构化图文档
Neo4jGraph支持将"从文本中抽取出的节点与关系"以结构化图文档的形式写入数据库,这为构建知识图谱类应用(例如从文档抽取实体与关系后入库)提供了直接通路。
import { Neo4jGraph, Node, Relationship, GraphDocument, } from "@langchain/neo4j"; import { Document } from "@langchain/core/documents"; const source = new Document({ pageContent: "Alice works at Acme Corp.", metadata: { id: "doc1" }, }); const alice = new Node({ id: "alice", type: "Person", properties: { name: "Alice" }, }); const acme = new Node({ id: "acme", type: "Company", properties: { name: "Acme Corp" }, }); const worksAt = new Relationship({ source: alice, target: acme, type: "WORKS_AT", }); const graphDoc = new GraphDocument({ nodes: [alice, acme], relationships: [worksAt], source, }); await graph.addGraphDocuments([graphDoc]);三种数据结构定义在 src/graphs/document.ts:
Node:{ id, type, properties },type默认"Node",即图中的标签(Label);Relationship:{ source, target, type, properties },连接两个节点,type为关系类型;GraphDocument:{ nodes, relationships, source },source记录该图文档的来源Document。
addGraphDocuments的底层实现(src/graphs/neo4j_graph.ts)有几个值得注意的细节:
- 若
source.metadata.id缺失,会自动用sha256(pageContent)生成稳定文档 ID; - 节点写入使用
apoc.merge.node,以id为键做 MERGE(存在即更新,不存在则创建),保证幂等; - 关系类型会被
replace(/ /g, "_").toUpperCase()规范化(空格转下划线、统一大写); - 可选配置
AddGraphDocumentsConfig支持两个开关:
await graph.addGraphDocuments([graphDoc], { baseEntityLabel: true, // 为所有实体附加统一的基础标签 "__Entity__" includeSource: true, // 在图中同时创建 Document 节点并建立 (:Document)-[:MENTIONS]->(实体) 关系 });baseEntityLabel: true时,写入前会自动创建__Entity__标签上的id唯一约束(对应源码导出的常量BASE_ENTITY_LABEL),并先 MERGE 实体节点、再通过apoc.create.addLabels追加其具体标签;includeSource: true则通过INCLUDE_DOCS_QUERY将文档内容与元数据一并入库并建立MENTIONS关系,便于溯源。
Neo4jVectorStore:基于向量索引的检索存储
Neo4jVectorStore是 Neo4j 官方 vector index 之上的向量存储实现,在初始化时会自动创建向量索引,支持向量检索、混合检索(向量 + 全文)以及元数据过滤。
从文档创建向量存储
import { Neo4jVectorStore } from "@langchain/neo4j"; import { OpenAIEmbeddings } from "@langchain/openai"; const embeddings = new OpenAIEmbeddings(); const vectorStore = await Neo4jVectorStore.fromDocuments(docs, embeddings, { url: "bolt://localhost:7687", username: "neo4j", password: "password", indexName: "vector", nodeLabel: "Chunk", textNodeProperty: "text", embeddingNodeProperty: "embedding", }); // 相似度检索 const results = await vectorStore.similaritySearch("What is Neo4j?", 4); // 带分数的相似度检索 const resultsWithScore = await vectorStore.similaritySearchWithScore( "What is Neo4j?", 4 ); // 关闭连接 await vectorStore.close();Neo4jVectorStoreArgs的完整配置定义在 src/vectorstores/neo4j_vector.ts,常用项及源码中的默认值如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
url/username/password | 必填 | 连接信息 |
database | "neo4j" | 目标数据库 |
indexName | "vector" | 向量索引名 |
nodeLabel | "Chunk" | 存储向量的节点标签 |
textNodeProperty | "text" | 文本内容属性名 |
embeddingNodeProperty | "embedding" | 向量属性名 |
keywordIndexName | "keyword" | 全文索引名(hybrid 模式用) |
searchType | "vector" | "vector"或"hybrid" |
indexType | "NODE" | "NODE"或"RELATIONSHIP"(后者查询关系向量) |
preDeleteCollection | false | 初始化时先删除同名索引与数据 |
retrievalQuery | 自动生成 | 自定义检索 Cypher(返回 text/metadata/score) |
textNodeProperties | [] | 多文本属性(fromExistingGraph等场景) |
createIdIndex | true | 是否为节点id创建唯一约束 |
底层初始化流程(initialize)会:创建驱动 →verifyAuthentication验证凭据 → 用embeddings.embedQuery("foo")探测向量维度 → 检查同名索引是否存在,不存在则通过db.index.vector.createNodeIndex创建(相似度度量固定为cosine,即DistanceStrategy默认值)。
写入文档时(addVectors)使用MERGE (c:\Chunk` {id: row.id})按id幂等写入,再通过db.create.setVectorProperty设置向量属性,批量事务按 1000 行切分。fromTexts与fromDocuments` 语义一致,只是入参为纯文本数组。
Hybrid Search:向量 + 全文混合检索
searchType: "hybrid"时,检索同时执行向量查询与全文(full-text)查询,两者各自按最高分归一化后取最大值融合排序:
const vectorStore = await Neo4jVectorStore.fromDocuments(docs, embeddings, { url: "bolt://localhost:7687", username: "neo4j", password: "password", searchType: "hybrid", indexName: "vector", keywordIndexName: "keyword", nodeLabel: "Chunk", });混合模式的实现细节(src/vectorstores/neo4j_vector.ts):
- 初始化时若检测不到指定
keywordIndexName的全文索引,会自动执行CREATE FULLTEXT INDEX keyword FOR (n:\Chunk`) ON EACH [n.`text`]` 创建; - 查询阶段先分别调用
db.index.vector.queryNodes与db.index.fulltext.queryNodes,各自将原始分数除以组内最大分实现 0–1 归一化,再对两条结果取max(score)排序取 Top-K; - 全文查询的输入会先经过
removeLuceneChars(源码位于 neo4j_vector.ts),将+ - & | ! ( ) { } [ ] ^ " ~ * ? : \等 Lucene 特殊字符替换为空格,避免查询语法被意外注入。
连接已有索引
若索引已在 Neo4j 中预先创建(例如由运维或其它流程建立),用fromExistingIndex直接接入,无需重新建索引:
const vectorStore = await Neo4jVectorStore.fromExistingIndex(embeddings, { url: "bolt://localhost:7687", username: "neo4j", password: "password", indexName: "my_existing_index", });该方法会通过SHOW INDEXES校验索引存在性与维度匹配:索引不存在会抛出提示核对索引名;嵌入函数维度与索引维度不一致会明确报错。hybrid 模式则额外要求全文索引存在,且向量索引与全文索引必须指向同一个节点标签。
补充:源码还提供了fromExistingGraph静态方法,用于对已有图数据按指定文本属性批量生成向量并回填(db.create.setVectorProperty),适合对存量节点做离线向量化。
元数据过滤
similaritySearch/similaritySearchWithScore的第三个参数支持filter,按节点属性过滤检索结果(要求 Neo4j 5.18+):
const results = await vectorStore.similaritySearch("query", 4, { filter: { category: "science", year: { $gte: 2020 }, }, });完整支持的操作符(源码 neo4j_vector.ts 中SUPPORTED_OPERATORS):
| 操作符 | Cypher 映射 | 说明 |
|---|---|---|
$eq | = | 相等(无操作符的裸值默认视为$eq) |
$ne | <> | 不等 |
$lt/$lte/$gt/$gte | <<=>>= | 大小比较 |
$in/$nin | IN/NOT IN | 集合包含/不包含(元素须为 string/number/boolean) |
$like | CONTAINS | 子串包含(注意:实现为 CONTAINS,值尾部通配符会被截掉) |
$ilike | toLower(...) CONTAINS | 大小写不敏感的子串包含 |
$between | 低值 <= n.field <= 高值 | 区间过滤,值为[low, high] |
$and/$or | AND/OR | 逻辑组合,值为子过滤条件数组 |
过滤实现位于constructMetadataFilter(neo4j_vector.ts):多字段同时出现时自动以AND拼接;字段名需符合合法标识符正则,否则抛错;参数名按位置自动编号(param_1、param_2_low等),杜绝 Cypher 注入。几个使用限制需要留意:
- 过滤仅支持
searchType: "vector",与 hybrid 检索互斥(组合使用会直接抛错); - 依赖版本检查(源码
_verifyVersion):向量索引要求 Neo4j5.11+,元数据过滤要求5.18+;Aura 版本会单独做版本解析; - Enterprise 版会为过滤查询附加
CYPHER runtime = parallel并行运行时前缀以提升性能。
Neo4jChatMessageHistory:图数据库中的对话记忆
Neo4jChatMessageHistory将聊天消息持久化到 Neo4j,适合多轮对话场景下的消息存取,与 LangChain 的RunnableWithMessageHistory等组件可无缝组合。
import { Neo4jChatMessageHistory } from "@langchain/neo4j"; import { HumanMessage, AIMessage } from "@langchain/core/messages"; const history = await Neo4jChatMessageHistory.initialize({ url: "bolt://localhost:7687", username: "neo4j", password: "password", sessionId: "my-session-id", // 可选,缺省时自动生成 UUID windowSize: 5, // 可选,默认 3 }); // 追加消息 await history.addMessage(new HumanMessage("Hello!")); await history.addMessage(new AIMessage("Hi there! How can I help you?")); // 读取消息 const messages = await history.getMessages(); // 清空历史 await history.clear(); // 关闭连接 await history.close();配置项定义在 src/stores/message/neo4j.ts,除连接信息外还有三个可选参数:
| 配置项 | 默认值 | 说明 |
|---|---|---|
sessionId | 自动生成uuidv4() | 会话标识,同一会话共享历史 |
sessionNodeLabel | "ChatSession" | 会话节点的标签 |
messageNodeLabel | "ChatMessage" | 消息节点的标签 |
windowSize | 3 | 读取时保留的消息窗口大小(读取最近消息条数) |
从源码看,其存储模型是一个双向链表式的图结构:每个会话有一个ChatSession节点,通过LAST_MESSAGE关系指向最后一条消息;消息节点之间用NEXT关系串成链。addMessage会用一条 Cypher 完成"创建新消息节点、断开旧 LAST_MESSAGE、建立新 LAST_MESSAGE、并让旧末条消息指向新消息"的原子操作;getMessages通过<-[:NEXT*0..windowSize*2-1]-()沿链回溯最多windowSize条消息(每次读写各占一个单位),再借助@langchain/core/messages的mapStoredMessagesToChatMessages还原为标准BaseMessage对象(Human/AI/System 等类型由节点上的type属性区分)。
该组件继承自BaseListChatMessageHistory,因此可以直接传给 LangChain 的RunnableWithMessageHistory等记忆机制,用于多轮对话:
import { RunnableWithMessageHistory } from "@langchain/core/runnables"; const withHistory = new RunnableWithMessageHistory({ runnable: model, // 任意 Runnable(如 ChatOpenAI) getMessageHistory: () => history, inputMessagesKey: "input", historyMessagesKey: "history", }); await withHistory.invoke( { input: "继续刚才的话题" }, { configurable: { sessionId: "my-session-id" } } );GraphCypherQAChain:自然语言驱动的图问答
GraphCypherQAChain是一个"Text-to-Cypher"问答链:将用户问题交给 LLM 生成 Cypher 查询,在图上执行查询,再把结果交给 LLM 综合成自然语言答案。它继承自@langchain/classic的BaseChain,因此需要将@langchain/classic作为可选依赖安装(见 package.json 的 peerDependenciesMeta 声明)。
import { Neo4jGraph, GraphCypherQAChain } from "@langchain/neo4j"; import { ChatOpenAI } from "@langchain/openai"; const graph = await Neo4jGraph.initialize({ url: "bolt://localhost:7687", username: "neo4j", password: "password", }); const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }); const chain = GraphCypherQAChain.fromLLM({ graph, llm, returnIntermediateSteps: true, }); const result = await chain.invoke({ query: "Who played in Pulp Fiction?" }); console.log(result.result);也可以拆分"生成 Cypher"与"生成答案"两个环节使用不同模型(例如用更强的模型写 Cypher、用更经济的模型总结答案):
const chain = GraphCypherQAChain.fromLLM({ graph, cypherLLM: new ChatOpenAI({ model: "gpt-4o", temperature: 0 }), qaLLM: new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }), });fromLLM的可配置项(源码 src/chains/graph_qa/cypher.ts):
| 配置项 | 默认值 | 说明 |
|---|---|---|
graph | 必填 | Neo4jGraph实例(链会读取getSchema()作为 Cypher 生成上下文) |
llm | - | 单模型,同时承担 Cypher 生成与答案合成 |
cypherLLM/qaLLM | - | 分别指定两个模型(与llm二选一) |
cypherPrompt/qaPrompt | 内置模板 | 自定义提示词模板 |
returnIntermediateSteps | false | 返回intermediateSteps(含生成的 Cypher 与查询结果),便于调试 |
returnDirect | false | 为true时只返回查询结果、跳过答案合成 |
topK | 10 | 限制查询返回的路径数量(追加到生成的 Cypher 中) |
inputKey/outputKey | "query"/"result" | 输入输出键名 |
链的执行分为三个阶段:① 用内置的CYPHER_GENERATION_PROMPT将图 Schema 与问题喂给 LLM 生成 Cypher;② 通过graph.query在图上执行;③ 将结果连同原问题交给CYPHER_QA_PROMPT合成最终答案。两个提示词模板定义在 src/chains/graph_qa/prompts.ts,均从 src/index.ts 导出,可复用或覆盖。该链的实现与提示词均有对应测试: tests/cypher.test.ts 与 tests/prompts.test.ts。
MemgraphGraph:Bolt 协议兼容的图数据库接入
MemgraphGraph是Neo4jGraph的子类(源码 src/graphs/memgraph_graph.ts),用于接入同样基于 Bolt 协议的 Memgraph。与 Neo4j 的差异主要在 Schema 自省方式上:Memgraph 通过其 LLM 工具过程CALL llm_util.schema("raw")获取原始 Schema,再在客户端格式化为与Neo4jGraph一致的结构化形式。
import { MemgraphGraph } from "@langchain/neo4j"; const graph = await MemgraphGraph.initialize({ url: "bolt://localhost:7687", username: "memgraph", password: "password", }); console.log(graph.getSchema()); await graph.close();MemgraphGraphConfig仅含url/username/password/database(默认"memgraph"),不支持enhancedSchema(其 Schema 输出格式为Node name: '...', Node properties: {...}与(:Start)-[:TYPE]->(:End)形式的文本)。
安全注意事项
本文档涉及的数据库组件(图封装、向量存储、聊天历史)都接受连接凭据,并且GraphCypherQAChain会执行 LLM 生成的 Cypher 语句。官方安全文档(见 LangChain 安全文档)与源码中各组件的安全注释均强调:
- 凭据最小化:使用仅包含必要权限的窄范围凭据,优先创建只读用户,防止被诱导执行删除、变更数据或读取敏感数据的操作;
- 这尤其重要——Cypher 是强表达能力的查询语言,一旦 LLM 生成的语句被注入或误用,可能导致数据损坏或泄露。
从 @langchain/community 迁移
若此前使用@langchain/community中的 Neo4j 集成,迁移只需将分散的导入收敛到新包:
// 迁移前 import { Neo4jGraph } from "@langchain/community/graphs/neo4j_graph"; import { Neo4jVectorStore } from "@langchain/community/vectorstores/neo4j_vector"; import { Neo4jChatMessageHistory } from "@langchain/community/stores/message/neo4j"; import { GraphCypherQAChain } from "@langchain/community/chains/graph_qa/cypher"; // 迁移后 import { Neo4jGraph, Neo4jVectorStore, Neo4jChatMessageHistory, GraphCypherQAChain, } from "@langchain/neo4j";本地开发与测试
若要在本仓库内开发调试该包,使用 pnpm 工作区命令(对应 package.json 中的 scripts):
# 安装依赖 pnpm install # 构建 pnpm --filter @langchain/neo4j build # 运行测试 pnpm --filter @langchain/neo4j test # Lint pnpm --filter @langchain/neo4j lint仓库为各组件提供了完整的单元测试与集成测试参考实现:图与 Schema 相关见 graphs/tests/neo4j_graph.test.ts、graphs/tests/document.test.ts 与 graphs/tests/memgraph_graph.test.ts,向量检索见 vectorstores/tests/neo4j_vector.test.ts,消息历史见 stores/message/tests/neo4j.test.ts。阅读这些测试可以快速掌握各组件在真实(或模拟)数据库上的调用方式与边界行为。
【免费下载链接】langchainjsThe agent engineering platform项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考