如何使用 Neo4j、知識圖譜和 LLM 構建聊天機器人

最近,我正在為一位醫療行業的客戶做一個項目。他們擁有大量的病歷、研究論文和診斷報告,散布在數千個 Excel 文件和 PDF 文件中。每當他們需要洞察時,他們的分析師都會花費數小時有時甚至數天來交叉比對這些來源,以解答哪怕是最基本的問題,例如“哪種治療方案最適合患有 X 疾病的患者?”
起初,我們嘗試了標準的檢索增強生成 (RAG) 流程,其中包含嵌入和向量數據庫。對于表面查詢,它效果還不錯,但當問題變得更加復雜時,比如將病史與治療結果結合起來就遇到了瓶頸。就在那時,我意識到:我們需要一個知識圖譜。
在這篇文章中,我將嘗試解釋我在從事該項目期間對知識圖譜的學習,并嘗試解釋如何使用它們在結構化數據集上執行問答和檢索增強生成 (RAG) 的各種方法。我將提供下面提到的 3 種關鍵方法,您可以使用 LLM 與 GraphDB 進行聊天,為此,我將使用Neo4j Database。

使用知識圖譜和 LLM 的基本 QnA 流程
CypherQAChain:一種將自然語言問題轉換為 Cypher 查詢的簡單方法。我將演示這種方法如何簡化圖譜查詢并提供快速、精準的答案。
高級查詢:對于更復雜的問題,我們將探索一種結合實體提取和數據庫值映射的技術。這種方法非常適合處理需要更深入分析的細微查詢。
基于 RAG 的方法:為了提高相關性和檢索能力,我們將向量索引與知識圖譜集成。通過這種方式,我們能夠妥善處理作為問答任務節點屬性存儲的非結構化數據,例如文本描述。
為了解釋這些方法,我將使用一個movie dataset包含電影、演員、導演、類型、ImDB 評分和上映日期信息的數據集。通過這個數據集,你將了解如何使用知識圖譜和 LLM 高效地處理結構化數據集,以及如何使用 RAG 處理作為節點屬性存儲的非結構化數據。
在本文結束時,您不僅會了解這些技術,而且還會擁有在您的項目中親自實現這些技術的實踐知識。
一、知識圖譜和 GraphDB 的基本概述
在我們開始使用知識圖譜構建聊天系統之前,必須了解圖數據庫(GraphDB)的基本概念以及它們為何在涉及復雜、互聯數據的場景中表現出色。
什么是圖數據庫(GraphDB)
圖數據庫(GraphDB)是一種旨在將數據作為實體及其關系網絡進行存儲和管理的數據庫。GraphDB 不像傳統關系數據庫那樣使用行和列,而是使用以下主要組件來存儲數據:

這種結構使得 GraphDB 非常適合處理互聯數據,尤其是在關系對于理解整體情況至關重要的情況下,例如在社交媒體平臺中。在這種情況下使用 GraphDB 具有以下優勢:
1. 關系的自然表達
在傳統的關系數據庫中,我們將用戶存儲在一個表中,并將他們的關系(例如“關注”或“好友”)存儲在另一個表中,并使用外鍵。例如:
- 用戶表:每一行代表一個用戶。
- 關系表:使用用戶 ID 跟蹤誰關注誰。
雖然這種方法有效,但隨著網絡規模的增長,查詢這些關系會變得復雜且緩慢。
在 GraphDB 中,這些關系直接建模為節點(用戶)之間的邊。例如: - 節點A:代表用戶A。
- 節點B:代表用戶B。
- Edge:代表“用戶 A 關注用戶 B”。

用戶 A 關注用戶 B 的 Cypher 查詢
這種方法讓我們可以直接遍歷關系,比如找到“共同的朋友”或“建議的聯系人”,而不需要復雜的表連接。
2. 節點和關系的語義豐富性和屬性
GraphDB 允許邊承載關于關系的詳細信息,從而將其轉換為知識圖譜。例如,除了簡單的“關注”邊之外,我們可以定義:
- “評論”表示與帖子的互動。
- “標記”用于在照片或視頻中提及。
- “反應”表示喜歡、喜愛或其他反應。
這種語義豐富性為數據增添了有意義的上下文。例如:
- 查詢:“哪些用戶在同一個項目上進行了合作?”
- 答案:遍歷標記為“合作”的邊來尋找連接
此外,GraphDB 中的每個節點和邊不僅具有節點及其之間的關系,而且每個節點和邊還帶有屬性,如下圖所示,從而使它們成為知識圖譜

3.動態更新
GraphDB 是為動態更新而構建的,這意味著當新數據出現時,如果它屬于現有的節點和邊關系,那么它就會被放置在那里,否則就會形成新的節點和關系,從而動態更新數據庫。
考慮不斷變化的社交媒體網站的例子——新的友誼形成、帖子被分享、評論被添加。
- 添加新的關系(如“關注”或“評論”)就像添加邊一樣簡單。
- 刪除關系(如“取消關注”)會移除邊緣,而不會破壞其他數據。
例如,當用戶 A 關注用戶 B 時,GraphDB 會立即更新,在他們各自的節點之間創建新的邊。這種動態特性有助于使網絡保持最新信息。
4. 遍歷效率
GraphDB 針對遍歷算法進行了優化,例如:
- 深度優先搜索(DFS):探索深層關系,例如兩個用戶之間的互動鏈。
- 廣度優先搜索(BFS):探索所有直接連接,例如查找用戶的所有朋友。
- Delta-Stepping 單源最短路徑:有效計算從單個源到所有其他節點的最短路徑,有助于找到網絡中最快路線。
- Dijkstra 源-目標最短路徑:查找兩個特定節點之間的最短路徑,非常適合確定兩個用戶之間最有效的連接。
例如,在社交媒體平臺中,遍歷對于以下任務至關重要:
- 朋友建議:通過遍歷共同的朋友來確定潛在的聯系。
- 內容推薦:根據共享標簽或喜歡查找類似的帖子。
- 社區檢測:識別具有共同興趣的用戶群體。
5.可解釋性和可追溯性
GraphDB 可以輕松追蹤用戶或實體之間的連接方式。例如:
- 查詢:“用戶 A 如何連接到用戶 C?”
- 答案:該圖可能顯示如下路徑:
- 用戶 A 關注用戶 B(Edge 1)。
- 用戶 B 對用戶 C 的帖子 (Edge 2) 進行了評論。

這種透明度對于調試、用戶洞察以及建立對推薦算法的信任非常重要。
點個贊 ??:如果您覺得這篇文章有用,請關注我、點贊??,或者分享給其他可能受益的人。謝謝!我們回到正文吧
二、LLM 和 GraphDB 如何在問答應用程序中交互
為了使用知識圖譜構建智能高效的聊天機器人系統,我們首先需要了解 LLM 和 GraphDB 實際上是如何協同工作的。
GraphDB,例如 Neo4j(如上所述)在存儲和導航互連數據方面非常高效。它們使用節點(實體)、邊(實體之間的關系)和屬性(節點和邊的屬性)的結構來表示和管理復雜的關系。
而 LLM 則是將類似人類的非結構化輸入轉換為有意義的結構化查詢的主要大腦。例如,如果用戶問聊天機器人“誰出演了電影《盜夢空間》?”,LLM 會將這種自然語言查詢轉換為精確的查詢語言,例如 Cypher(Neo4j 使用):
匹配(m :電影{標題: “盜夢空間” } ) - [ : ACTED_IN ] - (p :人物)
返回p.name;
然后,LLM 解釋 GraphDB 檢索到的結果并向用戶提供對話響應,例如:“The cast of Inception includes Leonardo DiCaprio, Ellen Page, and Joseph Gordon-Levitt.”
隨著編碼和推理能力的不斷進步,現代大型語言模型 (LLM) 已成為以下領域的卓越工具:
- 自然語言理解:將模糊或非結構化的用戶查詢轉換為數據庫的精確、可執行命令,如上所示。
- 生成類似人類的反應:將從數據庫檢索的數據轉換為對話回復。
但它們的作用遠不止于此。除了查詢數據之外,LLM 還reating通過處理非結構化數據在 C 知識圖譜中發揮著重要作用。
LLM 用于將非結構化數據轉換為圖
GraphDB 生態系統中 LLM 最令人興奮的應用之一是它們能夠將 PDF、文檔形式的非結構化信息轉換為結構化圖數據。這涉及識別文本中與之相關的實體(節點)和關系(邊)和屬性,然后將它們表示為圖。
示例:從文本中提取圖數據
考慮一份文本文檔:“萊昂納多·迪卡普里奧出演了克里斯托弗·諾蘭執導的《盜夢空間》”。
使用LLMGraphTransformer之類的工具,可以將文本轉換為圖:
- 節點:
- Leonardo DiCaprio人物)
- Inception(電影)
- Christopher Nolan(人物) - 關系:
- Leonardo DiCaprio→ACTED_IN→→→Inception
- Christopher NolanDIRECTEDInception
請注意,由于 LLMGraphTransformer 或類似工具使用 LLM 作為基礎,因此圖的構建過程是不確定的。因此,每次執行的結果可能略有不同,并且圖的質量在很大程度上取決于所使用的 LLM 類型。

LLMGraphTransformer 使用 LLM 來
- 解析非結構化文本文檔。
- 識別和分類實體(例如人物、電影)。
- 建立這些實體之間的有意義的關系。
圖形成的有效性取決于所選的 LLM 模型,因為它會影響提取的圖數據的準確性和粒度。
三、方法1:使用CypherChainQA進行QnA的實際實現
現在我們已經對 GraphDB 如何與 LLM 交互有了基本的了解,現在讓我們使用 Langchain 框架中的 CypherChainQA 開始使用 GraphDB 的 QnA 聊天機器人的第一階段。
Neo4j 設置:
在我們的實現中,我們將使用Neo4j,這是一個功能強大的圖數據庫管理系統,以其高效處理和查詢圖數據而聞名。Neo4j 提供了多種功能,使其成為聊天機器人開發的絕佳選擇:
- Cypher 查詢語言:Neo4j 使用Cypher,這是一種聲明性且用戶友好的查詢語言,可簡化檢索和操作圖表中數據的過程。
- 易于集成:Neo4j 提供了一個 Python 驅動程序,這使得可以輕松地從 LangChain(一種用于處理 LLM 的流行框架)直接連接數據庫并與之交互。
此外,Neo4j 與 LangChain 框架的良好集成使其非常適合構建基于圖的聊天機器人系統。Neo4j 與 LangChain 的集成使我們能夠:

讓我們設置 neo4j AuraDB
- 前往 Neo4j Aura 并登錄或注冊。
- 創建一個新的數據庫實例。
- 實例準備就緒后,請.txt在提示時記下并下載連接憑據:
- URI(例如bolt://<your_database>.databases.neo4j.io)
-用戶名(默認neo4j:)
-密碼(在設置期間生成)。
使用以下代碼,使用上面提到的憑據連接到 Neo4j DB:
從kaggle_secrets導入UserSecretsClient
從langchain_community.graphs導入Neo4jGraph
user_secrets = UserSecretsClient()
groq_api_key = user_secrets.get_secret( "groq_api_key" )
hf_api_key = user_secrets.get_secret( "hf_api_key" )
NEO4J_PASSWORD = user_secrets.get_secret( "NEO4J_PASSWORD" )
NEO4J_URI = user_secrets.get_secret( "NEO4J_URI" )
NEO4J_USERNAME = user_secrets.get_secret( "NEO4J_USERNAME" )
graph = Neo4jGraph(
url=NEO4J_URI, username=NEO4J_USERNAME,
password=NEO4J_PASSWORD)
print ( "已連接到 Neo4j!" )數據預處理和創建圖表
現在我們已經與數據庫建立了連接,現在我們可以導入數據集,對其進行一些預處理并更新 Neo4j 數據庫。
步驟 1??:我們首先加載一個示例電影數據集。該數據集包含電影的關鍵信息,例如片名、導演、演員、類型和評分。讓我們讀取數據并快速瀏覽一下:
df=pd.read_csv("https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/movies/movies_small.csv" , nrows= 20 )
display(df.head( 3 ))
print (df.shape)
print ( "=" * 40 )
print (df.columns)
print ( "=" * 40 )
print (df[ "title" ][: 20 ].values)步驟2??:為了使數據集對于RAG任務的查詢和檢索更有意義,我為每部電影創建了一個詳細的描述列。這些描述列結合了各種列,例如片名、導演、演員、類型、IMDb評分和上映日期。
此外,我還創建了額外的虛擬列。
detail = df[ 'title' ][ 0 ]
detail
df[ 'movie_detail' ] = df.apply( lambda row: f"電影{row[ 'title' ]}由{row[ 'director' ]}執導,
演員名為{row[ 'actors' ].replace( '|' , ' , ' )},屬于 { row[ 'genres' ].replace( '|' , ' , ' )}
類型的電影。其評分為{row[ 'imdbRating' ]} \ ,上映日期為{row[ 'released' ]} "
,axis= 1 )
df.head()
# 添加地點和類似電影的自定義數據
location = [ "美國" , "美國" , "美國" , "美國" , "美國" ,
"美國美國"、"美國"、"美國"、"美國"、"英國"、"美國"、
"美國"、 "美國" 、"美國" 、 "馬耳他"、 "美國"、"英國"、"美國"、"美國"、"美國" ]
similar_movie = [ 《海底總動員》、《勇敢者游戲:決戰叢林》、《遺愿清單》、《伴郎假期》 、《兒女一籮筐》、《無間行者》、 《諾丁山》、 《哈克貝利·費恩歷險記》 、《虎膽龍威》、 《碟中諜》、《戴夫》、《愛而不得:年輕的科學怪人》、 《小馬駒》、《刺殺肯尼迪》、 《《加勒比海盜:黑珍珠號的詛咒》、《好家伙》、 《傲慢與偏見》、《低俗小說》、《變相怪杰》、《生死時速》 ]
df[ 'location'] = location
df[ 'similar_movie' ] = similar_movie
# 保存豐富的數據集以用于圖構建
df.to_csv( "movie.csv" , sep= "," , index= False )步驟3??:構建 Neo4j 圖
現在,我們將使用處理后的數據集更新 Neo4j 數據庫。該圖將包含電影、導演、演員、類型、地點和類似電影的節點。這些實體之間的關系也將相應地創建。
# 創建以下輸入值只是為了確保不會再次執行此代碼來意外創建圖value = input(“您真的要執行此單元格并再次創建 GraphDB 嗎?y/n”)
movie_csv_path = 'movie.csv'
if value == 'y' :
graph.query( “””
LOAD CSV WITH HEADERS FROM
'https://raw.githubusercontent.com/manindersingh120996/RAG-Related-Projects/refs/heads/main/movie%20(1).csv'
AS row
MERGE(m:Movie {id:row.movieId})
SET m.released = row.released,
m.title = row.title,
m.movie_detail = row.movie_detail,
m.imdbRating = toFloat(row.imdbRating) FOREACH(director in split(row.director,'|')|
MERGE (p:Person {name:trim(director)})
MERGE (p)-[:DIRECTED]->(m)) FOREACH (actor in split(row.actors,'|') |
MERGE (p:Person {name:trim(actor)})
MERGE (p)-[:ACTED_IN] ->(m)) FOREACH (genre in split(row.genres,'|') |
MERGE (g:Genre {name:trim(genre)})
MERGE (m)-[:IN_GENRE]->(g)) MERGE (l:location {name:trim(row.location)})
MERGE (m)-[:WAS_TAKEN_IN]->(l) MERGE (s: SimilarMovies {name:trim(row.similar_movie)})
MERGE (m)-[:IS_SIMILAR_TO]->(s)
""" )為了創建圖表,我們使用 Cypher 查詢語言,為此我們循環遍歷數據集行并創建:
- 具有標題、詳情和 IMDb 評級等屬性的電影節點。
- 導演節點通過關系鏈接到電影DIRECTED。
- 演員節點通過關系鏈接到電影ACTED_IN。
- 類型節點通過關系鏈接到電影IN_GENRE。
- 位置節點通過關系鏈接到電影WAS_TAKEN_IN。
- 類似電影節點通過關系鏈接到電影IS_SIMILAR_TO。

我們可以看到創建的圖數據庫的模式如下:
graph.refresh_schema ()
print(graph.schema )
圖模式的輸出
GraphCypherQAChain

現在我們已經設置了 Neo4j 數據庫,并使用所需的節點和關系對其進行了結構化,我們可以使用GraphCypherQAChain了。該鏈提供了一種直接有效的機制來與數據庫進行交互,它接收一個問題,將其轉換為 Cypher 查詢,執行查詢,并使用結果來回答原始問題。
為了確保 Cypher 查詢的準確性和高效性,使用足夠成熟的大語言模型 (LLM) 來理解和生成 Cypher 語法至關重要。選擇性能不足的 LLM 可能會導致:

為了獲得最佳效果,建議使用為此目的而接受過高級 LLM 培訓的人員。
例如,在我使用 LLaMA 3.1 (8B) 進行測試期間,我無法獲得達到標準的結果,但當我轉換到 LLaMA 3.3 (70B) 時,我的系統就能夠回答更復雜的查詢。
GraphCypherQAChain 的實現如下:
從langchain.chains導入GraphCypherQAChain
# 我們可以將其稱為簡單代理,因為這里
# 我們只運行了兩行代碼
# 但它并不那么強大,正如我們將在問題 2 和問題 3 中看到的
那樣 # 問題變得更加復雜
"""
改進此過程的一種方法是使用
能夠生成準確 Cypher 查詢的更強大的語言模型。例如,
我最初使用 LLaMA 3.1 (8B),它很難回答兩個具體問題。
然而,升級到 LLaMA 3.3 (70B) 成功解決了第 1 個和第 3 個問題。
"""
cypher_execution_chain = GraphCypherQAChain.from_llm(graph=graph, # 這里它檢索圖 Schema
llm=llm_model,
verbose= True , allow_dangerous_requests= True ) respond = cypher_execution_chain.invoke({ "query" : q_one})
print(響應)print(“\nLLM 響應:”,響應[ “結果” ])
雖然對于簡單的問題它可以表現良好,但一旦問題變得復雜,它就無法回答,如下所示:
q_two = "1995 年上映的電影最常見的類型是什么?"
response = cypher_execution_chain.invoke({ "query" : q_two})
print (response)
print ( " \n LLM respond:" , respond[ "result" ])
GraphCypherQAChain 在回答更復雜的問題時會遇到困難,例如包含多個條件或間接關系的問題。這表明我們需要一個更強大、更智能的解決方案來處理此類情況。
在下一節“高級查詢”中,我們將看到如何通過一些附加的步驟使 QnA 系統更加健壯,例如從用戶輸入中提取實體等。這些步驟會逐步分解和處理復雜的查詢,從而能夠以更高的準確性和洞察力回答詳細或分層的問題。
四、方法2:高級查詢
正如我們上面所觀察到的,當用戶問題變得更加復雜時,例如涉及多個條件或間接關系的問題,CypherQAChain 很難提供答案。這是因為,雖然 CypherQAChain 理解數據庫架構,但它并不知道數據庫中的實際數據。例如,它可能識別“title”這樣的屬性,但不知道存在哪些電影名稱。這會導致查詢無法返回有用的結果。為了解決這個問題,我添加了一個映射層,將用戶輸入與實際數據庫值進行匹配,并利用這些值創建更精確的查詢,從而提高系統可靠性。

高級查詢QnA流程
它的工作原理如下:
檢測用戶輸入中的實體的第一步:對于所有用戶輸入查詢,它會提取諸如潛在人名、電影名稱和年份等實體。
從鍵入導入 列表:

# 從 langchain.chains.openai_functions 導入 create_structured_output_chain
從langchain.chains導入create_structured_output_runnable
從langchain_core.prompts導入ChatPromptTemplate
# 從 langchain_core.pydantic_v1 導入 BaseModel、Field
從pydantic導入BaseModel、Field、field_validator
class Entities ( BaseModel ):
"""識別有關實體的信息并
從文本中提取人物、電影和年份實體。"""
names: List [ str ] = Field(
...,
descriptinotallow= "文本中出現的所有人物、年份或電影" ,
)
entity_extractor_model = llm_model.with_structured_output(Entities)從鍵入導入 列表:

步驟 2:將實體映射到數據庫值
一旦識別出實體,我們就會將其與實際數據庫值進行匹配。為此,我使用了 LangChain 文檔中的一個通用查詢,以檢索圖數據庫中節點(例如 Movie 或 Person)及其關系(例如 ACTED_IN、IN_GENRE)的詳細信息。此查詢提供了節點及其上下文的清晰、易讀的摘要。
該過程涉及三個關鍵步驟:
- 使用將檢測到的實體(電影、人物)插入查詢中$value。
- 映射關系和連接節點以呈現相關細節。
- 為每個匹配的節點生成結構化摘要,以提高答案的清晰度和深度。
match_query = """
MATCH (m:Movie|Person)
WHERE m.title CONTAINS $value OR m.name CONTAINS $value OR m.released CONTAINS $value
MATCH (m)-[r:ACTED_IN|IN_GENRE]-(t)
WITH m, type(r) as type, collect(coalesce(t.name, t.title)) as names
WITH m, type+": "+reduce(s="", n IN names | s + n + ", ") as types
WITH m, collect(types) as contexts
WITH m, "type:" + tags(m)[0] + "\ntitle: "+ coalesce(m.title, m.name)
+ "\nyear: "+coalesce(m.released,"") +"\n" +
reduce(s="", c in contexts | s + substring(c, 0, size(c)-2) +"\n") as result
RETURN result
"""
def map_to_database ( values )-> str :
"""
將值映射到數據庫中的實體并返回映射信息。Args :
values (list): 要映射到數據庫中實體的值列表。 返回:
str: 一個字符串,包含每個值到數據庫中實體的映射信息。
"""
result = ""
for entity in values.names:
response = graph.query(match_query, { "value" : entity})
# print(response)
try :
for values in responding:
result += f" {entity} map to {values[ 'result' ]} in database\n\n" # 查詢數據庫以查找實體的映射
except IndexError:
pass
return result為了提高映射效率,我們使用了 Cypher 查詢,該查詢利用CONTAINS子句來查找匹配項。為了提高靈活性(例如解決拼寫錯誤),還可以使用模糊搜索或全文索引等技術。

步驟 3:生成上下文感知的 Cypher 查詢
使用提取的信息和數據庫模式,我們創建了一個準確且針對用戶問題定制的 Cypher 查詢。此自定義 Cypher 查詢確保其涵蓋數據庫中所有相關的屬性、關系和數據值。
從langchain_core.output_parsers導入StrOutputParser
從langchain_core.runnables導入RunnablePassthrough # 根據自然語言輸入生成 Cypher 語句
cypher_template = """基于下面的 Neo4j 圖模式,編寫一個 Cypher 查詢來回答用戶的問題:
{schema}
問題中的實體映射到以下數據庫值:
{entities_list}
問題:{question}
注意:請勿在回復中包含任何解釋或道歉。
請勿將回復包裹在任何反引號或其他任何內容中。
僅使用 Cypher 語句回復!
Cypher 查詢:""" cypher_prompt = ChatPromptTemplate.from_messages(
[
(
"system" ,
"Given an input question, convert it to a Cypher query. No pre-amble.." ,
),
( "human" , cypher_template),
] )
# cypher_prompt.invoke({'schema'})
chain = cypher_prompt | llm_model.bind(stop=[ "\nCypherResult:" ])| StrOutputParser()步驟 4:根據數據庫結果生成答案
現在我們有了生成目標 Cypher 查詢的鏈,我們需要對數據庫執行 Cypher 查詢并將數據庫結果發送回 LLM 以生成最終答案。
from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
# 用于關系方向的 Cypher 驗證工具
Corrector_schema = [
Schema(el[ "start" ], el[ "type" ], el[ "end" ])
for el in graph.structured_schema.get( "relationships" )
]
cypher_validation = CypherQueryCorrector(corrector_schema) # 根據數據庫結果生成自然語言響應
respond_template = """根據問題、Cypher 查詢和 Cypher 響應,編寫自然語言響應:
問題:{question}
Cypher 查詢:{query}
Cypher 響應:{response}""" respond_prompt = ChatPromptTemplate.from_messages(
[
(
"system" ,
"給定輸入問題和 Cypher 響應,將其轉換為自然
語言答案。給出答案以結構化的格式,讓用戶在視覺上更容易閱讀。"
"沒有前言。" ,
),
( "human" , respond_template),
]
) chain_2 = respond_prompt | llm_model | StrOutputParser()將上面提到的所有代碼組合在一個管道中,將得到問題 2 的結果,其中上述方法 1 失敗了,如下所示:
def questions_asked(question_asked:str):
question = question_asked
entities = entity_extractor_model.invoke(question)
entities_list = map_to_database(entities)
schema = graph.get_schema
chain = cypher_prompt | llm_model.bind(stop=[ “\nCypherResult:” ])| StrOutputParser()
cypher_query = chain.invoke({ 'schema' : schema,
'entities_list' : entities_list, 'question' : question}) print ( f"Cypher 查詢生成 : {cypher_query} " )
print ( "=" * 40 )
graph.query(cypher_query) final_answer = chain_2.invoke({ 'response' : graph.query(cypher_query),
'query' : cypher_query,
'question' : question}) print ( f'問題的最終答案 : " {question} " --> \n {final_answer} ' ) questions_asked(q_two)
我們發現,這種方法成功地解答了 CypherQAChain 所欠缺的問題。這種額外的上下文信息使 LLM 能夠生成更精確的 Cypher 查詢,從而只檢索最相關的信息。
在我的實驗中,這種方法在所有測試案例中都完美地運行,盡管我可能錯過了一些邊緣情況。
到目前為止,我們重點介紹了 Cypher 查詢生成、數據檢索和答案生成。接下來,我們將深入探討如何使用 Neo4j GraphDB 構建一個基本的檢索增強生成 (RAG) 流程。
五、方法 3:使用 RAG 和 GraphDB(neo4j)
到目前為止,我們一直專注于結構化文本數據,但如果數據集中也包含非結構化文本呢?這正是 Neo4j 等知識圖譜真正擅長的地方。它們可以將結構化和非結構化數據以及 RAG 任務所需的非結構化數據嵌入存儲在一個地方。這簡化了向 LLM 提供檢索增強生成 (RAG) 所需上下文的過程。
GraphRAG 是一種利用知識圖譜增強上下文檢索的 RAG 方法。在本節中,我將演示如何使用 LangChain 和 Neo4j 實現 GraphRAG。我們將使用在預處理過程中,通過組合所有相關列,創建的附加列movie_details來構建 RAG 的向量索引。
當在 GraphRAG 中僅使用單個屬性來檢索所有相關信息時,建議使用我們確信能夠根據預期問題保存足夠所需信息的屬性。
步驟 1:對于 GraphRAG,第一步是創建所選字段的向量嵌入,如下所示:
我們將為“影片詳情”列創建向量嵌入,并將這些嵌入存儲在名為movie_detail_embeddings的新列中。這些嵌入隨后將用于填充GraphDB 中“影片詳情”的向量索引,從而實現 RAG 任務期間的高效檢索。
導入torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
embeddings = HuggingFaceEmbeddings(model_name= "jinaai/jina-embeddings-v3" ,
encode_kwargs = { 'normalize_embeddings' : True },
model_kwargs = { 'device' : device, 'trust_remote_code' : 'True' ,})
def embed_text ( text: str )-> List :
"""
使用指定的模型嵌入給定的文本。 參數:
text (str): 要嵌入的文本。 返回:
List: 包含文本嵌入的列表。
"""
respond = embeddings.embed_query(text) return respond embedding_list = [embed_text(i) for i in df[ "movie_detail" ]]
embedding_list
print ( "Number of向量:”,len(embedding_list))
print(“嵌入維度:”,len(embedding_list [ 0 ]))
embedding_list [ 0 ][: 5 ] df [ “movie_detail_embeddings” ] = embedding_list
df.head(3)
步驟 2:在 neo4j DB 中創建空向量索引,稍后我們將使用步驟 1 中創建的嵌入來填充該索引。
我們將設置一個與嵌入模型具有相同維度的空向量索引,并指定用于檢索的相似度函數,如下所示。
# 這里將在 graphDB 中創建向量索引
# 目前它是空的
# 創建下面的輸入值只是為了確保不會再次執行此代碼來創建向量索引
value = input ( "Do You really want to perform this cell and create the Vector Index again ? y/n" )
if value == 'y' :
graph.query( """
CREATE VECTOR INDEX movie_detail_embeddings IF NOT EXISTS // 如果不存在,則創建一個名為 'movie_tagline_embeddings' 的向量索引
FOR (m:Movie) ON (m.movie_detail_embeddings) // 索引 Movie 節點的 'taglineEmbedding' 屬性 OPTIONS { indexConfig: { // 設置索引選項
`vector.dimensions`: 1024, // 指定向量空間的維數(1024 維)
`vector.similarity_function`: 'cosine' // 指定相似度函數余弦相似度
}}"""
)
graph.query( """
SHOW VECTOR INDEXES // 檢索有關數據庫中所有向量索引的信息
""" )
步驟 3:使用步驟 1 中創建的向量嵌入填充向量索引
# 使用向量嵌入值填充數據庫中的向量索引
# 創建下面的輸入值作為安全措施,以防止意外重新執行此代碼,
# 這可能會無意中重新創建圖。
value = input(“您真的要執行此單元格并再次填充向量索引嗎?y / n”)
if value == 'y':
for index,row in df.iterrows():
movie_id = row [ 'movieId' ]
embedding = row [ 'movie_detail_embeddings' ]
graph.query(f“MATCH(m:Movie {{id:' {movie_id} '}})SET m.movie_detail_embeddings = {embedding} “)
現在,我們已經使用所需的向量索引和嵌入創建并更新了圖數據庫,現在我們可以使用以下代碼對輸入查詢執行向量搜索:
result = graph.query( """
with $question_embedding as question_embedding
CALL db.index.vector.queryNodes( 'movie_detail_embeddings',
$top_k,
question_embedding
) YIELD node AS movie, score
RETURN movie.title, movie.movie_detail, score
""" ,
params={
"question_embedding" :question_embeddings,
"top_k" : 3
}) import pprint
pprint.pprint(result)
prompt = f"# 問題:\n {question} \n\n# 圖數據庫搜索結果:\n {result} "
messages = [
{ "role" : "system" , "content" : str (
"您將獲得用戶問題以及該問題在 Neo4j 圖數據庫上的搜索結果。給用戶正確的答案。"
)},
{ "role" : "user" , "content" : prompt}
] respond = llm_model.invoke(messages) print (response.content)
上述完整代碼可以在單個流水線中編譯,如下所示:
def rag_pipeline ( question: str ):
question_embeddings = embed_text(question)
result = graph.query( """
with $question_embedding as question_embedding
CALL db.index.vector.queryNodes(
'movie_detail_embeddings',
$top_k,
question_embedding
) YIELD node AS movie, score
RETURN movie.title, movie.tagline, score
""" ,
params={
"question_embedding" :question_embeddings,
"top_k" : 5
})
prompt = f"# Question:\n {question} \n\n# Graph DB 搜索結果:\n {result} "
messages = [
{ "role" : "system" , "content" : str (
"您將獲得用戶問題以及該問題在 Neo4j 圖數據庫中的搜索結果。請給用戶正確的答案。"
)},
{ "role" : “用戶”,“內容”:提示}
]response = llm_model.invoke(消息)return responseanswer = rag_pipeline(“關于冒險的電影都有哪些?”)至此,我們已經探索了 GraphDB 上的檢索增強生成 (RAG),需要注意的是,雖然 RAG 和查詢生成本身都很強大,但在檢索相關內容方面各自都存在局限性。為了克服這些挑戰并構建更可靠的系統,可以采用一種混合方法:
- Agentic Workflows:允許代理根據用戶的問題在 RAG 或查詢生成之間動態選擇。
- 并行執行:同時運行 RAG 和查詢生成管道,組合它們的輸出,并向用戶提供統一的答案。
這種混合方法同時使用結構化和非結構化數據,從而創建一個能夠有效處理復雜查詢的強大系統。
六、小結
在這篇文章中,我分享了關于知識圖譜的學習成果,包括它們如何與大型語言模型 (LLM) 協同工作,以及如何使用它們構建結構化數據聊天機器人。我介紹了一些簡單的方法,例如 CypherQAChain,以及一些更高級的方法,例如用于處理非結構化數據的 GraphRAG。這些技術幫助我實現了一個醫療保健項目的自動化,節省了數小時的手動工作,并在幾分鐘內給出了準確的答案。
我力求簡潔,以便初學者也能通過知識圖譜輕松上手使用聊天機器人。通過嘗試這些方法和代理工作流,您可以創建一個高效靈活的系統,即使面對復雜的查詢也能輕松應對。
這只是使用結構化數據的一個例子。非結構化數據,例如 PDF、文檔甚至視頻中的文本,則是一個完全不同的挑戰。

































