关于 NebulaGraph
一个可靠的分布式、线性扩容、 性能高效的图数据库,擅长处理千亿节点万亿条边的超大数据集,同时保持毫秒级查询延时的图数据库解决方案
部署 NebulaGraph 集群
使用 Docker Compose 部署 NebulaGraph 集群
参考资料:https://docs.nebula-graph.com.cn/
使用 Docker Compose 可以基于准备好的配置文件快速部署 NebulaGraph 服务,仅建议在测试NebulaGraph 功能时使用该方式
主机 Linux 系统:Ubuntu 20.04.6
安装 Docker
使用 Apt repository 进行安装
参考文档:https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
- 创建 Docker 的 Apt repository.
1 | Add Docker's official GPG key: |
- 安装最新的 Docker packages
1 | sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin |
- 验证 Docker Engine 是否安装成功
1 | sudo docker run hello-world |
安装 Docker Compose
- 下载并安装 Compose standalone
1 | curl -SL https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose |
1 | sudo chmod +x /usr/local/bin/docker-compose |
- 验证 Compose standalone 是否安装成功
1 | docker-compose |
部署 NebulaGraph
- 通过 Git 克隆
nebula-docker-compose
仓库的分支到主机,并切换工作目录
1 | git clone -b release-3.6 https://github.com/vesoft-inc/nebula-docker-compose.git |
注:执行该命令时出现报错 GnuTLS recv error (-54): Error in the pull function
,没有解决,尝试将 release-3.6
更换为 release-3.5
之后连接超时,没有解决,尝试将 release-3.6
更换为 release-3.4
之后成功克隆仓库(具体原因未知)
1 | cd nebula-docker-compose |
- 启动 NebulaGraph 服务
1 | docker-compose up -d |
连接 NebulaGraph(两种方式)
在容器外通过 Nebula Console 连接
因为容器配置文件中将 Graph 服务的外部映射端口也固定为 9669,因此可以直接通过默认端口连接
下载所需版本的 NebulaGraph Console 二进制文件
可选:重命名文件为
nebula-console
(方便使用)授予
nebula-console
文件的执行权限:sudo chmod 111 nebula-console
运行
nebula-console
,连接 NebulaGraph1
./nebula-console -addr 127.0.0.1 -port 9669 -u root -p nebula
登录安装了 NebulaGraph Console 的容器,然后再连接 Graph 服务
1
docker exec -it nebuladockercompose_console_1 /bin/sh
1
/ # ./usr/local/bin/nebula-console -u root -p nebula --address=graphd --port=9669
查看 NebulaGraph 服务的状态和端口
NebulaGraph 默认使用
9669
端口为客户端提供服务,如果需要修改端口,需要修改目录nebula-docker-compose
内的文件docker-compose.yaml
,然后重启 NebulaGraph 服务
1 | docker-compose ps |
关闭 NebulaGraph 服务
1 | sudo docker-compose down |
关于 LLM 与 KG 的结合
核心内容概述
要点总结
整个 Demo 对 LLM 与 KG 的结合作出了一定的探索并已经将具体实现整合到 langchain 与 llama_index 中,大致分为三个方向(其中,第一个方向主要涉及借助 LLM 构建 KG,后两个方向主要涉及借助 KG 增强 LLM):
- Construct KG via LLM:从已有的文档中按照确定的规则抽取知识(三元组)并存入图数据库中
- Text2Cypher:对于用户提出的问题,由 LLM 将自然语言的询问转换为对图数据库的 Cypher 查询,获取到查询结果后,再经由 LLM 整合并输出回答 (类似 前端 Hubble 打通 LLM 方案梳理)
- Graph RAG:对于用户提出的问题,由 LLM 提取问题中所涉及的 Entity,以此来从 KG 中获取与问题相关的 sub-KG 作为上下文,再由 LLM 对该上下文信息进行理解并输出回答(该方法分为两个使用场景:一是使用 llama_index 等框架/工具基于已有的文档构建 KG 并使用,二是基于已有的 KG 使用,案例中两者均给出了具体实现)
- 对比 Vector RAG:与 Graph RAG 不同的地方在于,将文档(构建 KG 的原始文档)分为多个 chunk,通过向量搜索找到 K 个最语义相关的 chunk 作为 上下文信息
- 尝试 Graph + Vector RAG:结合 Graph RAG 与 Vector RAG (并与单独的 Vector RAG 和单独的 Graph RAG 对比效果)
实验结论
原作者针对 Demo 进行了对比试验,实验结果指出:
- pure-KG-based 和 otherwise:
- Text2Cypher 和 Graph RAG 均能给出精确的答案,并且 Token 开销远小于 Vector RAG 和 Graph + Vector RAG
- 关于此处 Token 开销,原作者仅在实验文档中对比了 Response 的 Token 数量而没有对比 Prompt 的 Token 数量
- pure-KG-based 方法得到的回答更加简短精炼(作者想表达的意思应该是其信息密度较高 )
- Graph + Vector RAG 在问题涉及的知识广泛分布在文档的不同段落时,能够给出更加综合和完整的回答 (意思是说 –> 更适合复杂问题?)
- Text2Cypher 和 Graph RAG 均能给出精确的答案,并且 Token 开销远小于 Vector RAG 和 Graph + Vector RAG
- Text2Cypher 和 Graph RAG:
- Text2Cypher 仅结合查询结果作出回答,Graph RAG 结合所有相关上下文进行回答,因此,对于答案本身属于细节性、碎片性信息的,Text2Cypher 效果更好,反之 Graph RAG 效果更好
- 对于以下情形优先使用 Graph RAG:
- 需要考虑潜在相关信息
- KG 的 schema 相对复杂
- KG 本身的数据质量不高
- 问题包含多个 Starting Entity
详细复现过程
- 添加环境变量
1 | import os |
- 图数据库的 space 创建与 schema 定义(in nebula-console)
1 | CREATE SPACE guardians(vid_type=FIXED_STRING(256), partition_num=1, replica_factor=1); |
- LLM 服务的构建与准备
1 | from llama_index import LLMPredictor |
- 图存储的构建与准备(基于 NebulaGraph)
1 | from llama_index.graph_stores import NebulaGraphStore |
- 样例文档数据的准备
1 | from llama_index import download_loader |
- 提取文档中的知识并存入图数据库(for Cypher Query & Graph RAG)
Construct KG via LLM 的核心步骤
1 | from llama_index import KnowledgeGraphIndex |
- 为文档生成向量索引(for Vector RAG)
1 | from llama_index import VectorStoreIndex |
- 将相关数据固定在磁盘并支持重复读取(可选)
1 | from llama_index import load_index_from_storage |
- Text2Cypher 的构建
Text2Cypher 的核心步骤
1 | from llama_index.query_engine import KnowledgeGraphQueryEngine |
- Graph RAG 的构建
Graph RAG 的核心步骤
1 | kg_rag_query_engine = kg_index.as_query_engine( |
- Vector RAG 的构建
1 | vector_rag_query_engine = vector_index.as_query_engine() |
- Graph + Vector RAG 的构建
1 | from llama_index import QueryBundle |
- 提问并获取回答
1 | seperator = '\n' + '#'*50 + '\n' |
经代码整合后的完整案例复现代码如下:
1 | from typing import List, Optional |
源码实现细节
Text2Cypher 过程解析
service_context
对象提供 LLM 服务的上下文(可用其他 LLM 替换 OpenAI 提供的服务)storage_context
对象提供图数据库存储的上下文- 由
NebulaGraphStore
类实现(./llama_index/graph_stores/nebulagraph.py
) - 继承自
GraphStore
类(./llama_index/graph_stores/types.py
)
- 由
query_engine
对象提供向图数据库进行查询的接口- 由
KnowledgeGraphQueryEngine
类实现(./llama_index/query_engine/knowledge_graph_query_engine.py
) - 使用
service_context
对象与storage_context
对象作为参数实例化KnowledgeGraphQueryEngine
类 - 在与 LLM 交互时使用了为 NebulaGraph 定制的提示模板(
./llama_index/query_engine/knowledge_graph_query_engine.py
)
- 由
query_engine
对象的query
方法完成了提示词构建以及与 LLM 和 KG 的交互- 自然语言转换为查询语句 & 通过查询语句从图数据库中得到查询结果(Retrieve)
- 查询结果转换为自然语言回答(Response Synthesize)
Text2Cypher Prompt 解析
任务阐述:根据自然语言询问生成图数据库查询语句
辅助信息:图数据库相关 schema;自然语言询问语句
约束限制:只能用提供 schema 中涉及的关系类型和属性
特殊提示:NebulaGraph 的 Cypher 方言规范(语言描述 + 举例说明)
- 对比 Neo4j 图数据库的定制化提示模板,特殊提示中还可以添加:
- 回答中不要包含解释或道歉
- 不要回答任何可能要求构建 Cypher 语句以外的问题
- 回答中不要包含除了生成的 Cypher 语句以外的内容
1 | # ./llama_index/query_engine/knowledge_graph_query_engine.py |
Graph RAG 过程解析
Graph RAG for LlamaIndex Built KG
对于基于已有的文档使用 LlamaIndex 构建的 KG,已经在构建时保存了相关索引,由 KnowledgeGraphIndex
类封装(./llama_index/indices/knowledge_graph/base.py
),继承自 BaseIndex
类(./llama_index/indices/base.py
)
- 调用索引对象的 as_query_engine() 获得一个
RetrieverQueryEngine
类对象(./llama_index/query_engine/retriever_query_engine.py
),继承自 BaseQueryEngine 类(./llama_index/indices/query/base.py
)- 核心仍是由 Retriever 和 Response Synthesizer 构成
- Retriever 由 KGTableRetriever 类对象承担(
./llama_index/indices/knowledge_graph/retrievers.py
),继承自BaseRetriever
类(./llama_index/indices/base_retriever.py
) - Response Synthesizer 的具体类型由 response_mode 参数决定,如本案例中使用了 TreeSummarize(
./llama_index/response_synthesizers/tree_summarize.py
),继承自 BaseSynthesizer(./llama_index/response_synthesizers/base.py
)
- 检索过程:(详见
./llama_index/indices/knowledge_graph/retrievers.py
中KGTableRetriever
类的_retrieve
方法)- 根据问题归纳相关关键词(由 LLM 完成)
- 根据关键词找到对应的实体
- 根据相关实体以固定深度提取 SubGraph
- 将 SubGraph 转换为上下文信息(字符串化等处理)
- 回答整合过程:(详见
./llama_index/response_synthesizers/tree_summarize.py
中TreeSummarize
类的get_response
方法)
Graph RAG for Existing KG
与 Graph RAG for LlamaIndex Built KG 流程基本相同,不同之处在于:
- 前者 query_engine(包括其中的 retriever)直接通过索引构建,并且其 retriever 是一个 KGTableRetriever 对象
- 后者需要手动构建 retriever 和 query_engine,其中 retriever 是一个 KnowledgeGraphRAGRetriever 对象
- 两者 retriever 的实现方式不同,详见
./llama_index/indices/knowledge_graph/retrievers.py
注:搜索相关实体的方式可以是基于关键字的提取,也可以是基于嵌入的提取,这由 KnowledgeGraphRAGRetriever
的参数 retriever_mode
控制,支持的选项有
- keyword
- embedding(尚未实现)
- keyword_embedding(尚未实现)
资料来源
- https://www.siwei.io/graph-enabled-llama-index/
- https://www.siwei.io/llm-text-to-nebulagraph-query/
- https://www.siwei.io/graph-rag/
- https://www.siwei.io/demos/graph-rag/
- https://gpt-index.readthedocs.io/en/stable/examples/query_engine/knowledge_graph_rag_query_engine.html
- https://www.siwei.io/demo-dumps/local-llm/Graph_RAG_Local.html (本地 LLM 构建案例)
- https://github.com/jerryjliu/llama_index/tree/main/docs/examples/index_structs/knowledge_graph (LlamaIndex KG)