编者按:本文是与 Neo4J 团队合作撰写的。
RAG 应用目前非常流行。大家都在构建公司的文档聊天机器人或类似应用。大多数这类应用都有一个共同点,那就是它们的知识来源是非结构化文本,这些文本会被分块并以某种方式进行嵌入。然而,并非所有信息都以非结构化文本的形式出现。
举个例子,假设您想创建一个能够回答有关微服务架构、正在进行的任务等问题的聊天机器人。任务大多是以非结构化文本定义的,所以这方面与常规 RAG 工作流没有区别。但是,您如何准备有关微服务架构的信息,以便聊天机器人能够检索到最新的信息呢?一种选择是创建架构的每日快照,并将其转换为 LLM 可以理解的文本。但是,有没有更好的方法呢?这就是知识图谱,它可以将结构化和非结构化信息存储在同一个数据库中。

节点和关系用于在知识图谱中描述数据。通常,节点用于表示实体或概念,如人、组织和地点。在微服务图谱示例中,节点描述了人、团队、微服务和任务。另一方面,关系用于定义这些实体之间的连接,例如微服务之间的依赖关系或任务所有者。
节点和关系都可以拥有存储为键值对的属性值。

微服务节点有两个节点属性,分别描述它们的名称和技术。另一方面,任务节点更复杂。它们包含名称、状态、描述以及嵌入属性。通过将文本嵌入值作为节点属性存储,您可以对任务描述进行向量相似性搜索,这与将任务存储在向量数据库中一样。因此,知识图谱允许您存储和检索结构化和非结构化信息来支持您的 RAG 应用。
在这篇博文中,我将带您了解一个使用 LangChain 实现基于知识图谱的 RAG 应用的场景,以支持您的 DevOps 团队。代码可在 GitHub 上找到。
Neo4j 环境设置
您需要设置 Neo4j 5.11 或更高版本才能跟随本博文中的示例。最简单的方法是在 Neo4j Aura 上启动一个免费实例,它提供 Neo4j 数据库的云实例。或者,您也可以通过下载 Neo4j Desktop 应用程序并创建一个本地数据库实例来设置本地 Neo4j 数据库实例。from langchain.graphs import Neo4jGraph。
from langchain.graphs import Neo4jGraph
url = "neo4j+s://databases.neo4j.io"
username ="neo4j"
password = ""
graph = Neo4jGraph(
url=url,
username=username,
password=password
)数据集
知识图谱非常擅长连接来自多个数据源的信息。在开发 DevOps RAG 应用时,您可以从云服务、任务管理工具等获取信息。

import requests
url = "https://gist.githubusercontent.com/tomasonjo/08dc8ba0e19d592c4c3cde40dd6abcc3/raw/da8882249af3e819a80debf3160ebbb3513ee962/microservices.json"
import_query = requests.get(url).json()['query']
graph.query(
import_query
)
如果您在 Neo4j Browser 中检查图谱,应该会看到类似的可视化效果。

蓝色节点描述微服务。这些微服务可能相互依赖,这意味着一个微服务的功能或结果可能依赖于另一个微服务的运行。另一方面,棕色节点代表直接链接到这些微服务的任务。除了展示设置以及它们关联的任务之外,我们的图谱还显示了哪些团队负责哪些工作。
Neo4j 向量索引
我们将从实现向量索引搜索开始,用于按名称和描述查找相关任务。如果您不熟悉向量相似性搜索,请允许我快速回顾一下。关键思想是根据每个任务的描述和名称计算文本嵌入值。然后,在查询时,使用余弦距离等相似度度量来查找与用户输入最相似的任务。

然后,可以从向量索引检索到的信息作为 LLM 的上下文,以便它能够生成准确和最新的答案。
任务已经存在于我们的知识图谱中。但是,我们需要计算嵌入值并创建向量索引。这可以通过 `from_existing_graph` 方法实现。
import os
from langchain.vectorstores.neo4j_vector import Neo4jVector
from langchain.embeddings.openai import OpenAIEmbeddings
os.environ['OPENAI_API_KEY'] = "OPENAI_API_KEY"
vector_index = Neo4jVector.from_existing_graph(
OpenAIEmbeddings(),
url=url,
username=username,
password=password,
index_name='tasks',
node_label="Task",
text_node_properties=['name', 'description', 'status'],
embedding_node_property='embedding',
)在此示例中,我们为 `from_existing_graph` 方法使用了以下特定于图谱的参数。
- index_name: 向量索引的名称
- node_label: 相关节点的节点标签
- text_node_properties: 用于计算嵌入值并从向量索引中检索的属性
- embedding_node_property: 用于存储嵌入值的属性
现在向量索引已初始化,我们可以像在 LangChain 中使用任何其他向量索引一样使用它。
response = vector_index.similarity_search(
"How will RecommendationService be updated?"
)
print(response[0].page_content)
# name: BugFix
# description: Add a new feature to RecommendationService to provide ...
# status: In Progress您可以注意到,我们在 `text_node_properties` 参数中构造了一个具有定义属性的映射或类似字典的字符串作为响应。
现在,我们可以轻松创建聊天机器人响应,只需将向量索引包装到 `RetrievalQA` 模块中。
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
vector_qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(),
chain_type="stuff",
retriever=vector_index.as_retriever()
)
vector_qa.run(
"How will recommendation service be updated?"
)
# The RecommendationService is currently being updated to include a new feature
# that will provide more personalized and accurate product recommendations to
# users. This update involves leveraging user behavior and preference data to
# enhance the recommendation algorithm. The status of this update is currently
# in progress.
向量索引通常有一个限制,那就是它们不像 Cypher 这样的结构化查询语言那样提供聚合信息的能力。例如,以下示例
vector_qa.run(
"How many open tickets there are?"
)
# There are 4 open tickets.
响应看起来有效,LLM 使用了断言性的语言,让您相信结果是正确的。但是,问题在于响应直接与从向量索引检索到的文档数量相关,而该数量默认为四。实际上发生的是,向量索引检索到四个开放的票据,LLM 不加质疑地认为那就是所有开放的票据。然而,事实并非如此,我们可以使用 Cypher 语句来验证这一点。
graph.query(
"MATCH (t:Task {status:'Open'}) RETURN count(*)"
)
# [{'count(*)': 5}]我们的玩具图中有五个开放的任务。虽然向量相似性搜索在筛选非结构化文本中的相关信息方面非常出色,但它缺乏分析和聚合结构化信息的能力。使用 Neo4j,可以通过使用 Cypher(一种用于图数据库的结构化查询语言)轻松解决此问题。
Graph Cypher 搜索
Cypher 是一种用于与图数据库交互的结构化查询语言,它提供了一种可视化匹配模式和关系的方式。它依赖于以下 **ascii**-**art** 类型的语法
(:Person {name:"Tomaz"})-[:LIVES_IN]->(:Country {name:"Slovenia"})
此模式描述了一个具有 **Person** 标签和 **Tomaz** 名称属性的节点,该节点与 **Country** 节点 **Slovenia** 存在 **LIVES_IN** 关系。
LangChain 的一个优点是它提供了一个 GraphCypherQAChain,它可以为您生成 Cypher 查询,这样您就不必学习 Cypher 语法即可从 Neo4j 等图数据库中检索信息。
以下代码将刷新图谱模式并实例化 Cypher 链。
from langchain.chains import GraphCypherQAChain
graph.refresh_schema()
cypher_chain = GraphCypherQAChain.from_llm(
cypher_llm = ChatOpenAI(temperature=0, model_name='gpt-4'),
qa_llm = ChatOpenAI(temperature=0), graph=graph, verbose=True,
)生成有效的 Cypher 语句是一项复杂的任务。因此,建议使用最先进的 LLM,例如 gpt-4 来生成 Cypher 语句,而使用数据库上下文生成答案则可以留给 gpt-3.5-turbo。
现在,您可以就开放票据的数量提出相同的问题。
cypher_chain.run(
"How many open tickets there are?"
)结果如下

您还可以让链使用各种分组键来聚合数据,例如以下示例。
cypher_chain.run(
"Which team has the most open tasks?"
)结果如下

您可能会说这些聚合不是基于图谱的操作,您是正确的。当然,我们可以执行更多基于图谱的操作,例如遍历微服务的依赖图。
cypher_chain.run(
"Which services depend on Database directly?"
)结果如下

当然,您还可以让链生成 可变长度路径遍历,通过提出类似以下问题
cypher_chain.run(
"Which services depend on Database indirectly?"
)
提到的某些服务与直接依赖性问题中的服务相同。原因是依赖图的结构,而不是无效的 Cypher 语句。
知识图谱代理
由于我们为知识图谱的结构化和非结构化部分实现了单独的工具,我们可以添加一个代理,该代理可以使用这两个工具来探索知识图谱。
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
tools = [
Tool(
name="Tasks",
func=vector_qa.run,
description="""Useful when you need to answer questions about descriptions of tasks.
Not useful for counting the number of tasks.
Use full question as input.
""",
),
Tool(
name="Graph",
func=cypher_chain.run,
description="""Useful when you need to answer questions about microservices,
their dependencies or assigned people. Also useful for any sort of
aggregation like counting the number of tasks, etc.
Use full question as input.
""",
),
]
mrkl = initialize_agent(
tools,
ChatOpenAI(temperature=0, model_name='gpt-4'),
agent=AgentType.OPENAI_FUNCTIONS, verbose=True
)让我们试试代理的效果。
response = mrkl.run("Which team is assigned to maintain PaymentService?")
print(response)结果如下

让我们现在尝试调用 **Tasks** 工具。
response = mrkl.run("Which tasks have optimization in their description?")
print(response)结果如下

有一点是肯定的。我必须提高我的代理提示工程技能。工具描述肯定有改进的空间。此外,您还可以自定义代理提示。
结论
当您需要结构化和非结构化数据来支持您的 RAG 应用时,知识图谱是一个绝佳的选择。通过本博文中介绍的方法,您可以避免多语言架构,从而不必维护和同步多种类型的数据库。您可以在此处 了解更多关于 LangChain 中基于图谱的搜索。
代码可在 GitHub 上找到。
如果您渴望了解更多关于使用图谱构建 AI 应用的知识,请加入我们参加由 Neo4j 于 2023 年 10 月 26 日举办的在线 24 小时会议 NODES。