7 種查詢策略教你用好 Graph RAG 探索知識圖譜

2023-12-07 18:02:00

近來 NebulaGraph 社群在 LLM + Graph 和 Graph RAG 領域進行了深入的探索和分享。在 LlamaIndex 和 LangChain 中,NebulaGraph 引入了一系列知識圖譜和圖儲存工具,支援編排、圖譜與大模型間的互動。之前,NebulaGraph 佈道師古思為作為這項工作的主要貢獻者已向大家詳細介紹瞭如何構建圖譜、Text2Cypher、GraphRAG、GraphIndex 等方法,並展示了相關範例與效果。

最近,ArisGlobal 公司的工程師 Wenqi Glantz 對基於 NebulaGraph 和 LlamaIndex 的所有 Graph + LLM、RAG 方法進行了全面的實驗、評估、綜述、總結和分析,並給出了深刻的結論。

此文在 Twitter 和 LinkedIn 上獲得了廣泛認可。在得到 Wenqi 的同意後,我們為大家提供了中文翻譯,期望為大家在 Graph + LLM 方法的探索和實踐中提供更多的洞見和參考。

由於 Wenqi Glantz 全家都是 Philadelphia Phillies(費城費城人棒球隊,下文僅做英文展示)的鐵桿粉絲,因此,在本文中她將會使用知識圖譜,確切點是圖資料庫 NebulaGraph 來查詢這隻位於費城的 Major League Baseball(大聯盟棒球隊,下文僅做英文展示)Philadelphia Phillies 的資訊。

架構思路

這裡,我們將使用維基百科·Philadelphia Phillies 頁面作為其中一個資料來源。此外,因為最近費城球迷為我們喜愛的球員 Trea Turner 發起了 standing ovation(起立致敬是指演奏、比賽等專案結束時,聽眾或觀眾起立鼓掌之行為)事件,我們還將使用一段評論這個大事件的 YouTube 視訊作為另一個資料來源。

現在,我們的架構圖是這樣的:

(作者提供的架構圖)

如果你熟悉知識圖譜和圖資料庫 NebulaGraph,可以直接跳到「RAG 具體實現」章節。如果你不熟悉 NebulaGraph,請繼續往下讀。

什麼是知識圖譜(Knowledge Graph,KG)

知識圖譜是一種使用圖結構的資料模型或拓撲來整合資料的知識庫。它是一種表示現實世界實體及其相互關係的方式。知識圖譜常用來實現搜尋引擎、推薦系統、社群網路等業務場景。

知識圖譜的組成

知識圖譜一般有兩個主要組成部分:

  • 頂點/節點:英文對應是 Vertex 和 Node,無論是頂點還是節點,都表示知識領域中的實體或物件。每個節點對應一個唯一的實體,並通過唯一識別符號進行標識。例如,本例的棒球隊知識圖譜中,節點可能有「Philadelphia Phillies」和「Major League Baseball」。
  • 邊:表示兩個節點之間的關係。例如,一條邊 compete in(參賽)可能連線 「Philadelphia Phillies」 的節點和 「Major League Baseball」 的節點。

三元組

三元組是知識圖譜的基本資料單元,由三個部分組成:

  • 主體(Subject):三元組所描述的節點
  • 客體(Object):關係指向的節點
  • 謂詞(Predicate):主體和客體之間的關係

在下面的三元組範例中,「Philadelphia Phillies」是主體,「compete in」是謂詞,「Major League Baseball」是客體。

(Philadelphia Phillies)--[compete in]->(Major League Baseball)

而圖資料庫通過儲存三元組來高效地儲存和查詢複雜的圖資料。

什麼是 Cypher

Cypher 是由圖資料庫支援的一種宣告性圖查詢語言。通過 Cypher,我們告訴知識圖譜我們想要什麼資料,而不是如何得到結果資料。這使得 Cypher 查詢更易讀、更好維護。此外,Cypher 易上手使用,且能夠表達複雜的圖查詢。

以下,是一個 Cypher 的簡單的查詢範例:

%%ngql 
MATCH (p:`entity`)-[e:relationship]->(m:`entity`)
  WHERE p.`entity`.`name` == 'Philadelphia Phillies' 
RETURN p, e, m;

該查詢語句將找到與棒球隊「Philadelphia Phillies」相關的所有實體。

什麼是 NebulaGraph

NebulaGraph 是市面上最好的圖資料庫之一。它是開源、分散式的,並且能處理包含萬億條邊和頂點的大規模圖,而延遲僅為毫秒級。很多大公司在廣泛地使用它,進行各種應用開發,包括社交媒體、推薦系統、欺詐檢測等。

安裝 NebulaGraph

要實現 Philadelphia Phillies 的 RAG,我們需要在本地安裝 NebulaGraph。藉助 Docker Desktop 安裝 NebulaGraph 是最便捷的方式之一。詳細的安裝說明可以在 NebulaGraph 的檔案中找到。

如果你不瞭解 NebulaGraph,強烈建議去熟悉下檔案。

知識圖譜 RAG 具體實現

NebulaGraph 的首席佈道師古思為,以及 LlamaIndex 團隊精心撰寫了一份關於知識圖譜 RAG 開發的綜合指南。從這本指南中我學到了很多知識,我建議你在讀完本文之後也去讀下這個指南。

現在,利用我們從指南中學到的知識,開始逐步地介紹使用 LlamaIndex、NebulaGraph 和 GPT-3.5 構建 Philadelphia Phillies RAG。

原始碼可參考我的 GitHub 倉庫:https://github.com/wenqiglantz/llamaindex_nebulagraph_phillies,當中包括了專案完整的 JupyterNote。

實現第 1 步:安裝和設定

除了 LlamaIndex,我們還要安裝一些庫:

  • ipython-ngql:一個 Python 包,幫你更好地從 Jupyter Notebook 或 iPython 連線到 NebulaGraph;
  • nebula3-python:連線和管理 NebulaGraph 的 Python 使用者端;
  • pyvis:用最少的 Python 程式碼快速生成視覺化網圖的工具庫;
  • networkx:研究圖和網路的 Python 庫;
  • youtube_transcript_api:可獲取 YouTube 視訊的轉錄/字幕的 Python API。
%pip install llama_index==0.8.33 ipython-ngql nebula3-python pyvis networkx youtube_transcript_api

我們還要設定 OpenAI API 金鑰並設定應用程式的紀錄檔記錄:

import os
import logging
import sys

os.environ["OPENAI_API_KEY"] = "sk-####################"

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

實現第 2 步:連線到 NebulaGraph 並新建圖空間

假設你已經在本地安裝了 NebulaGraph,現在我們可以從 JupyterNote 連線它(注意:不要嘗試從 Google Colab 連線到原生的 NebulaGraph,由於某些原因,它無法運作)。

按照下面的步驟和程式碼片段來操作下:

  • 連線到原生的 NebulaGraph(預設賬號密碼為 root、nebula)
  • 建立一個名為 phillies_rag 的圖空間
  • 在新的圖空間中建立標籤、邊和標籤索引
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));

建立新的圖空間後,再來構建下 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 索引

是時候載入資料了。我們的源資料來自 Philadelphia Phillies 的維基百科頁面和一個關於 Trea Turner 在 2023 年 8 月收到 standing ovation 的 YouTube 視訊。

為了節省時間和成本,我們先檢查下本地 storage_context 來載入 KG 索引。如果存在索引,我們就載入索引。如果不存在索引(例如初次存取應用程式時),我們需要載入這兩個原始檔(上文提到的維基百科頁面和 YouTube 視訊),再構建 KG 索引,並在專案 root 目錄的本地 storage_graph 中持久化地儲存 doc、index 和 vector。

from llama_index import (
    LLMPredictor,
    ServiceContext,
    KnowledgeGraphIndex,
)
from llama_index.graph_stores import SimpleGraphStore
from llama_index import download_loader
from llama_index.llms import OpenAI

# define LLM
llm = OpenAI(temperature=0.1, model="gpt-3.5-turbo")
service_context = ServiceContext.from_defaults(llm=llm, chunk_size=512)

from llama_index import load_index_from_storage
from llama_hub.youtube_transcript import YoutubeTranscriptReader

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')

    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,
        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:說明建立 KG 索引時,是否包含資料的 Embedding。Embedding 是一種將文字資料表示為資料語意的向量法。它們通常用來讓模型理解不同文字片段之間的語意相似性。當設定 include_embeddings=True 時,KnowledgeGraphIndex 會在索引中包含這些嵌入。當你想在知識圖譜上執行語意搜尋時,include_embeddings=True 會很有用,因為 Embedding 可用來找到與查詢在語意上相似的節點和邊。

實現第 4 步: 通過查詢來探索 NebulaGraph

現在,讓我們跑一個簡單的查詢。

比如說,告知一些 Philadelphia Phillies 隊的資訊:

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>"))

這是從 Philadelphia Phillies 隊的維基百科頁面中得到的概述,是個非常不錯的簡述:

再用 Cypher 查詢下:

%%ngql 
MATCH (p:`entity`)-[e:relationship]->(m:`entity`)
  WHERE p.`entity`.`name` == 'Philadelphia Phillies' 
RETURN p, e, m;

該查詢將匹配與 Philadelphia Phillies 相關的所有實體。查詢結果將會返回與 Philadelphia Phillies 隊相關的所有實體、它們與 Philadelphia Phillies 隊的關係,以及 Philadelphia Phillies 隊實體本身的列表。

現在,讓我們在 Jupyter Notebook 中執行下這個 Cypher 查詢:

可以看到,結果返回了 9 條資料。

下面,執行 ipython-ngql 包中的 ng_draw 命令,它能在一個單獨的 HTML 檔案中渲染NebulaGraph 查詢的結果;我們得到了以下的圖形。以 Philadelphia Phillies 節點為中心,它延伸出 9 個其他節點,每個節點代表 Cypher 查詢結果中的一行資料。連線每個節點到中心節點的是邊,表示兩個節點之間的關係。

非常酷的是,你還可以拖動節點來操作圖形!

現在,我們對 NebulaGraph 的基本知識有了初步的瞭解,讓我們深入一點。

實現第 5 步: 圖探索的 7 種方式

下面根據 KG 索引,讓我們使用不同的方法查詢知識圖譜並觀察它們的結果。

圖探索的方法 1:KG 基於向量的檢索

query_engine = kg_index.as_query_engine()

這種方法通過向量相似性查詢 KG 實體,獲取連線的文字塊,並選擇性探索關係。是 LlamaIndex 基於索引構建的預設查詢方式。它非常簡單、開箱即用,不用額外的引數。

圖探索的方法 2:KG 基於關鍵詞的檢索

kg_keyword_query_engine = kg_index.as_query_engine(
    # setting to false uses the raw triplets instead of adding the text from the corresponding nodes
    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:Top K 設定,它將根據 Embedding 檢索出最相似結果的前三個結果。你可以根據你的使用場景彈性地調整這個值;
  • explore_global_knowledge=True:指定查詢引擎是否要考慮知識圖譜的全域性上下文來檢索資訊。當設定 explore_global_knowledge=True時,查詢引擎不會將其搜尋限制在本地上下文(即,一個節點的直接鄰居),而是會考慮知識圖譜的更廣泛的全域性上下文。當你想檢索與查詢不直接相關,但在該知識圖譜的更大上下文中有關的資訊時,這可能很有用。

基於關鍵詞的檢索和混合檢索二者主要區別,在於我們從知識圖譜中檢索資訊的方法:基於關鍵詞的檢索使用關鍵詞方法,而混合檢索使用結合 Embedding 和關鍵詞的混合方法。

圖探索方法 4:原生向量索引檢索

vector_index = VectorStoreIndex.from_documents(wiki_documents + youtube_documents)
vector_query_engine = vector_index.as_query_engine()

這種方式完全不處理知識圖譜。它基於向量索引,會先構建檔案的向量索引,再從向量索引構建向量查詢引擎。

圖探索方法 5:自定義組合查詢引擎(KG 檢索和向量索引檢索的組合)

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,
)

LlamaIndex 構建了一個 CustomRetriever。如上所示,你可以看到它的具體實現。它用來進行知識圖譜搜尋和向量搜尋。預設的 mode OR 保證了兩種搜尋結果的並集,結果是包含了這兩個搜尋方式的結果,且進行了結果去重:

  • 從知識圖譜搜尋(KGTableRetriever)獲得的細節;
  • 從向量索引搜尋(VectorIndexRetriever)獲得的語意相似性搜尋的詳情。

圖探索方法 6:KnowledgeGraphQueryEngine

到目前為止,我們已經探索了使用 KG 索引構建的不同查詢引擎。現在,來看看另一個由 LlamaIndex 構建的知識圖譜查詢引擎——KnowledgeGraphQueryEngine。看下面的程式碼片段:

query_engine = KnowledgeGraphQueryEngine(
    storage_context=storage_context,
    service_context=service_context,
    llm=llm,
    verbose=True,
)

KnowledgeGraphQueryEngine 是一個可讓我們用自然語言查詢知識圖譜的查詢引擎。它使用 LLM 生成 Cypher 查詢語句,再在知識圖譜上執行這些查詢。這樣,我們可以在不學習 Cypher 或任何其他查詢語言的情況下查詢知識圖譜。

KnowledgeGraphQueryEngine 接收 storage_contextservice_contextllm,並構建一個知識圖譜查詢引擎,其中 NebulaGraphStore 作為 storage_context.graph_store

圖探索方法 7:KnowledgeGraphRAGRetriever

KnowledgeGraphRAGRetriever 是 LlamaIndex 中的一個 RetrieverQueryEngine,它在知識圖譜上執行 Graph RAG 查詢。它接收一個問題或任務作為輸入,並執行以下步驟:

  1. 使用關鍵詞在知識圖譜中提取或 Embedding 搜尋相關實體;
  2. 從知識圖譜中獲取那些實體的子圖,預設深度為 2;
  3. 基於子圖構建上下文。

一個下游任務,如: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
)

好了,現在我們對 7 種查詢方法有了不錯的瞭解。下面,我們用一組問題來測試下它們的效果。

使用 3 個問題測試 7 種圖查詢

問題 1:告訴我 Bryce Harper 相關資訊

下圖展示了 7 種查詢方式對這一問題的回覆,我用不同的顏色對查詢語言進行了標註:

這是我基於結果的一些看法:

  • KG 基於向量的檢索、基於關鍵詞的檢索,KnowledgeGraphQueryEngineKnowledgeGraphRAGRetriever,都返回了我們正在查詢的主題——Bryce Harper 的關鍵事實——只有關鍵事實,沒有詳情的闡述;
  • KG 混合檢索,原生向量索引檢索和自定義組合查詢引擎都返回了與主題相關的大量資訊,主要是因為它們能夠存取查詢 Embedding;
  • 原生向量索引檢索返回的回答速度更快(約 3 秒),比其他 KG 查詢引擎(4+ 秒)快。KG 混合實體檢索是最慢的(約 10 秒)。

問題 2:Trey Turner 收到的 standing ovation 是如何影響他的賽季表現?

這個問題是特意設計的,來自 YouTube 視訊,這個視訊專門講述了這個 standing ovation 事件——Philly 的粉絲們對 Trea Turner(因為 YouTube 把他的名字誤寫為「Trey」而不是「Trea」,所以我們在問題中使用「Trey」)的支援。

看下 7 種查詢方法的回答列表:

這是我基於結果的一些看法:

  • KG 基於向量的檢索返回了一個完美的回答,所有支援的事實和詳細的統計資料都顯示出 Philly 的粉絲是如何幫助 Trea Turner 的賽季。而這些事實(解釋原因)都儲存在 NebulaGraph 中,取自 YouTube 視訊的內容;
  • KG 基於關鍵詞的檢索返回了一個非常簡短的回答,沒有支援的事實;
  • KG 混合檢索返回了良好的回答,儘管缺乏 Turner 在 standing ovation 後表現的詳細事實資訊。個人認為這個回答稍微遜於 KG 基於向量的檢索返回的回答;
  • 原生向量索引檢索和自定義組合查詢引擎返回了不錯的回答,有更詳細的事實資訊,但不如 KG 基於向量的檢索返回的回答完整。為什麼自定義組合查詢引擎沒有比 KG 基於向量的檢索更好的回答?我能想到的主要原因是,維基百科頁面沒有關於 Turner 的 standing ovation 事件的資訊。只有 YouTube 視訊有,YouTube 視訊專門講述了 standing ovation 事件,這些都被載入到了知識圖譜中。知識圖譜有足夠的相關內容來返回一個堅實的回答。原生向量索引檢索或自定義組合查詢引擎沒有更多的內容可以輸入做事實支撐;
  • KnowledgeGraphQueryEngine 返回了以下語法錯誤。可能原因是 Cypher 生成不正確,如下面的摘要截圖所示。看起來 KnowledgeGraphQueryEngine 在提高其 Text2Cypher 能力上還有提升空間;

  • KnowledgeGraphRAGRetriever 返回了關於 Trea Turner 的 standing ovation 事件的最基礎資訊,顯然這個回答是不理想的;
  • 原生向量索引檢索返回的回答速度(約 5 秒)比其他 KG 查詢引擎(10+ 秒)快,除了 KG 基於關鍵詞的檢索(約 6 秒)。自定義組合查詢引擎是最慢的(約 13 秒)。

小結下:如果將全面的上下文資料正確地載入到知識圖譜中,KG 基於向量的檢索似乎比上述任何其他查詢引擎做得更好。

問題 3:告訴我一些 Philadelphia Phillies 當前球場的事實。

看下 7 種查詢方法的回答列表:

這是我基於結果的一些看法:

  • KG 基於向量的檢索返回了一個不錯的回答,有一些球場的歷史背景;
  • KG 基於關鍵詞的檢索搞錯了答案,它甚至沒有提到當前球場的名字;
  • 混合檢索只返回了關於當前球場的最基本的事,如名字,年份和位置,這讓我懷疑知識圖譜中的 Embedding 實現是否可以改進。於是,我聯絡了 NebulaGraph 的 Wey(古思為),他反饋未來會優化 Embedding,支援 NebulaGraph 的向量搜尋。太讚了!
  • 原生向量檢索返回了關於當前球場的一些事實,與混合檢索返回的結果類似;
  • 自定義組合查詢引擎給出了最好的回答,詳細且全面,由許多關於球場的統計資料和事實支援。這是所有查詢引擎中最好的回答;
  • 基於給定的上下文資訊,KnowledgeGraphQueryEngine 找不到任何關於 Philadelphia Phillies 隊當前球場的事。似乎這又是一次自然語言自動生成 Cypher 有問題;
  • 基於給定的上下文資訊,KnowledgeGraphRAGRetriever 找不到任何關於當前球場的事實;
  • 原生向量檢索返回結果的速度(約 3 秒),比 KG 查詢引擎(6+ 秒)快。自定義組合查詢引擎是最慢的(約 12 秒)。

關鍵收穫

基於上面 3 個問題在 7 個查詢引擎上的實驗,比較了 7 個查詢引擎的優點和缺點:

哪個查詢引擎最適合,將取決於你的特定使用情況。

  • 如果你的資料來源中的知識片段是分散和細粒度的,並且你需要對你的資料來源進行復雜的推理,如提取實體和它們在網格中的關係,如在欺詐檢測、社群網路、供應鏈管理,那麼知識圖譜查詢引擎是一個更好的選擇。當你的 Embedding 生成假相關性,導致幻覺時,KG 查詢引擎也很有幫助。
  • 如果你需要相似性搜尋,如找到所有與給定節點相似的節點,或找到在向量空間中最接近給定節點的所有節點,那麼向量查詢引擎可能是你的最佳選擇;
  • 如果你需要一個能快速響應的查詢引擎,那麼向量查詢引擎可能是一個更好的選擇,因為它們通常比 KG 查詢引擎更快。即使沒有 Embedding,任務的提取(執行在 NebulaGraph 單個 storage 服務上的子任務)也可能是 KG 查詢引擎延遲高的主要原因;
  • 如果你需要高質量的回答,那麼自定義組合查詢引擎,它結合了 KG 查詢引擎和向量查詢引擎的優勢,是你最好的選擇。

總結

我們在這篇文章中探討了知識圖譜,特別是圖資料庫 NebulaGraph,是如何結合 LlamaIndex 和 GPT-3.5 為 Philadelphia Phillies 隊構建了一個 RAG。

此外,我們還探討了 7 種查詢引擎,研究了它們的內部工作,並觀察了它們對三個問題的回答。我們比較了每個查詢引擎的優點和缺點,以便更好地理解了每個查詢引擎設計的用例。

希望本篇文章對你有所啟發,相關程式碼請檢視 GitHub 倉庫:https://github.com/wenqiglantz/llamaindex_nebulagraph_phillies/tree/main

Happy coding!

參考資料


謝謝你讀完本文 (///▽///)