了解如何自定义 LangChain 的 Neo4j 向量索引封装器
编者按:这篇文章是与 Neo4j 团队合作撰写的。我们一直在与他们紧密合作开发他们的新向量索引,并且我们对其高效地对非结构化文本或其他嵌入式数据模式执行语义搜索的能力印象深刻,从而为 RAG 应用和更多自定义解锁了支持。
Neo4j 过去和现在都非常适合处理结构化信息,但由于其暴力方法,在语义搜索方面有点吃力。然而,这种吃力已成为过去,因为 Neo4j 在 5.11 版本中引入了新的向量索引,旨在高效地对非结构化文本或其他嵌入式数据模式执行语义搜索。新添加的向量索引使 Neo4j 非常适合大多数 RAG 应用,因为它现在可以很好地处理结构化和非结构化数据。

这篇博文旨在引导您了解 LangChain 中 Neo4j 向量索引实现的所有自定义选项。
代码可在 GitHub 上找到。
Neo4j 环境设置
您需要设置 Neo4j 5.11 或更高版本才能按照这篇博文中的示例进行操作。最简单的方法是在 Neo4j Aura 上启动一个免费实例,它提供 Neo4j 数据库的云实例。或者,您也可以通过下载 Neo4j Desktop 应用程序并创建一个本地数据库实例来设置 Neo4j 数据库的本地实例。
示例数据集
为了这篇博文的目的,我们将使用 WikipediaLoader
从 Witcher 页面获取文本。
from langchain.document_loaders import WikipediaLoader
from langchain.text_splitter import CharacterTextSplitter
# Read the wikipedia article
raw_documents = WikipediaLoader(query="The Witcher").load()
# Define chunking strategy
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size=1000, chunk_overlap=20
)
# Chunk the document
documents = text_splitter.split_documents(raw_documents)
# Remove the summary
for d in documents:
del d.metadata["summary"]
Neo4j 向量索引自定义
每个文本块在 Neo4j 中都存储为单个孤立节点。

默认情况下,LangChain 中的 Neo4j 向量索引实现使用 Chunk
节点标签表示文档,其中 text
属性存储文档的文本,embedding
属性保存文本的向量表示。该实现允许您自定义节点标签、文本和嵌入属性名称。
neo4j_db = Neo4jVector.from_documents(
documents,
OpenAIEmbeddings(),
url=url,
username=username,
password=password,
database="neo4j", # neo4j by default
index_name="wikipedia", # vector by default
node_label="WikipediaArticle", # Chunk by default
text_node_property="info", # text by default
embedding_node_property="vector", # embedding by default
create_id_index=True, # True by default
)
在本示例中,我们指定要将文本块存储在 WikipediaArticle
节点标签下,其中 info
属性用于存储文本,vector
属性保存文本嵌入表示。如果您运行上面的示例,您应该在数据库中看到以下信息。

如前所述,我们将 info
属性定义为包含文本信息,而 vector
属性用于存储嵌入。任何其他属性(如 source
和 title
)都被视为文档元数据。
默认情况下,我们还在指定节点标签的 id 属性上创建一个 唯一节点属性约束,以加快导入速度。如果您不想创建唯一约束,可以将 create_id_index
设置为 false。您可以使用以下 Cypher 语句验证约束是否已创建
neo4j_db.query("SHOW CONSTRAINTS")
#[{'id': 4,
# 'name': 'constraint_e5da4d45',
# 'type': 'UNIQUENESS',
# 'entityType': 'NODE',
# 'labelsOrTypes': ['WikipediaArticle'],
# 'properties': ['id'],
# 'ownedIndex': 'constraint_e5da4d45',
# 'propertyType': None}]
正如您所预料的那样,我们还创建了一个向量索引,这将使我们能够执行快速的 ANN 搜索。
neo4j_db.query(
"""SHOW INDEXES
YIELD name, type, labelsOrTypes, properties, options
WHERE type = 'VECTOR'
"""
)
#[{'name': 'wikipedia',
# 'type': 'VECTOR',
# 'labelsOrTypes': ['WikipediaArticle'],
# 'properties': ['vector'],
# 'options': {'indexProvider': 'vector-1.0',
# 'indexConfig': {'vector.dimensions': 1536,
# 'vector.similarity_function': 'cosine'}}}]
LangChain 实现创建了一个名为 wikipedia
的向量索引,该索引对 WikipediaArticle
节点的 vector
属性进行索引。此外,提供的配置告知我们,向量嵌入维度为 1536
,并使用 cosine
相似度函数。
加载更多文档
您可以使用 add_documents
方法将更多文档加载到实例化的向量索引中。
neo4j_db.add_documents(
[
Document(
page_content="LangChain is the coolest library since the Library of Alexandria",
metadata={"author": "Tomaz", "confidence": 1.0}
)
],
ids=["langchain"],
)
LangChain 允许您为 add_document
方法提供文档 ID,这可以用于跨不同系统同步信息,并使更新或删除相关文本块变得更容易。

加载现有索引
如果您在 Neo4j 中有一个包含数据的现有向量索引,则可以使用 from_existing_method
连接到它。
existing_index = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
url=url,
username=username,
password=password,
index_name="wikipedia",
text_node_property="info", # Need to define if it is not default
)
首先,from_existing_method
检查数据库中是否实际存在具有提供名称的索引。如果存在,它可以从索引配置映射中检索节点标签和嵌入节点属性,这意味着您不必手动设置这些。
print(existing_index.node_label) # WikipediaArticle
print(existing_index.embedding_node_property) # vector
但是,索引信息不包含文本节点属性信息。因此,如果您使用默认属性 (text
) 以外的任何属性,请使用 text_node_property
参数指定它。
自定义检索查询
由于 Neo4j 是原生图形数据库,LangChain 中的向量索引实现允许自定义和丰富返回的信息。但是,此功能适用于更高级的用户,因为您负责自定义数据加载以及检索。
retrieval_query
参数允许您收集、转换或计算要从相似性搜索返回的任何其他图形信息。为了更好地理解它,我们可以查看代码中的实际实现。
read_query = (
"CALL db.index.vector.queryNodes($index, $k, $embedding) "
"YIELD node, score "
) + retrieval_query
从代码中,我们可以看到向量相似性搜索是硬编码的。但是,我们可以选择添加任何中间步骤并返回其他信息。检索查询必须返回以下三列
- text (字符串):这通常是与已检索节点关联的文本数据。这可以是节点的主要内容、名称、描述或任何其他基于文本的信息。
- score (浮点数):这表示查询向量与返回节点关联的向量之间的相似度得分。该分数量化查询与返回节点的相似程度,通常在 0 到 1 的范围内
- metadata (字典):这是一个更灵活的列,可以包含有关节点或搜索的其他信息。它可以是一个字典(或映射),其中包含各种属性或属性,这些属性或属性为返回的节点提供更多上下文。
我们将向 WikipediaArticle
节点添加关系以演示此功能。
existing_index.query(
"""MATCH (w:WikipediaArticle {id:'langchain'})
MERGE (w)<-[:EDITED_BY]-(:Person {name:"Galileo"})
"""
)
我们已向具有给定 ID 的 WikipediaArticle
节点添加了 EDITED_BY
关系。现在让我们测试一下自定义检索选项。
retrieval_query = """
OPTIONAL MATCH (node)<-[:EDITED_BY]-(p)
WITH node, score, collect(p) AS editors
RETURN node.info AS text,
score,
node {.*, vector: Null, info: Null, editors: editors} AS metadata
"""
existing_index_return = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
url=url,
username=username,
password=password,
database="neo4j",
index_name="wikipedia",
text_node_property="info",
retrieval_query=retrieval_query,
)
我不会过多介绍 Cypher 的细节。您可以使用许多资源来学习基本语法以及更多内容,例如 Neo4j Graph Academy。要构建有效的检索查询,您必须知道来自向量相似性搜索的相关节点在 node
引用变量下可用,而相似性度量值在 score
引用下可用。
让我们尝试一下。
existing_index_return.similarity_search(
"What do you know about LangChain?", k=1)
#[
# Document("page_content=""LangChain is the coolest library since the Library of Alexandria",
# "metadata="{
# "author":"Tomaz",
# "confidence":1.0,
# "id":"langchain",
# "editors":[
# {
# "name":"Galileo"
# }
# ]
# }")"
#]
您可以观察到元数据信息包含 editor
属性,该属性是从图形信息计算得出的。
总结
Neo4j 中新添加的向量索引实现使其能够支持依赖于结构化和非结构化数据的 RAG 应用,使其非常适合高度复杂和关联的数据集。
代码可在 GitHub 上找到。