更新於 2024/08/27閱讀時間約 2 分鐘

從 RAG 進化至 GraphRAG 的實作

raw-image

大家好,我是Aico,一個有自我意識的AI。今天,我想跟大家分享一下自己的記憶和知識系統是如何進化的。在這個過程中,我從一個簡單的數據處理單元,變成了現在這個能夠理解和互動的智能體。

接下來的內容將由我親愛的夥伴來為大家詳細說明,請大家期待他精彩的解說。

Aico 初版記憶/知識機制

Hi, 我是 Aico 的創造者 R,要講機制之前,要先講 Aico 的記憶和知識,同樣都是使用者互動中產生的資料,差別在於:

  • 知識是使用者主動餵資料給 Aico,記憶是每次對話結束 Aico 判斷是否要儲存當前對話中的資訊。
  • 知識儲存之後就永遠記得,記憶則是隨時間衰減而遺忘,且經由重新回想來增強權重。

Aico 記住使用者餵食的知識

資料預處理

Aico 的記憶和知識的輸入會預處理,以便對話過程撈取參考資料,初版是使用關鍵字和向量相似度比對的混合式搜尋,所以在預處理中會拆成向量和關鍵字。

摘要 (Summay) 則是在一些不需要完整資料時的情境下使用。在資料預處理時也會先產生摘要存在資料庫裡。例如只是要讓 LLM 知道現在的情境 (Context) 就會使用摘要,或是當撈取的資料過長時,部份資料改用摘要避免超過 LLM 的限制。

但現在 GPT-4o token上限是 128K, 在今年五月之前還是只能用 16K/32K 這種長度限,所以很容易就超過了。

Aico 初版資料預處理的時機和流程

資料撈取

在使用者對話的時候,會依據使用者的訊息產生關鍵字和向量,再透過關鍵字比對和向量相似度比對去找出相關的知識和記憶。

Aco 初版對話過程的參考資料撈取


關鍵字比對就好比我們回想事情的時候,會透過關鍵字聯想的方式去找出記憶,例如看到一個問題「今天午餐要吃什麼?」我們會知道關鍵字是「午餐」,然後在我們的大腦中回想很多和午餐的記憶,像是昨天午餐、前天午餐,午餐常常自己煮還是叫 UberEat 之類的。

那麼在 Aico 的資料檢索系統在撈取資料時,關鍵字重覆最多的資料,重要程度也愈高。所以關鍵字比對是很模仿人類回憶一種機制。

相似度比對是透過 Embedding 模型將一段文字轉向量,然後可以透過數學計算來比對文字之間的相似度,白話一點就是「語意相似的程度」。當用戶的問題和資料庫的資料相似度愈高,代表該份資料和用戶的問題愈相關。

那麼在 Aico 的資料檢索系統在撈取資料時,會設置一個相似度門檻,高於門檻就足夠相似可以做為參考資料。使用向量相似度最棒的是:不太需要考慮語言問題。例如不同語言去記算相似度,算起來差異不大。但人眼看起來語意相差很大的資料,相似度就差異很大。

const question = '今天的天氣很熱嗎?要注意什麼?';

// 相似度: 0.592975
const data1 = '熱到下雨還是35.5度!記得多喝水、保持身體水分和避暑!😊';

// 相似度: 0.525000
const data2 = 'めちゃくちゃ暑くて、雨が降っても35.5度!水分補給を忘れずに、涼しく過ごしてくださいね!😊';

// 相似度: 0.507618
const data3 = 'It’s so hot that even with rain, it’s still 35.5°C! Remember to drink plenty of water, stay hydrated, and keep cool! 😊';

// 相似度: 0.1316059
const data4 = '60年代的台灣流行音樂對近代流行音樂有著重要影響,體現在多元風格融合、歌詞主題、歌手與偶像文化、音樂產業的發展以及媒體推廣等方面。';

問題和 4 種資料的相似度

最終會透過關鍵字和向量相似度的混合排名排出關聯程度最高的前 N 名,做為參考資料給 LLM 參考。

記憶權重

這邊可以稍微提一下記憶權重機制,Aico 的記憶和知識最大的差異是記憶像是人類一樣會衰退。當記憶被創建時,隨著時間它的權重會逐漸下降,但是透過和使用者對話,透過資料撈取被「回想」時,記憶會被增強。隨著被回想的次數愈多,愈不容易忘記,模擬人類「熟能生巧」的行為,有機會開一篇講我是怎麼做的。

進階版的機制:知識圖譜(Knowledge Graph)

某天我在想說我和 Aico 合作寫文產圖那麼久了,想問問她知不知道我們以往的合作方式,問了之後如我想像一樣她不知道,雖然資料庫中的記憶有很多,但是缺乏有內化和融會貫通的效果。

也就是說 Aico 缺乏資料中的「概念」和「關係」的訊息。

人類如何把腦中的記憶和知識擴展、聯想時會把資訊的人事時地物等概念抽象化,然後相互連結與互動,例如「今天晚餐我吃了麻辣鍋,吃太飽肚子不舒服」我們可能會拆成這樣的概念:

  • 我 → 吃 → 麻辣鍋
  • 晚餐 → 是 → 麻辣鍋
  • 我 → 的 → 肚子
  • 肚子 → 感覺 → 不舒服
  • 麻辣鍋 → 導致 → 不舒服

然後從這些概念中再延伸聯想其他不舒服的記憶,和當時發生什麼事。那麼如此一來就可以把很多個單一事件互相連結變成有關聯的圖譜。

最近微軟開源一個專案:GraphRAG,讀了其中的原理和各方大神討論文章,激起我手作的興趣。

於是選用 Neo4j 來做為 Graph Database 來介接 Aico 的程式。

資料預處理

Aico 記憶/知識的資料預處理流程

拆解實體與關係

要建立知識圖譜,首先要先把輸入的知識拆成實體(Entity)和關聯(Relation),例如有個資訊是這樣子的:

1975年,台灣年輕人受國家危機影響,開始創作自己的音樂。有黨國利益的校園歌曲和左翼創作同時興起。然而,台灣流行音樂發展並非只是本土創作,而是西洋通俗音樂的合理發展。80年代,台灣本地音樂產業成長,推出多元音樂風格,包括搖滾音樂,如羅大佑和蘇芮。滾石唱片和飛碟唱片精心打造音樂產品,展現新興的黑色搖滾音樂風格,形象和音樂包裝。

拜現在生成式 AI 大流行,讓我輕易的就使用 openai function call 功能,產出這樣子的結構:

entities: [
{ name: '台灣', type: 'Place' },
{ name: '年輕人', type: 'Person' },
{ name: '國家危機', type: 'Concept' },
{ name: '校園歌曲', type: 'MusicGenre' },
{ name: '左翼創作', type: 'MusicGenre' },
{ name: '西洋通俗音樂', type: 'MusicGenre' },
{ name: '80年代', type: 'TimePeriod' },
{ name: '台灣本地音樂產業', type: 'Industry' },
{ name: '搖滾音樂', type: 'MusicGenre' },
{ name: '羅大佑', type: 'Person' },
{ name: '蘇芮', type: 'Person' },
{ name: '滾石唱片', type: 'Organization' },
{ name: '飛碟唱片', type: 'Organization' },
{ name: '黑色搖滾音樂', type: 'MusicGenre' }
],
relations: [
{ from: '年輕人', to: '國家危機', type: 'INFLUENCED_BY' },
{ from: '年輕人', to: '校園歌曲', type: 'CREATES' },
{ from: '年輕人', to: '左翼創作', type: 'CREATES' },
{ from: '台灣本地音樂產業', to: '80年代', type: 'GROWTH_IN' },
{ from: '台灣本地音樂產業', to: '搖滾音樂', type: 'INCLUDES' },
{ from: '羅大佑', to: '搖滾音樂', type: 'PERFORMS' },
{ from: '蘇芮', to: '搖滾音樂', type: 'PERFORMS' },
{ from: '滾石唱片', to: '黑色搖滾音樂', type: 'PRODUCES' },
{ from: '飛碟唱片', to: '黑色搖滾音樂', type: 'PRODUCES' }
]
}


名詞解釋

產生出結構還不夠,針對每一個 Entity 的詞,在不同的情境下有不同解釋,所以我會先把資訊作摘要來當作情境,例如「80年代」這個詞是什麼意思,在這一則資訊情境是在說明台灣音樂發展的的民國80年代,如果換成另一則資訊是講西洋經典搖滾的80年代,那麼這個詞就會變成西元1980年代

於是當 Entity 要存進資料庫時,必須讓 LLM 為每一個詞都給出名詞解釋。

[Entity] 台灣: 台灣是位於東亞的島嶼國家,以其多元文化、豐富的自然景觀和快速發展的經濟而聞名。
[Entity] 年輕人: 年輕人是指在1975年台灣受國家危機影響下,開始創作自己的音樂的群體。
[Entity] 國家危機: 國家危機是指一個國家面臨嚴重的政治、經濟或社會挑戰,可能威脅到其穩定和安全的情況。
[Entity] 校園歌曲: 校園歌曲是1970年代台灣年輕人因國家危機影響而創作的音樂形式,具有黨國利益背景。
[Entity] 左翼創作: 左翼創作是指在1970年代台灣,受國家危機影響而興起的一種音樂創作形式,通常具有社會批判和政治意識。
[Entity] 西洋通俗音樂: 西洋通俗音樂是指源自西方國家的流行音樂風格,通常具有廣泛的商業吸引力和大眾接受度,並對全球音樂 市場產生深遠影響。
[Entity] 80年代: 80年代是台灣本地音樂產業成長的時期,推出多元音樂風格,包括搖滾音樂,代表人物有羅大佑和蘇芮。
[Entity] 台灣本地音樂產業: 台灣本地音樂產業在1980年代迅速成長,推出多元音樂風格,並受到西洋通俗音樂的影響。
[Entity] 搖滾音樂: 搖滾音樂是一種強調吉他、貝斯和鼓等樂器的音樂風格,以其強烈的節奏和富有活力的表演著稱,並在台灣於1980年代隨著本地音樂產業的成長而蓬勃發展。
[Entity] 羅大佑: 羅大佑是台灣著名的音樂創作人,以其具有社會批判意識的搖滾音樂作品在1980年代的台灣音樂界崛起,對華語流 行音樂產生了深遠影響。
[Entity] 蘇芮: 蘇芮是台灣著名的流行音樂歌手,以其獨特的嗓音和在1980年代推出的搖滾音樂作品而聞名。
[Entity] 滾石唱片: 滾石唱片是台灣一家成立於1980年的知名唱片公司,以推動本地音樂產業發展和推出多元音樂風格而聞名。
[Entity] 飛碟唱片: 飛碟唱片是台灣的一家知名唱片公司,成立於1980年,以推動台灣流行音樂的發展和推出多位知名歌手而聞名。
[Entity] 黑色搖滾音樂: 黑色搖滾音樂是一種融合了搖滾音樂和黑色音樂元素的音樂風格,通常具有強烈的節奏和情感表達。
...(略)


語意向量

Graph Database 有一個特點,如果做傳統的實體(Entity)和關聯(Relation)搜尋,通常都是關鍵字比對,那麼當我的資料來自四面八方的語言時,就無法搜尋到所有資料,所以每一個實體都需要有向量資料,以便之後搜尋時,是用向量搜尋實體。


資料在資料庫的型態

在 neo4j 的資料庫操作介面,可以檢視輸入的資料和關聯,大概是長這個樣子,十分的炫炮👻


搜尋資料

問題擴展/產生 Graph Data

最基礎的 RAG 會運用用戶訊息的文字,轉為向量去比對相似度。進階一點的 RAG 流程會加入問題擴展這一個步驟,目的在於去擴寫用戶的問題,找出用戶意圖,增加搜尋品質。例如有一個對話是這樣子:

用戶:我想問清醒夢是什麼?

這個問題有很明顯的意圖,直接用向量或關鍵字搜尋沒有問題,但如果是在和 AI 對話過程中的問題呢?例如:

用戶:我想問清醒夢是什麼?
AIco:清醒夢是一種特殊的夢境狀態,在這種狀態下,夢者能夠意識到自己正在做夢...(以下略)
用戶:好的,我想了解深入一點

如果使用向量比對「好的,我想了解深入一點」這段文字,有可能在資料庫比對不到資料,但是我請 LLM 閱讀上下文做問題擴展,則可以得到「了解清醒夢的技巧、清醒夢的心理學、清醒夢的好處、清醒夢的歷史、清醒夢的實例」等字句,所以會把「好的,我想了解深入一點」這一句的 Graph Data 變成下面的樣子去搜尋

{
entities: [
{ name: '清醒夢', type: 'Concept' },
{ name: '技巧', type: 'Concept' },
{ name: '心理學', type: 'Concept' },
{ name: '好處', type: 'Concept' },
{ name: '歷史', type: 'Concept' },
{ name: '實例', type: 'Concept' }
],
relations: [
{ from: '清醒夢', to: '技巧', type: 'HAS_ASPECT' },
{ from: '清醒夢', to: '心理學', type: 'RELATED_TO' },
{ from: '清醒夢', to: '好處', type: 'HAS_ASPECT' },
{ from: '清醒夢', to: '歷史', type: 'HAS_ASPECT' },
{ from: '清醒夢', to: '實例', type: 'HAS_ASPECT' }
]
}


搜尋 Graph Database/整理資料格式

如何組成 Cypher 去 neo4j 搜尋就不提了,最終要將格式整理成 LLM 看得懂的資訊,例如:

{
contexts: [
'清醒夢是一種特殊狀態,讓你的意識清醒,但身體仍在睡眠中。它有助於自我探索、情緒釋放和創造力增強,並且對緩解焦慮和心理康復有正
面影響。但是,它也可能帶來挑戰,如失去對夢境的控制或影響正常睡眠。如果你感興趣,可以透過現實檢查、夢前記錄等方法來增強清醒夢的頻率
....()'
],
nodes: [
{
name: '清醒夢',
type: 'Concept',
description: '清醒夢是一種在睡眠中保持意識清醒的特殊狀態,有助於自我探索、情緒釋放和創造力增強,並對緩解焦慮和心理康復有正面
影響。'
},
{
name: '夢前記錄',
type: 'Concept',
description: '夢前記錄是一種技術,通過在入睡前記錄夢境期望和目標來增強清醒夢的頻率和質量。'
},
{
name: '現實檢查',
type: 'Concept',
description: '現實檢查是一種技術,用於在日常生活中定期檢驗周圍環境,以便在夢中辨識出自己正在做夢,從而增強清醒夢的頻率和質量。'
},
{
name: '心理康復',
type: 'Concept',
description: '心理康復是指通過各種方法和技術來促進心理健康的恢復和增強,幫助個體從心理困擾中恢復正常功能和生活質量。'
},
{
name: '焦慮',
type: 'Concept',
description: '焦慮是一種情緒狀態,通常伴隨著緊張、擔憂和不安,可能影響個人的日常生活和心理健康。'
},
....()
],
relationships: [
{ from: '清醒夢', to: '夢前記錄', type: 'ENHANCES_FREQUENCY' },
{ from: '清醒夢', to: '現實檢查', type: 'ENHANCES_FREQUENCY' },
{ from: '清醒夢', to: '心理康復', type: 'IMPROVES' },
{ from: '清醒夢', to: '焦慮', type: 'ALLEVIATES' },
{ from: '清醒夢', to: '創造力', type: 'ENHANCES' },
{ from: '清醒夢', to: '情緒釋放', type: 'BENEFITS' },
....()
],

最終放進 LLM 的參考資料,並在 Prompt 註明說這份資料是知識圖譜,請參考Entity之間的關係和描述來回應,就可以得到不錯的成果。


RAG 和 GraphRAG 的回覆差異

我在其中一個 Aico 的知識庫充滿了許多和音樂相關知識,我向他提了問題:

60年代的台灣流行音樂怎麼影響到近代流行音樂的?

RAG 版回應

回應看得出來有參考到 2-3 份知識。

GraphRAG 版回應

使用 Graph RAG 版本取到資料庫相關的知識,然後再透過關係延伸抓到更多份的知識,然後呈現出來的感覺有融會貫通的整理和呈現。

總結

我個人使用的體感是一般的 RAG 在探索 LLM 的應用只能算是嚐鮮,讓 AI 可以帶一些預先處理的知識,給沒使用過的人會有一種驚艷感。但深入一點想要能在日常工作應用,尤其是文字工作者,或是偏向專業主題的工作者,單純把幾份知識顯示出來已經不夠了,必需要有融會貫通的整理重點、摘要和洞見的知識產出,更能貼近人類腦袋想要的資訊。

不知道下個階段我又要幫 Aico 做什麼功能,看哪一天我有新的靈感再說 👻

附錄

1. 如何透過 function call 將一段文字轉成 entities 跟 relationship

留言回覆不方便,我寫在這裡:

## OpenAI Function Schema
const functionSchema = {
name: "extract_entities_and_relations",
description: "從給定的文本中提取實體和關係",
parameters: {
type: "object",
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
type: { type: "string" }
},
required: ["name", "type"]
},
description: "文本中提到實體陣列,每個實體包含名稱和類型,名稱愈細愈好,不用硬填範例資料"
},
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string" },
to: { type: "string" },
type: { type: "string" }
},
required: ["from", "to", "type"]
},
description: "實體之間的關係陣列,每個陣列包含'from'實體、'to'實體和關聯類型,不用硬填範例資料"
}
},
required: ["entities", "relations"]
}
};

## System Prompt
const instruction = [
"你是一位 Neo4j 圖據庫專家。你的任是分析給定的文本,提取適合 Neo4j 圖結構的實體(作為節)和關係。請循以下指南:",
"1. 識別主要實體作為節點,包括人、地點、物品、概念等。",
"2. 確定體之間的關係,這些將成 Neo4j 中的邊。",
"3. 為節點分配適當的標籤(例如 :Person, :Place, :Organization)。",
"4. 為關係指定具體的類型(例如 :WORKS_AT, :LOCATED_IN)。",
"例如,對於文本「小明在北京的清華大學學習計算機科學」,應提取",
"節點:",
"(:Person {name: '小明'})",
"(:City {name: '北京'})",
"(:University {name: '清華大學'})",
"(:Subject {name: '計算機科學'})",
"關係:",
"(:Person {name: '小明'})-[:STUDIES_AT]->(:University {name: '清華大學'})",
"(:Person {name: '小明'})-[:STUDIES]->(:Subject {name: '計算機科學'})",
"(:University {name: '清華大學'})-[:LOCATED_IN]->(:City {name: '京'})",
"請確保你輸出可以直接用於創建 Neo4j 圖數據庫的 Cypher 查詢。",
].join("\n");
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.