Using a Knowledge Graph to implement a DevOps RAG application

使用知识图谱实现 DevOps RAG 应用

8 分钟阅读

编者注:本文与 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 浏览器中检查图谱,您应该会得到类似的visualization。

DevOps 图谱的子集。作者供图。

蓝色节点描述微服务。这些微服务可能彼此之间存在依赖关系,这意味着一个微服务的功能或结果可能依赖于另一个微服务的运行。另一方面,棕色节点代表直接链接到这些微服务的任务。除了展示事物的设置方式及其链接的任务之外,我们的图谱还展示了哪些团队负责哪些内容。

Neo4j 向量索引

我们将首先实现向量索引搜索,以通过任务的名称和描述查找相关任务。如果您不熟悉向量相似性搜索,让我给您快速复习一下。关键思想是基于每个任务的描述和名称计算其文本嵌入值。然后,在查询时,使用相似性指标(如余弦距离)找到与用户输入最相似的任务。

RAG 应用程序中的向量相似性搜索。作者供图。

从向量索引检索到的信息随后可以用作 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 参数中构造了一个具有定义属性的类似 map 或字典的字符串响应。

现在,我们可以通过将向量索引包装到 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 轻松解决此问题,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 中基于图谱的搜索的更多信息 here

代码可在 GitHub 上找到。

如果您渴望了解更多关于使用图谱构建 AI 应用程序的信息,请加入我们在 2023 年 10 月 26 日由 Neo4j 组织的 NODES,在线 24 小时会议