學習筆記|RAG外,圖形資料庫Graph RAG也許是LLM新方向

Eric Chang
12 min readMay 31, 2024

--

在傳統RAG的使用上,常常會抓不到我們所要的內容,結合最近我們正在研究圖形資料庫的使用,於是我找到了Neo4j一個專門研究LLM跟Graph DB結合的解決方案。

相對於傳統RAG使用的向量資料庫,Neo4j提供另外一種不同的檢索方法。(必須先聲明,我個人認為Graph DB不一定在整體表現上更好,但某些特定場景會比較有幫助,不是傳統RAG就是比較不好

相對於關聯式資料庫,Neo4j 是一個基於Graph的高性能 NoSQL 資料庫。圖形資料庫以圖的形式存儲數據,其中包括節點(nodes)、關係(relationships)和屬性(properties),這種結構使 Neo4j 特別適合處理高度關聯的數據和複雜的查詢,而neo4j的查詢語句叫做Cypher。

還是先來讀相關研究論文

關於Graph跟LLM的結合,其實我找到一篇最近才剛發佈的研究報告,主要是微軟跟中國北京航空航天大學的研究:From Local to Global: A Graph RAG Approach to Query-Focused Summarization。

論文裡面提到傳統 RAG 方法能從知識庫找資料,幫助大型語言模型回答問題,但它不擅長回答概括整份文本的問題,而現有可以用於概括文本的方法,又難以處理 RAG 裡那麼大量的資訊。

所以研究團隊提出了一個新 Graph RAG 方法,分兩步讓LLM建立一個基於Graph的文本索引:第一步從文本中提取Knowledge Graph,第二步是去生成摘要。用戶提問時,系統會利用每個community摘要生成部分答案,最後再把所有部分答案匯總成最終的答案。

但這份報告我自己比較有疑問的地方是評估的方式,我覺得也許還有比較客觀的研究或者比較方式,研究中用了四個方面來比較傳統RAG跟Graph RAG的表現,其中Graph RAG贏了其中的三項:

全面性,答案提供多少細節以涵蓋問題的所有細節?

多樣性,答案提供多少不同觀點和見解方面有多變化和豐富?

賦權性,答案能如何幫助讀者理解並對它做出判斷?

直接性,答案多麼具體和清晰地回答問題?

我們先申請Neo4j資料庫試試看

Neo4j除了有本地的版本之外,有也提供線上版本aura可以進行研究使用,只是免費版在node的數量上有所限制,進入AuraDB的頁面之後,可以用Google帳號來進行註冊與登入,然後新增一個免費的資料庫來進行研究。

Neo4j AuraDB 是託管的雲端圖形資料庫服務,利用數據中的關係,實現即時分析和洞察的高速查詢。AuraDB 提供以下訂閱方案:AuraDB Free、AuraDB Professional 和 AuraDB Enterprise。

有個需要特別注意的是,AuraDB 免費版本如果太久沒有使用的話,有可能會自動停止,要啟動的話只需要進入AuraDB的管理介面,然後再啟動就可以了,我覺得整個體驗還滿直覺。

透過Wikipedia準備資料庫

首先,我們先把相關的套件進行匯入,這次我們使用的方案,是OpenAI的API來進行測試,如果想要替換成llama3或者其他模型來源的,也可以參考langchain的文件進行相關的調整。

我們先匯入相對應套件,可以看到跟一般我們用langchain不同的是,我們多了neo4j跟其他Neo4jVector的套件,這個是langchain對於Neo4j支持的重要部分,另外為了可以更好的進行視覺化,我們用了GraphWidget讓圖形資料庫可以在juypter的環境進行顯示。

import os
from typing import Tuple, List, Optional
from neo4j import GraphDatabase
from yfiles_jupyter_graphs import GraphWidget
from langchain_core.runnables import RunnableBranch,RunnableLambda,RunnableParallel,RunnablePassthrough,ConfigurableField
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_community.vectorstores import Neo4jVector
from langchain_community.vectorstores.neo4j_vector import remove_lucene_chars
from langchain_community.graphs import Neo4jGraph
from langchain.document_loaders import WikipediaLoader
from langchain.text_splitter import TokenTextSplitter
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain.chains import GraphCypherQAChain
try:
import google.colab
from google.colab import output
output.enable_custom_widget_manager()
except ImportError:
pass

接下來,我們要填上Neo4j的資料庫與OpenAI帳號的相關資料,並且將Neo4jGraph這個物件建立出來,待會我們會往裡面新增相關的內容:

os.environ["OPENAI_API_KEY"] = "sk- "
os.environ["NEO4J_URI"] = "neo4j+s:// "
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = ""
graph = Neo4jGraph()

由於我們手上沒有文檔,所以我們選擇用Wikipedia的資料來進行解析,主要用的是WikipediaLoader,我找了Taiwan和Changhua兩個關鍵詞,langchain會把這兩個頁面的內容進行文本分割。這段code比較關鍵的是LLMGraphTransformer這個物件,因為他會用LLM把文本轉換成Graph,將識別出的文本圖形化,將內容轉換為node和edge的形式,這樣可以在圖數據庫中進行存儲和處理。

llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
raw_documents = WikipediaLoader(query="Changhua").load()
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])
llm_transformer = LLMGraphTransformer(llm=llm)graph_documents = llm_transformer.convert_to_graph_documents(documents)
graph.add_graph_documents(
graph_documents,
baseEntityLabel=True,
include_source=True
)

查看Neo4j的模樣

接下來,我們使用剛剛套入的jupyter工具來進行演示,其中cypher所執行的檢索是指,在圖形資料庫中查找所有節點 s 和 t 之間的關係 r,但排除 MENTIONS 類型的關係返回這些節點和關係,而且最多只返回50個結果:

default_cypher = "MATCH (s)-[r:!MENTIONS]->(t) RETURN s,r,t LIMIT 50"

def showGraph(cypher: str = default_cypher):
driver = GraphDatabase.driver(
uri = os.environ["NEO4J_URI"],
auth = (os.environ["NEO4J_USERNAME"],
os.environ["NEO4J_PASSWORD"]))
session = driver.session()
widget = GraphWidget(graph = session.run(cypher).graph())
widget.node_label_mapping = 'id'
display(widget)
return widget

showGraph()

對於圖型資料庫進行搜尋

我們先建立一個搜索index,我們稱為vector_index,其中search_type=”hybrid”:表示使用混合搜尋,包括了向量相似性搜尋和傳統搜尋。接著我們建立一個GraphCypherQAChain,值得注意的參數中包含了兩個llm,一個是用來生成Cypher查詢的cypher_llm,增加資訊內容量,另一個則是用來產生問答的qa_llm,這邊我們把temp都設為0.5,讓回答保守一點。

vector_index = Neo4jVector.from_existing_graph(
OpenAIEmbeddings(),
search_type="hybrid",
node_label="Document",
text_node_properties=["text"],
embedding_node_property="embedding"
)

cypher_chain = GraphCypherQAChain.from_llm(
cypher_llm = ChatOpenAI(temperature=0.5, model_name='gpt-4'),
qa_llm = ChatOpenAI(temperature=0.5), graph=graph, verbose=True,
)

我們來問一下這個cypher_chain,請問彰化是不是台灣的一部分:

cypher_chain.invoke(
{"query": "is changhua a part of taiwan?"}
)

回答如下,你可以從他的回覆當中看到生成cypher查詢的語句:

> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (c:City {id: "changhua"}), (co:Country {id: "taiwan"}) RETURN EXISTS((c)-[:PART_OF]->(co))
Full Context:
[]

> Finished chain.
{'query': 'is changhua a part of taiwan?',
'result': 'Yes, Changhua is a part of Taiwan.'}

以上是我根據微軟論文與Neo4j研究人員Tomaž Bratanič的研究所做的一個小範例,以結果來說還可以接受,但如果需要完整的驗證,我覺得還是需要將更多的資料或者說知識放到我們的圖形資料庫裡面。

--

--

Eric Chang

Data Scientist. Strengthen learning through writing.