
前言
最近,使用大型语言模型(LLMs)和知识图谱(KG)开发 RAG(Retrieval Augmented Generation)流程引起了很大的关注。在这篇文章中,我将使用 LlamaIndex 和 NebulaGraph 来构建一个关于费城费利斯队(Philadelphia Phillies)的 RAG 流程。
应用场景
我们用的是开源的 NebulaGraph 来完成这次的知识图谱。用来查询费城费利斯队的信息。我们将使用费城费利斯队的维基百科页面作为我们的其中一个信息源数据源,另一个是 YouTube 下载的一段视频。
我们的高级架构图如下:

对于那些已经熟悉知识图和 NebulaGraph 的人,请跳到“详细实现”部分。对于 NebulaGraph 的新手,请继续阅读。
知识图谱(KG)
知识图谱是使用图结构的数据模型或拓扑来集成数据的知识库。它是一种表示有关现实世界实体及其相互关系的信息的方法。知识图谱通常用于驱动搜索引擎、推荐系统、社交网络等。
主要组件
知识图谱通常由两个主要组件组成:
顶点/节点:表示知识领域中的实体或对象。每个节点对应一个唯一的实体,并通过唯一标识符进行标识。例如,在关于费城费利斯队的知识图谱中,节点可能具有“费城费利斯队”和“大联盟棒球”等值。
边:表示两个节点之间的关系。例如,一个边“参与”可能连接“费城费利斯队”的节点和“大联盟棒球”的节点。
三元组
三元组是图中的基本数据单位,由三部分组成:
主体:三元组所关注的节点。
客体:关系指向的节点。
谓词:主体和客体之间的关系。
例如,在下面的三元组示例中,“费城费利斯队”是主体,“参与”是谓词,“大联盟棒球”是客体。
(费城费利斯队)-- [参与] ->(大联盟棒球)知识图谱数据库可以通过存储三元组来高效地存储和查询复杂的图数据。
Cypher 查询语言
Cypher 是一种声明性的图查询语言,由知识图谱支持。使用 Cypher,我们告诉知识图谱我们想要什么,但不告诉它如何做。这使得 Cypher 的查询更加可读和易于维护。Cypher 易于学习、使用,并且足够表达复杂的图查询。
这是一个简单的 Cypher 查询示例:
ngqlCopy codeMATCH (p:`entity`)-[e:relationship]->(m:`entity`)WHERE p.`entity`.`name` == '费城费利斯队'RETURN p, e, m;
这个查询将匹配与费城费利斯队相关的所有实体。
NebulaGraph
NebulaGraph 是市场上最好的知识图谱数据库之一。它是开源的、分布式的,并且能够处理包含数万亿边和顶点的大规模图,延迟为毫秒级。大公司广泛使用它进行各种应用,包括社交媒体、推荐系统、欺诈检测等。
安装
当前项目 RAG 流程,需在本地安装 NebulaGraph。可通过 Docker Desktop 来快速安装 NebulaGraph 。详细的安装说明可以在 NebulaGraph 的文档中找到(文末有链接地址)。
具体实施
请参考我的 GitHub 项目仓库,查看费城 RAG 流程的完整 Jupyter Notebook。(文末有链接地址)
步骤 1:安装和配置
除了 LlamaIndex 外,我们还需要安装几个库:
ipython-ngql:可以增强你从 Jupyter Notebook 或 iPython 连接到 NebulaGraph 的能力。
nebula3-python:用于连接和管理 NebulaGraph 数据库。
pyvis:能够使用最少的 Python 代码快速生成可视化网络图。
networkx:用于研究图和网络。
youtube_transcript_api:这是一个 Python API,允许你获取 YouTube 视频的剧本/字幕。
openai-whisper:openAI 提供的语言库
%pip install llama_index==0.9.21 ipython-ngql nebula3-python pyvis networkx youtube_transcript_api llama-hub openai-whisper pydub调整输出日志配置
import logging
import sys
logging.basicConfig(
stream=sys.stdout, level=logging.INFO
) # logging.DEBUG for more verbose output
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))配置 Azure API 相关信息
# For Azure OpenAI
from llama_index.llms import AzureOpenAI
from llama_index.embeddings import AzureOpenAIEmbedding
from llama_index import (
VectorStoreIndex,
SimpleDirectoryReader,
KnowledgeGraphIndex,
ServiceContext,
)
from llama_index.storage.storage_context import StorageContext
import logging
import sys
from IPython.display import Markdown, display
logging.basicConfig(
stream=sys.stdout, level=logging.INFO
) # logging.DEBUG for more verbose output
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))
api_key = "<api-key>"
azure_endpoint = "https://<your-resource-name>.openai.azure.com/"
api_version = "2023-07-01-preview"步骤 2:连接到 NebulaGraph 并设置新空间
假设你已经在本地安装了 NebulaGraph;现在可以开始配置 Jupyter Notebook 连接到它(注意:不要尝试从 Google Colab 连接到你的本地 NebulaGraph,我试过,不行)
按照下面的代码片段进行操作:
连接到你的本地 NebulaGraph(其默认密码为 nebula)。
在新空间中创建一个名为 phillies_rag 的空间。
在新空间中创建标签、边缘和标签索引。
import os
os.environ["GRAPHD_HOST"] = "127.0.0.1"
os.environ["NEBULA_USER"] = "root"
os.environ["NEBULA_PASSWORD"] = "nebula"
os.environ["NEBULA_ADDRESS"] = "127.0.0.1:9669"
%reload_ext ngql
connection_string = f"--address {os.environ['GRAPHD_HOST']} --port 9669 --user root --password {os.environ['NEBULA_PASSWORD']}"
%ngql {connection_string}
%ngql CREATE SPACE IF NOT EXISTS phillies_rag(vid_type=FIXED_STRING(256), partition_num=1, replica_factor=1);
%%ngql
USE phillies_rag;
CREATE TAG IF NOT EXISTS entity(name string);
CREATE EDGE IF NOT EXISTS relationship(relationship string);
%ngql CREATE TAG INDEX IF NOT EXISTS entity_index ON entity(name(256));在 NebulaGraph 中创建的新空间,继续构建 NebulaGraphStore。
from llama_index.storage.storage_context import StorageContext
from llama_index.graph_stores import NebulaGraphStore
space_name = "phillies_rag"
edge_types, rel_prop_names = ["relationship"], ["relationship"]
tags = ["entity"]
graph_store = NebulaGraphStore(
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
)
storage_context = StorageContext.from_defaults(graph_store=graph_store)步骤 3:加载数据并创建 KG 索引
我们的源数据来自于费城费城人的维基百科页面以及关于2023年8月Trea Turner YouTube 视频。
我们首先判断本地 storage_context 是否有 KG 索引。如果存在,我们就加载这个索引。如果不存在,比如第一次访问应用程序时,我们需要加载这个源文档,然后构建 KG 索引,并将文档存储、索引存储和向量存储持久化到项目根目录下的 storage_graph 目录中。(注意:如果视频链接不上,请将视频下载下来,放进 graph_data 文件中)
from llama_index import load_index_from_storage
from llama_hub.youtube_transcript import YoutubeTranscriptReader
from llama_index import download_loader
try:
storage_context = StorageContext.from_defaults(persist_dir='./storage_graph', graph_store=graph_store)
kg_index = load_index_from_storage(
storage_context=storage_context,
service_context=service_context,
max_triplets_per_chunk=15,
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
verbose=True,
)
index_loaded = True
except:
index_loaded = False
if not index_loaded:
# 获取在线网站文档数据和视频数据
WikipediaReader = download_loader("WikipediaReader")
loader = WikipediaReader()
wiki_documents = loader.load_data(pages=['Philadelphia Phillies'], auto_suggest=False)
print(f'Loaded {len(wiki_documents)} documents')
# 在线获取视频需要验证,我们将视频下载到 graph_data 文件夹
# youtube_loader = YoutubeTranscriptReader()
# youtube_documents = youtube_loader.load_data(ytlinks=['<https://www.youtube.com/watch?v=k-HTQ8T7oVw>'])
# print(f'Loaded {len(youtube_documents)} YouTube documents')
kg_index = KnowledgeGraphIndex.from_documents(
# documents=wiki_documents + youtube_documents,
documents=wiki_documents + SimpleDirectoryReader(
"./graph_data"
).load_data() ,
storage_context=storage_context,
max_triplets_per_chunk=15,
service_context=service_context,
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
include_embeddings=True,
)
kg_index.storage_context.persist(persist_dir='./storage_graph')在 KG 索引构建中需要注意以下几点:
max_triplets_per_chunk:每个块中要提取的三元组的最大数量。我们设置为 15,这可以覆盖每个块中的大部分内容。
include_embeddings:指定创建知识图谱索引时是否应包括数据的嵌入。嵌入是一种表示文本数据的方式,它们捕捉了数据的语义含义。当设置 include_embeddings=True 时,KnowledgeGraphIndex 将在索引中包括这些嵌入。当你想在知识图谱上执行语义搜索时,这个可以用来找到与查询在语义上相似的节点和边。
步骤 4:运行查询 NebulaGraph
现在,我们来运行一个简单的查询。
query_engine = kg_index.as_query_engine()
response = query_engine.query("Tell me about some of the facts of Philadelphia Phillies.")
display(Markdown(f"<b>{response}</b>"))这是 “费城费利斯队” 从其维基百科页面获取的摘要!

在 Cypher 中查询,我们得到以下内容:
%%ngql
MATCH (p:`entity`)-[e:relationship]->(m:`entity`)
WHERE p.`entity`.`name` == 'Trey Turner'
RETURN p, e, m;这个查询将匹配与 Trey Turner 相关的所有实体。查询结果将是所有与 Trey Turner 相关的实体列表,以及它们与 Trey Turner 的关系,还包括 Trey Turner 实体本身。

接下来,运行 ng_draw,这是 ipython-ngql 包中的一个命令,可以将 NebulaGraph 查询的结果渲染到一个单一的 HTML 文件中;我们得到了以下图形。围绕“Philadelphia Phillies”节点,它扩展出其他九个节点,每个节点代表 Cypher 查询结果中的一行项目。连接每个节点到中心节点的是边,即两个节点之间的关系。

以上是 NebulaGraph 的基础知识,接下里我们继续深入了解。
探索 NebulaGraph 七种查询方法
基于我们的 KG 索引,我们使用不同的方法查询我们的知识图谱,并观察它们的结果。
查询方法 1:基于 KG 向量的实体检索
query_engine = kg_index.as_query_engine()这种方法通过向量相似性查找 KG 实体,提取链接的文本块,并可选地探索关系。这是 LlamaIndex 的 KG 查询引擎默认的构建方式,基于索引。用起来非常简单,开箱即用,无需额外的参数。
查询方法 2:基于 KG 关键词的实体检索
kg_keyword_query_engine = kg_index.as_query_engine(
include_text=False,
retriever_mode="keyword",
response_mode="tree_summarize",
)这个查询引擎 使用查询中的关键词来检索相关的 KG 实体,提取链接的文本块,并可选地探索关系以获取更多的上下文。通过指定 retriever_mode="keyword",将其配置为使用基于关键词的检索。
include_text=False:查询引擎只会使用原始的三元组进行查询;响应中不会包含来自相应节点的文本。
response_mode="tree_summarize":响应将是知识图谱树结构的摘要。该树将递归构建,查询位于根节点,最相关的答案位于叶节点。tree_summarize 响应模式对于摘要任务非常有用,例如提供一个主题的高级概述或回答需要全面响应的问题。它还可以生成更复杂的响应,例如提供为什么某事为真的理由列表或解释涉及的步骤。
查询方法 3:KG 混合实体检索
kg_hybrid_query_engine = kg_index.as_query_engine(
include_text=True,
response_mode="tree_summarize",
embedding_mode="hybrid",
similarity_top_k=3,
explore_global_knowledge=True,
)通过指定 embedding_mode="hybrid",这个查询引擎配置为使用混合方法 —— 向量基础的实体检索和基于关键词的实体检索来从知识图谱中检索信息,还进行了去重。KG 混合实体检索使用关键词来找到相关的三元组。然后,它还使用基于语义相似性的向量基础实体检索来找到相似的三元组。因此,本质上,混合模式结合了关键词搜索和语义搜索,利用两种方法的优点来提高搜索结果的准确性和相关性。
include_text=True:查询引擎将在响应中使用相应节点的文本。
similarity_top_k=3:它将基于嵌入检索出最相似的前三个结果。根据您的用例,随时调整这个值。
explore_global_knowledge=True:指定查询引擎在检索信息时是否应考虑知识图谱的全局上下文。当设置为 explore_global_knowledge=True 时,查询引擎不会限制其搜索于局部上下文(即节点的直接邻居),而是还会考虑知识图谱的更广泛的全局上下文。当您希望检索与查询不直接相关但在知识图谱的更大上下文中相关的信息时,这可能很有用。
关键词实体检索和混合实体检索之间的主要区别在于我们用于从知识图谱检索信息的方法:关键词实体检索使用基于关键词的方法,而混合实体检索使用结合了嵌入和关键词的混合方法。
查询方法 4:原始向量索引检索
这种方法完全不涉及知识图谱。它基于向量索引。首先,让我们从文档中构建向量索引,然后从向量索引构建一个向量查询引擎。
vector_index = VectorStoreIndex.from_documents(wiki_documents + SimpleDirectoryReader("./graph_data").load_data())
vector_query_engine = vector_index.as_query_engine()查询方法 5:自定义组合查询引擎(KG 检索器和向量索引检索器的组合)
LlamaIndex 创造了一个 CustomRetriever。你可以在下面的代码中看到它的样子。它执行知识图谱搜索和向量搜索。默认模式 或者 保证了两次搜索的并集,导致搜索结果包含两全其美的最佳部分,带有去重功能:
知识图谱搜索的细节(KGTableRetriever)。
向量索引搜索的语义相似性搜索细节(VectorIndexRetriever)。

from llama_index import QueryBundle
from llama_index.schema import NodeWithScore
from llama_index.retrievers import BaseRetriever, VectorIndexRetriever, KGTableRetriever
from typing import List
class CustomRetriever(BaseRetriever):
def __init__(
self,
vector_retriever: VectorIndexRetriever,
kg_retriever: KGTableRetriever,
mode: str = "OR",
) -> None:
"""Init params."""
self._vector_retriever = vector_retriever
self._kg_retriever = kg_retriever
if mode not in ("AND", "OR"):
raise ValueError("Invalid mode.")
self._mode = mode
def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
"""Retrieve nodes given query."""
vector_nodes = self._vector_retriever.retrieve(query_bundle)
kg_nodes = self._kg_retriever.retrieve(query_bundle)
vector_ids = {n.node.node_id for n in vector_nodes}
kg_ids = {n.node.node_id for n in kg_nodes}
combined_dict = {n.node.node_id: n for n in vector_nodes}
combined_dict.update({n.node.node_id: n for n in kg_nodes})
if self._mode == "AND":
retrieve_ids = vector_ids.intersection(kg_ids)
else:
retrieve_ids = vector_ids.union(kg_ids)
retrieve_nodes = [combined_dict[rid] for rid in retrieve_ids]
return retrieve_nodes
from llama_index import get_response_synthesizer
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.retrievers import VectorIndexRetriever, KGTableRetriever
# create custom retriever
vector_retriever = VectorIndexRetriever(index=vector_index)
kg_retriever = KGTableRetriever(
index=kg_index, retriever_mode="keyword", include_text=False
)
custom_retriever = CustomRetriever(vector_retriever, kg_retriever)
# create response synthesizer
response_synthesizer = get_response_synthesizer(
service_context=service_context,
response_mode="tree_summarize",
)
custom_query_engine = RetrieverQueryEngine(
retriever=custom_retriever,
response_synthesizer=response_synthesizer,
)这样,您就创建了一个自定义的查询引擎,它结合了知识图谱和向量索引的强大功能。
查询方法 6:KnowledgeGraphQueryEngine
到目前为止,我们已经探索了使用索引构建的不同查询引擎。我们继续看看由 LlamaIndex 制作的另一个知识图谱查询引擎 — KnowledgeGraphQueryEngine。
query_engine = KnowledgeGraphQueryEngine(
storage_context=storage_context,
service_context=service_context,
llm=llm,
verbose=True,
)KnowledgeGraphQueryEngine 是一个允许我们使用自然语言查询知识图谱的查询引擎。它使用 LLM 生成 Cypher 查询,然后在知识图谱上执行这些查询。这使得可以在不学习 Cypher 或任何其他查询语言的情况下查询知识图谱。
KnowledgeGraphQueryEngine 接受 storage_context、service_context 和 llm,并构建一个使用 NebulaGraphStore 作为 storage_context.graph_store 的知识图谱查询引擎。
查询方法 7:KnowledgeGraphRAGRetriever
KnowledgeGraphRAGRetriever 是 LlamaIndex 中的一个 RetrieverQueryEngine,它在知识图谱上执行 Graph RAG 查询。它接受问题或任务作为输入,并执行以下步骤:
使用关键词提取或嵌入在知识图谱中搜索相关实体。
从知识图谱中获取这些实体的子图,默认深度为 2。
基于子图构建上下文。
然后,下游任务(如 LLM)可以使用这个上下文来生成一个响应。请参阅下面的代码片段来构造一个 KnowledgeGraphRAGRetriever:
graph_rag_retriever = KnowledgeGraphRAGRetriever(
storage_context=storage_context,
service_context=service_context,
llm=llm,
verbose=True,
)
kg_rag_query_engine = RetrieverQueryEngine.from_args(
graph_rag_retriever, service_context=service_context
)了解了上面的七种查询方法。我们测试下效果。
效果测试
提问 1:告诉我有关 Bryce Harper 的信息。
以下是基于所使用的查询引擎列出的部分响应列表。

我的观察:
KG 向量基础实体检索、关键词基础实体检索、KnowledgeGraphQueryEngine 和 KnowledgeGraphRAGRetriever 都返回了我们试图查询的主题 — Bryce Harper 的主要事实 — 但没有详细说明。
KG 混合实体检索、原始向量索引检索和自定义组合查询引擎都返回了与我们的主题相关的相当多的信息,主要是因为它们可以访问查询嵌入。
原始向量索引检索的响应速度比其他 KG 查询引擎快(约 3 秒),而 KG 混合实体检索最慢(约 10 秒)。
提问 2:Philly 球迷给 Trea Turner 的起立鼓掌如何改变了他的赛季?
以下是七种查询方法的部分响应列表。

我的观察:
KG 向量基础实体检索返回了完美的响应,提供了所有支持事实和详细的统计数据,显示了 Philly 球迷如何帮助 Trea Turner 改变了他的赛季。
KG 关键词基础实体检索返回了非常简短的响应,没有支持的事实。
KG 混合实体检索返回了良好的响应,尽管在鼓掌后 Turner 的表现上缺乏详细的事实信息。
原始向量索引检索和自定义组合查询引擎返回了相当不错的响应,但没有 KG 向量基础实体检索返回的那么完整。
KnowledgeGraphQueryEngine 返回了语法错误。根本原因似乎是生成的 Cypher 不正确。
KnowledgeGraphRAGRetriever 返回了关于 Trea Turner 起立鼓掌的最低限度的响应。
原始向量索引检索的响应速度比其他 KG 查询引擎快(约 5 秒),除了 KG 关键词基础实体检索(约 6 秒)。自定义组合查询引擎最慢(约 13 秒)。
提问 3:告诉我关于 Philadelphia Phillies 当前体育场的一些事实。
以下是每种七种查询方法的部分响应列表。

我的观察:
KG 向量基础实体检索返回了相当不错的响应,包括体育场的一些历史背景。
关键词基础实体检索答错了,甚至没有提到当前体育场的名字。
混合实体检索只返回了关于当前体育场的最基本事实,如名称、年份和位置。
原始向量索引检索返回了与混合实体检索相似的一些关于当前体育场的事实。
自定义组合查询引擎提供了最佳响应,详细且全面,支持了大量关于体育场的统计数据和事实。
KnowledgeGraphQueryEngine 和 KnowledgeGraphRAGRetriever 都找不到关于 Philadelphia Phillies 当前体育场的任何事实。这似乎是自动生成的 Cypher 的问题。
原始向量索引检索的响应速度比 KG 查询引擎快(约 3 秒),自定义组合查询引擎最慢(约 12 秒)。
关键观点
根据上面对七种查询引擎进行的实验来分析,哪种查询引擎最适合您将取决于您的具体用例

如果数据源中的知识片段分散且细粒度,并且您需要对数据源进行复杂的推理,例如以网络格式提取实体及其关系,例如欺诈检测、社交网络、供应链管理,那么知识图谱查询引擎是更好的选择。当嵌入生成错误相关性(这会导致幻觉)时,KG 查询引擎也会很有帮助。
需要相似性搜索,例如找到与给定节点相似的所有节点,或在向量空间中找到最接近给定节点的所有节点,则向量查询引擎可能是您的最佳选择。
需要一个能够快速响应查询的查询引擎,那么向量查询引擎可能是更好的选择,因为它们通常比 KG 查询引擎更快。即使没有嵌入,任务(在 NebulaGraph 中的单一存储服务上运行的子任务)的提取可能是导致 KG 查询引擎延迟的主要原因。
如果您需要最高质量的响应,那么自定义组合查询引擎,结合了 KG 查询引擎和向量查询引擎的优势,是您的最佳选择。
总结
本文中,我们探讨了知识图(特别是 NebulaGraph)。我们使用 LlamaIndex、NebulaGraph 和 Auzre GPT 构建了一个 RAG 管道,用于 Philadelphia Phillies。
我们探索了七种查询引擎,研究了它们的内部工作原理,并观察了它们对三个问题的响应。我们比较了每种查询引擎的优缺点,并更好地理解了每种查询引擎设计的用例。
有关我们 Phillies 的 RAG 管道的完整 Jupyter 笔记本,请参阅我的 GitHub 仓库。
参考链接:
GitHub 项目地址:https://github.com/mcks2000/llm_notebooks/blob/main/notebooks/llamaindex_nebulagraph_phillies.ipynb
Knowledge Graph RAG Query Engine:https://docs.llamaindex.ai/en/stable/examples/query_engine/knowledge_graph_rag_query_engine.html#
Knowledge Graph Index:https://docs.llamaindex.ai/en/stable/examples/index_structs/knowledge_graph/KnowledgeGraphDemo.html#knowledge-graph-index
Knowledge Graph Query Engine:https://docs.llamaindex.ai/en/stable/examples/query_engine/knowledge_graph_query_engine.html
Knowledge Graph RAG Query Engine:https://docs.llamaindex.ai/en/stable/examples/query_engine/knowledge_graph_rag_query_engine.html
Custom Retriever combining KG Index and VectorStore Index:https://docs.llamaindex.ai/en/stable/examples/index_structs/knowledge_graph/KnowledgeGraphIndex_vs_VectorStoreIndex_vs_CustomIndex_combined.html
LlamaIndex Webinar: Graph Databases, Knowledge Graphs, and RAG with Wey (NebulaGraph):https://www.youtube.com/watch?v=bPoNCkjDmco
费城费利斯队比赛视频:https://www.youtube.com/watch?v=bPoNCkjDmco
费城费城人队的维基百科页面:https://en.wikipedia.org/wiki/Philadelphia_Phillies
NebulaGraph 3.6.0 文档:https://docs.nebula-graph.com.cn/3.6.0/
NebulaGraph 视频介绍:https://docs.nebula-graph.com.cn/3.6.0/2.quick-start/1.quick-start-overview/
知识图片维基百科:https://en.wikipedia.org/wiki/Knowledge_graph
NebulaGraph 官网:https://www.nebula-graph.io/
https://en.wikipedia.org/wiki/NetworkX
AzureOpenAI 配置文档:https://docs.llamaindex.ai/en/stable/examples/customization/llms/AzureOpenAI.html




