博客 / 詳情

返回

LazyLLM教程 | 第19講:高階RAG:基於知識圖譜的RAG


一、前言

"知識圖譜作為結構化知識的代表,正在深刻改變我們獲取和利用信息的方式。”

在本文中,我們將從基礎概念出發,首先解析知識圖譜的本質——它如何以『實體-關係-屬性』的形式組織海量信息,使機器能夠像人類一樣理解世界的關聯性。

接着,我們將探討知識圖譜在搜索引擎中的應用,看看它如何幫助Google實現從『關鍵詞匹配』到『語義理解』讓搜索更智能、更精準。

而在當今大模型時代,知識圖譜與RAG(檢索增強生成)系統的結合,進一步釋放了結構化知識的潛力。我們將深入分析知識圖譜如何優化RAG的檢索過程——它不僅能夠提供更精確的上下文片段,還能通過實體關係增強語義連貫性。

為了更直觀地理解這一技術融合,之後我們將介紹兩個基於知識圖譜的RAG開源實現方案 GraphRAG和LightRAG,剖析它們的設計思路和關鍵技術。

最後,我們將通過實戰和兩個案例分析説明知識圖譜如何顯著提升RAG系統的準確性。


二、背景介紹

RAG(Retrieval Augmented Generation) 是 LLM 問世以來生成式 AI 領域最熱門話題之一,它是結合了 檢索(Retrieval)生成(Generation) 的AI技術,旨在通過外部知識庫增強大語言模型(LLM)的生成能力。其核心思想是:

  • 先檢索:從外部知識庫(如文檔、數據庫)中查找與問題相關的信息片段。
  • 後生成:將檢索到的內容作為上下文,輸入給大語言模型,生成更準確、可靠的回答。

一般RAG(Retrieval-Augmented Generation)系統中,效果優化策略大致可以分為兩個核心方向:檢索階段的優化和生成階段的優化。

而基於知識圖譜的RAG正是優化的檢索環節。

傳統的、也被稱作 樸素RAG(naive RAG) 的流程,通常包括對私有知識庫中的內容進行文本切分、向量化編碼以及索引構建等步驟。隨後,系統根據用户查詢在向量索引中檢索相關內容片段,並將這些被召回的內容作為上下文輸入給大語言模型,用於生成回答。

在樸素RAG中,檢索返回的內容往往是孤立、缺乏結構關聯的文本塊。這種方式在回答在需要跨文檔、跨段落推理的場景中,由於召回片段之間缺乏邏輯和語義連貫性,容易導致生成結果不準確或片面。

此外,RAG還面臨結構缺失的問題,無法識別文檔間的主題關係,以及信息冗餘與漏召的問題,導致重要信息遺漏或內容重複。

這些限制顯著影響了其在複雜問答、邏輯推理和多跳檢索等等高階任務中的表現。

為了解決樸素RAG在檢索召回上碎片化的侷限,基於知識圖譜的RAG(Knowledge Graph-based RAG, 也有稱為Knowledge-enabled RAG / Knowledge Graph Guided RAG)技術應運而生。

Knowledge Graph-based RAG 在知識建模層引入了結構化語義網絡,即知識圖譜,用於明確刻畫實體之間的關係與上下文結構。

知識圖譜通過實體-關係-屬性的三元組形式顯式建模語義關聯,使系統能夠清晰刻畫知識之間的語義路徑。

通過在圖譜上進行結構感知的檢索或路徑遍歷,系統可以更有針對性地召回與用户問題語義相關、邏輯連貫的信息,顯著提升回答的準確性和解釋性。

此外,知識圖譜還支持跨文檔實體對齊與融合,增強主題聚合與複雜推理能力,從而更好地應對多跳問答和高階邏輯推理任務。


三、知識圖譜:讓機器理解世界的“知識網絡”

圖是一種由一組節點(或稱為頂點)和節點之間的邊組成的數據結構,可以用來表示各種關係和結構。

例如圖1 可以表示地鐵路線圖,每個頂點表示一個站點,每個邊表示站點之間的連通性;也可以表示社交網絡中的好友關係,每個頂點表示一個社交軟件用户,每條邊表示二者為好友關係。

img1.png

圖1 圖(Graph)結構

知識圖譜(Knowledge Graph, KG)是一種用 “圖” 結構表示知識的方式,由實體(節點)和關係(邊)組成,能夠將各種信息抽象成結構化的形式,進而支持機器對其進行理解、推理和查詢。

知識圖譜通常由三元組組成,其中每個三元組包含一個實體、關係以及與該實體相關的另一個實體,結構形式為 (實體1)—[關係]→(實體2)

比如,“阿基米德”是一個實體,“發現”是一個關係,“浮力原理”是另一個實體。通過這種形式,知識圖譜能夠把知識表示為一個網絡或圖結構,便於存儲、查詢和推理。

image.png

圖2 知識圖譜示例(圖源網絡)

知識圖譜有三個重要組成部分,即實體,關係和屬性,每個部分的解釋如下:

  • 實體(Entity):知識圖譜的基本構成單位,代表現實世界中的物品、概念、事件等。例如圖2中的每個人物、城市、課程都是實體。
  • 關係(Relationship):描述實體間聯繫的方式,通常用邊來表示。比如圖2中男士與Bonn的關係是“lives in”,表示這位男士生活在Bonn City。
  • 屬性(Attribute):實體或關係的附加信息。例如圖2中Bonn City有“Type”,“Population”和“Area”三個屬性。

知識圖譜可以將零散的信息組織成有結構的圖,可以清晰地展現實體之間的關係。例如,在醫療領域中,可以通過知識圖譜將疾病、症狀、治療方案等信息用圖的形式聯繫起來。

知識圖譜中每個邊連接不同的實體,可以通過這種連接關係實現複雜的多層次、多跳推理

不同於傳統數據庫的表格形式,知識圖譜能更自然地表示覆雜的關聯信息,不僅包含實體之間的關係,還可以標註關係的類型、方向等信息,使得計算機能夠理解不同類型的關係

此外,由於知識圖譜能夠不斷擴展,隨着更多的數據和知識加入,圖譜能夠持續成長,表示更復雜更多樣的信息,適用於海量知識的表示


四、從文檔中構建知識圖譜

image.png

從文檔中構建知識圖譜通常包括以下步驟:

1️⃣首先,通過文檔解析將 PDF、Word、網頁等非結構化文檔轉化為純文本,同時清除格式和亂碼。

2️⃣接着,通過信息抽取技術識別文本中的實體、關係和屬性,生成初步的結構化三元組。

3️⃣隨後,通過知識融合對同一實體進行合併,解決多義詞和衝突信息問題。將處理後的三元組存入圖數據庫中,便於高效查詢與分析。

4️⃣最終,知識圖譜可用於可視化展示、智能問答以及企業決策輔助等實際應用場景。


五、從搜索到問答:知識圖譜如何升級搜索引擎

知識圖譜應用於搜索引擎中,最初見於2012年Google的 Introducing the Knowledge Graph: things, not strings(https://blog.google/products/search/introducing-knowledge-graph-things-not/), 文中提出其對搜索的效果提升主要體現在三個方面:

(一)Find the right thing

理解字符串所代表的實體。

image.png

(二)Get the best summary

圍繞主題彙總相關內容,而非簡單返回其所出現在的原文。

image.png

(三)Go deeper and broader

從深度和廣度上擴大召回。比如:“關聯問題推薦”。

image.png


六、RAG系統的“智能外掛”:知識圖譜如何賦能RAG?

(一)RAG系統如何構建知識圖譜

基於知識圖譜的RAG系統需要首先從現有的結構化數據(如數據庫、RDF、Neo4j 等)和非結構化數據(如文本)構建出知識圖譜。

結構化數據通常已有明確的實體與關係,而非結構化數據(通常是文本文檔)則需要專門提取對應信息,例如重要的實體(如人名、地名、日期等)與它們之間的關係(如“創辦了”或“位於”)。

從非結構化文本中構建知識圖譜時,可以利用自然語言處理(NLP)中的命名實體識別(NER)和關係抽取技術,也可以通過 LLM 進行提取。

目前多數方法直接使用 LLM 提取文檔中的實體與關係再構造對應的知識圖譜,流程如圖所示:

image.png

圖4 利用大模型構建知識圖譜流程

image.png

首先對文本進行分塊,這一步與普通RAG中的分塊方法一致。

在選擇文本塊粒度時,必須在召回率精確率之間找到平衡。微軟 GraphRAG 工作中指出較長的文本塊可能導致召回率下降,影響圖譜索引的質量。這是因為較長的文本塊雖然可以減少LLM調用次數,但由於LLM上下文窗口的限制,可能會導致信息丟失從而降低召回率。

文本分塊後,使用LLM提示從文本塊中提取圖元素實例(節點、邊、協變量)。這一步可以通過 Prompt 提示分為兩個步驟實現:

1️⃣識別文本中出現的所有實體(包括實體名、類型和描述);

2️⃣識別相關實體之間的關係(包括源實體、目標實體和關係描述)。

具體實踐中,可以根據任務所屬的領域進行 Prompt 定製,例如針對科學、醫學、法律等不同專業領域,可以使用對應的示例來提高提取質量。

最後合併不同文檔/分片中已存在的實體和關係。

1. 從內容片段構建知識圖譜示例

原文

起燈燭熒煌,焚起香來。宋江在當中證盟,朝着涌金門下哭奠。戴宗立在側邊。先是僧人搖鈴誦咒,攝召呼名,祝讚張順魂魄,降墜神幡。次後戴宗宣讀祭文。宋江親自把酒澆奠,仰天望東而哭。正哭之間,只聽得橋下兩邊,一聲喊起,南北兩山,一齊鼓響,兩彪軍馬來拿宋江。正是:方施恩念行仁義,翻作勤王小戰場。正是:直誅南國數員將,攪動西湖萬丈波。畢竟宋江、戴宗怎地迎敵,且聽下回分解。此一回內,折了三員將佐:郝思文、徐寧、張順。京師取回一員將佐:安道全\n第一百五十五段  話説浙江錢塘西湖這個去處,果然天生佳麗,水秀山明。正是帝王建都之所,名實相孚,繁華第一。自古道:江浙昔時都會,錢塘自古繁華。卻才説不了宋江和戴宗正在西陵橋上祭奠張順,不期方天定已知,着令差下十員首將,分作兩路來拿宋江,殺出城來。南山五將是吳值、趙毅、晁中、元興、蘇涇;北山路也差五員首將,是温克讓、崔彧、廉明、茅迪、湯逢士。南兵兩路,共十員首將,各引三千人馬,半夜前後開門,兩頭軍兵一齊殺出來。宋江正和戴宗奠酒化紙,只聽得橋下喊聲大舉。左有樊瑞、馬麟,右有石秀,各引五千人埋伏。聽得前路火起,一齊也舉起火來。兩路分開,趕殺南北兩山軍馬。南兵見有準備,急回舊路。兩邊宋兵追趕。温克讓引着四將急回過河去時,不提防保叔塔山背後撞出阮小二、阮小五、孟康,引五千軍殺出來,正截斷了歸路,活捉了茅迪,亂槍戳死湯逢士。南山吳值,也引着四將,迎着宋兵追趕,急退回來,不提防定香橋正撞着李逵、鮑旭、項充、李袞,引五百步隊軍殺出來。那兩個牌手,直搶入懷裏來,手舞蠻牌,飛刀出鞘,早剁倒元興。鮑旭刀砍死蘇涇,李逵斧劈死趙毅。接入中軍帳坐下。宋江對軍師説道:“我如此行計,已得他四將之首,活捉了茅迪,將來解赴張招討軍前,斬首施行。”宋江在寨中,惟不知獨鬆關、德清二處消息。便差戴宗去探,急來回報。戴宗去了數日,回來寨中,參見先鋒,説知:“盧先鋒已過獨鬆關了,早晚便到此間。”宋江聽了,憂喜相半,又問:“兵將如何?”戴宗答道:“我都知那裏廝殺的備細,更有公文在此。先鋒請休煩惱。”宋江道:“莫非又損了我幾個弟兄?你休隱避,可與我實説情由。”戴宗道:“盧先鋒自從去取獨鬆關,那關兩邊都是高山,只中間一條路,山上蓋着關所。關邊有一株大樹,可高數十餘丈,望得諸處皆見。下面盡是叢叢雜雜松樹。關上守把三員賊將,為首的喚做吳升,第二個是蔣印,第三個是衞亨。初時連日下關和林沖廝殺,被林沖蛇矛戳傷蔣印。吳升不敢下關,只在關上守護。次後厲天閏又引四將到關救應,乃是厲天祐、張儉、張韜、姚義四將。次日下關來廝殺。賊兵內厲天祐首先出馬和呂方相持,約鬥五六十合,被呂方一戟刺死厲天祐。賊兵上關去了,並不下來。連日在關下等了數日。盧先鋒為見山嶺險峻,卻差歐鵬、鄧飛、李忠、周通四個上山探路。不提防厲天閏要替兄弟復仇,引賊兵衝下關來,首先一刀,斬了周通。李忠帶傷走了。若是救應得遲時,都是休了的。救得三將

這段原文是徵方臘時的獨鬆關之戰的內容,其中有兩個難點:

1️⃣實體歧義,盧先鋒實際上是盧俊義,但片段原文未體現

2️⃣這段內容出現人物眾多,且無主要人物

LightRAG使用LLM提取實體與關係示例

LightRAG中Chunk, Entity, Relation 的數據格式

@dataclass
class GraphChunkNode:
    chunk_id: str
    content: str
    chunk_order_index: int
    full_doc_id: str 
    tokens: int
@dataclass
class GraphEntityNode:
    entity_name: str
    entity_type: str
    description: str
    source_chunk_ids: list[int] = field(default_factory=list)
@dataclass
class GraphRelationNode:
    src_id: str
    tgt_id: str
    weight: float
    keywords: str
    description: str
    source_chunk_ids: list[int] = field(default_factory=list)

LLM 提取結果

好的,我將補充上一次提取中遺漏的實體和關係。\n\n(\"entity\"<|>\"樊瑞\"<|>\"person\"<|>\"樊瑞在宋江的軍隊中擔任伏擊任務,參與了對南兵的攻擊。\")
...
(\"entity\"<|>\"林沖\"<|>\"person\"<|>\"林沖在宋
江的軍隊中參與了對獨鬆關的戰鬥,與蔣印交戰。\")##\n(\"entity\"<|>\"呂方\"<|>\"person\"<|>\"呂方在宋江的軍隊中參與了對獨鬆關的戰鬥,殺了厲天祐。\")
...
(\"entity\"<|>\"盧先鋒\"<|>\"person\"<|>\"盧先鋒率領軍隊取下了獨鬆關,在獨鬆關與敵人激戰。\")##
...
(\"relationship\"<|>\"盧先鋒\"<|>\"獨鬆關的奪取\"<|>\"盧先鋒領導軍隊成功地取下了獨鬆關,擊敗了吳升等守關將領。\"<|>\"戰鬥,勝利\"<|>10)##
(\"relationship\"<|>\"林沖\"<|>\"獨鬆關的奪取\"<|>\"林沖在對獨鬆關的戰鬥中表現出色,成功地傷了蔣印。<|>\"戰鬥,勝利\"<|>8)
...
(\"content_keywords\"<|>\"戰鬥,伏擊,勝利,防禦,攻擊,敵我交戰,奪取關隘
\")<|COMPLETE|>

可以看到,眾多人物都分別提取出了一條實體/關係內容項。

"entity": "盧先鋒",
"type": "person",
"description": "盧先鋒率領軍隊取下了獨鬆關,在獨鬆關與敵人激戰。",
"organization": "宋江軍",
"skills": ["...", "..."]

image.png

"relationship" 的表示:

"relationship" <|> 實體1 <|> 實體2 <|> 關係描述

上述形式可以抽象出 ("實體A", "關係", "實體B") ,通過該元組即可構建知識圖譜的結構:節點(實體)+ 邊(關係)。

例如:

"relationship"<|>"盧先鋒"<|>"獨鬆關的奪取"<|>"盧先鋒領導軍隊成功地取下了獨鬆關……

通過這行表示即可建立實體“盧先鋒”與實體“獨鬆關的奪取”之間的聯繫。

另外,對 “盧先鋒”的消歧是通過其他片段的提取,完成了關聯。

<node id="&quot;盧先鋒&quot;">
  <data key="d0">"PERSON"</data>
  <data key="d1">"盧先鋒在軍事行動中發揮領導作用,指揮軍隊進攻,並在軍事行動後進行獎勵。"&lt;SEP&gt;"盧先鋒是軍隊中的指揮角色,負責決策和指揮軍事行動。"&lt;SEP&gt;"盧先鋒是前線的軍事指揮團隊,攻破晉寧,並贏得了對
陣孫安的戰鬥。"&lt;SEP&gt;"盧先鋒是宋軍的指揮官,指揮軍隊攻佔昱嶺關並獲得關鍵勝利。"&lt;SEP&gt;"盧先鋒是宋江手下的將領,曾接待孫安,並派遣孫安前往壺關探聽消息。"&lt;SEP&gt;"盧先鋒是宋江手下的將領,被派遣去攻打湖>州。"&lt;SEP&gt;"盧先鋒是晉寧的指揮官,戴宗前往晉寧探聽軍情的對象。"&lt;SEP&gt;"盧先鋒是梁山泊的好漢之一,被賀統軍圍困。"&lt;SEP&gt;"盧先鋒是這場戰役中的重要指揮官,負責制定策略和指揮戰鬥。"&lt;SEP&gt;"盧先鋒率領
軍隊取下了獨鬆關,在獨鬆關與敵人激戰。"</data>
  <data key="d2">chunk-70d484f8faf2a52a92c82011634f06dc&lt;SEP&gt;chunk-52ed314b18da8a382391ebd05c40a624&lt;SEP&gt;chunk-e054d9d89262c9af698db2a0121d5acc&lt;SEP&gt;chunk-09a8db8ba347c7b7bc0176d4525c0cf2&lt;SEP&gt;chunk-f7436d476ba11775cbffaaaec7258ed9&lt;SEP&gt;chunk-fe76f7b689672053ec6306a05f0cdd0b&lt;SEP&gt;chunk-0626d86fb637457aed588a314f92803a&lt;SEP&gt;chunk-5dccdbae178f7538ee5e466f932be0dc&lt;SEP&gt;chunk-71fb5004c46bc9c5d7428068a57a02e0&lt;SEP&gt;chunk-75d67bf231332d5355799af8db0ae8e3</data>
</node>

<!-- "副先鋒"是個孤立實體,最終知識圖譜中無任何關聯實體 -->
<node id="&quot;副先鋒&quot;">
  <data key="d0">"ROLE"</data>
  <data key="d1">"副先鋒是指盧俊義的職務,他擔任的職務僅次於宋先鋒,負責同樣重要的軍事任務。"&lt;SEP&gt;"盧俊義擔任的職位,負責與宋江一起執行軍事行動。"</data>
  <data key="d2">chunk-8be99d60dd263a48610af8a66041938b&lt;SEP&gt;chunk-06d94d584d8549b7f59105730610e475</data>
</node>

<node id="&quot;盧俊義&quot;">
  <data key="d0">"PERSON"</data>
  <data key="d1">盧俊義,綽號玉麒麟,是大名府的長者和富豪,以高超的武藝尤為擅長棍棒技巧而聞名。在梁山泊中,盧俊義與宋江並列成為重要將領,經常擔任先鋒的角色。他曾獨自抵抗四個番將並斬殺耶律宗霖,也參與多次軍事行動>,如攻打薊州和檀州。被任命為副先鋒,盧俊義不僅負責領導軍隊攻擊多個城市,包括宣州、湖州等地,同時也負責執行宋江下達的各項軍事任務。在平定各城市的過程中,盧俊義展現出了英勇的戰鬥能力及傑出的統帥才能。作為宋江的重要
副手,盧俊義不僅指揮中軍,還與其他好漢一起商討戰略,且親自領兵發起攻擊以破壞敵軍。不僅參與軍事行動,盧俊義還協助宋江處理招安事宜,並在對抗遼國的侵擾中扮演關鍵角色。此外,他親自帶領軍隊突破昱嶺關,進攻歙州等重要行
動,展現了其軍事才能和領導才能。在戰鬥中,盧俊義雖然遭遇了多次挫折,如曾經歷多次軍事行動的挫敗,但仍作為梁山泊重要的軍事指揮官,與宋江一同指揮軍中事務,最終為梁山泊的歸順朝廷作出了重要貢獻,成為梁山泊的關鍵領導人
之一。然而,在故事的最終章,盧俊義因墜入淮河而死去,這一事件進一步深化了故事的發展。盧俊義不僅被皇帝冊封,曾擔任兵馬副總管、平南副先鋒等職務,也積極參與出征和保境安民的行動,展示了他的非凡領導才能和軍事能力。</data>
<edge source="&quot;天子&quot;" target="&quot;盧俊義&quot;">
  <data key="d3">9.0</data>
  <data key="d4">"天子任命盧俊義為副先鋒。"</data>
  <data key="d5">"任命,信任"</data>
  <data key="d6">chunk-06d94d584d8549b7f59105730610e475</data>
</edge>

<edge source="&quot;盧俊義&quot;" target="&quot;盧先鋒&quot;">
  <data key="d3">8.0</data>
  <data key="d4">"盧俊義作為盧先鋒,統領軍隊進攻玉田縣。"</data>
  <data key="d5">"軍隊指揮, 戰略推進"</data>
  <data key="d6">chunk-a7949e3833d3220bc3a90adf93e855f8</data>
</edge>

(二)RAG如何使用知識圖譜召回

知識圖譜檢索的目標是根據用户的查詢,返回相關的實體、關係或路徑,以提供精準的答案。

用户查詢往往是文本形式,因此要在知識圖譜中獲取相應的知識,需要先對用户查詢進行解析並提取相關實體和關係

例如,用户提問“牛頓發現了什麼?”,這是應該解析出實體“牛頓”和對應的關係“發現”,然後在知識圖譜中查找與“牛頓”相關的關係。

具體來説,查詢會轉化為一組圖的路徑或子圖的匹配,系統通過搜索與查詢中實體和關係相匹配的節點和邊,獲取相關的子圖。

除了簡單的子圖匹配外,在一些複雜的查詢中,可能還需要進行路徑搜索,即從一個實體出發,沿着關係邊遍歷圖,找到與查詢相關的其他實體。

例如,如果查詢是“愛因斯坦和牛頓的關係是什麼?”,系統可能需要從“愛因斯坦”和“牛頓”出發,沿着圖中的關係(如“影響”、“繼承”)找到它們之間的聯繫。

其流程如圖3 所示:

image.png

圖3 Knowledge Graph Based RAG 流程示例

基於知識圖譜的RAG系統回答過程包括以下步驟:

1️⃣首先,在檢索階段,根據用户輸入問題從知識圖譜中召回相關的實體、關係以及原文內容;

2️⃣接着,在增強階段,將召回的結構化結果整合為額外的上下文信息,與用户輸入一起提供給大模型,以增強其理解和推理能力;

3️⃣最後,在生成階段,系統通過大模型生成答案,該過程與樸素RAG系統一致。


七、知識圖譜增強型RAG的優勢

image.png

增強可解釋性和推理能力

  • 知識圖譜是結構化的,天然適合組織分散的非結構知識,使檢索更具可控性與解釋性。

高可擴展性,低成本更新

  • 圖譜動態更新,不依賴重新訓練語言模型

動態知識融合

  • 知識可視為大型語言模型 可訪問的動態數據庫,用於查詢最新相關信息。這些知識與 LLM 的集成是通過高級架構實現的,從而促進了文本標記與知識圖譜實體之間的深度交互。

八、開源實現:兩大知識圖譜RAG系統解析GraphRAG與LightRAG

Knowledge Graph Base RAG 實現方式上,目前有兩款知名的開源方案 :GraphRAG 與 LightRAG

(一)GraphRAG

image.png

GraphRAG通過構建知識圖譜分層社區結構,將局部信息聚合為全局理解。其核心優勢在於:

  • 利用LLM從文檔中提取實體、關係和事實聲明構建知識圖譜,通過圖結構索引捕捉語義關聯;
  • 通過分層社區檢測,使用圖社區算法將圖譜劃分為緊密關聯的社區,遞歸生成從局部到全局的摘要;
  • 採用Map-Reduce式回答生成方法,藉助社區摘要的並行處理與聚合,生成全面且多樣的全局回答。

1. GraphRAG工作流程

image.png

GraphRAG通過以下流程實現從文檔到全局回答的生成:

首先,將文檔拆分為文本塊,並使用LLM提取其中的實體、關係及事實聲明;

接着,基於提取結果構建知識圖譜,將實體和關係轉化為圖節點和邊,對重複實例進行聚合並生成節點描述,同時根據關係出現頻率設定邊的權重。

然後,利用Leiden算法對圖譜進行遞歸劃分,形成嵌套的社區結構,每個社區代表一個語義主題,構建從局部到全局的層級結構。

社區摘要生成階段中,葉社區按節點重要性排序,聚合其中的實體、關係和聲明生成摘要,而高層社區則通過遞歸整合子社區摘要,平衡細節與全局視角。

在查詢處理與回答生成階段,系統根據用户問題對每個社區摘要獨立生成部分回答,並通過評分過濾無關內容,最終聚合高分回答生成全面且準確的全局回答

2. 社區及社區摘要

微軟 GraphRAG 在知識圖譜的基礎上增加了一個 社區(Community) 的概念。

image.png

社區是 GraphRAG 中的高級結構,表示一組相關實體的集合。每個社區包含以下字段:

ID:社區的唯一標識符。
Level:社區的層級(如 0、1、2 等)。
Entity IDs:社區中包含的實體 ID 列表。
Relation IDs:社區中包含的關係 ID 列表。
Text Block IDs:社區中包含的文本塊 ID 列表。
Description:社區的描述信息。
Summary:社區的摘要信息。

GraphRAG 是在構建Knowledge Graph後,增加了一個 社區摘要(Community Summaries) 的生成環節。這一策略使得系統在檢索時只關注與查詢高度相關的社區,而不是檢索整個圖。

社區摘要對數據全局結構和語義的高度概括, 即使沒有問題, 用户也可以通過瀏覽不同層次的社區摘要來理解語料庫。

社區摘要生成由兩步組成:

(1)社區檢測:使用圖分析算法,獲得具有高度連接性的實體簇

(2)摘要提取:使用LLM根據社區中的實體和關係,提取各類型總結。

image.png

圖5 微軟 Graph RAG 的社區聚類效果圖

圖5 展示了微軟 Graph RAG 在社區聚類後生成的可視化圖示例,不同顏色代表不同的社區,每個社區都有對應的摘要。

3. 三種檢索方法

在這種知識圖譜中進行檢索時,除了直接匹配新型實體和關係,或利用推理獲取信息外,還需要考慮知識的層次結構

微軟 Graph RAG 針對其層次結構提出如下三種檢索方法

(1)全局搜索(Global Search)

image.png

採用Map-Reduce 方式,在所有由 AI 生成的社區摘要中進行搜索並生成答案。

這種方法消耗較多資源,但對於需要整體理解數據集的問題(例如:“該筆記中提到的草藥最重要的價值是什麼?”),通常能夠提供更優質的回答

在給定用户查詢的情況下,全局搜索方法利用來自指定層級的社區層次的LLM生成社區摘要作為上下文數據,以 map-reduce 方式生成響應。

  • 在 map 步驟中,社區摘要被分割成預定義大小的文本片段,每個文本片段用於生成一個包含要點列表的中間響應,每個要點附帶一個數值評分,表示其重要性。
  • 在 reduce 步驟中,從中間響應中篩選出最重要的要點並進行聚合,以此作為上下文來生成最終的用户響應。

全局搜索的響應質量受到所選社區層級的影響,較低的社區層級提供更詳細的報告,通常能生成更深入的響應,但由於報告數量較大,也可能增加最終響應的生成時間和LLM計算資源的消耗。

Global Search更側重於回答全文摘要總結類的場景,比如:“請幫我總結一下這篇文章講了什麼內容?”。

(2)局部搜索(Local Search)

image-2.png

通過結合知識圖譜與原始文檔的文本片段來生成答案,特別適用於需要理解文檔中特定實體的問題,例如:“洋甘菊有哪些治療功效?”。

在給定用户查詢的情況下,局部搜索方法會從知識圖譜中識別出與用户輸入在語義上相關的一組實體。這些實體作為訪問知識圖譜的入口,幫助系統提取更多相關細節,如連接的實體、關係、實體協變量以及社區摘要。

此外,該方法還會從原始輸入文檔中提取與識別出實體相關的文本片段。然後對這些候選數據源進行優先排序和篩選,以適應一個預定義大小的單一上下文窗口,用於生成對用户查詢的響應。

局部搜索適用於需要理解文檔中提及的具體實體的特定問題,例如:洋甘菊具有哪些療效?

(3)DRIFT Search(Dynamic Reasoning and Inference with Flexible Traversal)

一種結合Global Search和Local Search的方案。其搜索過程分為三步:

1️⃣社區報告檢索:DRIFT 將用户的查詢與語義最相關的前K個社區報告進行比較,生成廣泛的初步答案和後續問題以引導進一步的探索。

2️⃣相關數據提取:DRIFT使用本地搜索來優化查詢,生成額外的中間答案和後續問題以增強特異性,從而引導搜索引擎獲取更豐富的上下文信息。

3️⃣相關性排序:最後則是根據相關性對相關節點進行排序,並將排序後的文檔作為上下文信息輸入到LLM,生成最終的響應。

(二)Light RAG

LightRAG的知識圖譜是比較基礎的 分片+實體/關係。

image.png

微軟 GraphRAG 在處理全局或高層級概念查詢方面表現優越,具備較強的可擴展性,並通過圖索引提供更好的上下文理解和可解釋性。

然而,其運行速度較慢,依賴大量 LLM API 調用,可能觸及速率限制且成本高昂。例如,使用 GPT-4o 索引一本 32K 單詞的書可能需花費 6-7 美元。

此外,添加新數據需要重建整個知識圖,效率低下,並且缺乏去重機制,可能導致索引冗餘和噪聲增加。

對於Legal Dataset,共94篇文章,約5000K Token(約5個水滸傳大小)。GraphRAG 生成了 1399 個communities,平均每個社區報告生成需要5000Token, 其中 610 個 level-2 communities, 平均每個level-2的社區佔用1000Token。

下表對比了創建知識圖譜和一次檢索所消耗的LLM Token。C\_Max為API單次請求最多Token限制,T\_extract 代表提取實體和關係消耗Token數,C_extract則表示提取產生的API調用次數。

注:GraphRAG 從0.4.0開始支持增量索引。

LightRAG 是一種輕量級的知識圖譜RAG,它沒有使用層次化的社區聚類,而直接構造了知識圖譜,從而支持了增量更新,無需每添加一部分數據都需要進行實體和關係的提取。

1. 雙層檢索策略

由於使用了無關層次的圖譜結構,在檢索方面也採用了新的方法。LightRAG 採用了雙層檢索策略,包括低層級檢索(集中關注特定實體的信息)和高層級檢索(處理更廣泛的主題),這種設計能夠在不進行社區層次聚類的基礎上有效地檢索相關實體及其關係,從而提高檢索的全面性和響應的上下文相關性。

image.png

圖 6 LightRAG 整體結構(圖源 LightRAG 論文)

具體來説 LightRAG 對文檔進行實體和關係抽取,在這一步完成後進行去重操作,然後進行圖索引,即構建知識圖譜

構建好的知識圖譜如圖6 中間位置所示,每個節點有(Entity Name,Entity Type,Description,Original Chunk ID)幾個屬性,而每條關係都有(Source,Target,Keyword,Description,Original Chunks ID)幾個屬性。

1️⃣當系統接收到用户消息時,首先通過 LLM 提取具體(Specific)和抽象(Abstract)兩個層次的關鍵詞,其中具體關鍵詞關注實體的具體信息,即實體名稱、實體類型等,而抽象關鍵詞則更關注問題所涉及的主題或領域等高層級信息。

2️⃣提取了這些關鍵詞之後對前一步構造的知識圖譜進行檢索,具體來説使用具體關鍵詞進行低層級檢索獲得相關實體和概念,使用抽象關鍵詞進行高層級檢索獲得宏觀主題或趨勢等信息。

3️⃣最終 LLM 接收相關實體、關係以及原文文檔作為上下文,對用户問題給出答案。此後新數據加入時,僅需更新新增的圖節點和邊,無需重建整個索引,大幅降低計算開銷。

LightRAG 引入的高層級檢索和低層級檢索關注不同層次內容:

(1)低層級檢索(Low Level Retrieval):

專注於提取特定實體及其關聯的屬性,旨在提供關於圖中特定節點或邊的精確信息。當系統發起查詢時,低層級檢索方法會基於圖數據庫中的節點和邊,生成包含詳細屬性和關係的上下文數據,以輔助響應生成。

例如圖6 中提取的低層級關鍵詞包括“beekeeper”,“hive”等非常精確具體的詞語,通過這些關鍵詞對圖進行檢索時對應的實體名稱或實體的屬性信息,為 LLM 提供細節信息。

(2)高層級檢索(High Leval Retrieval):

面向宏觀主題與整體趨勢,側重於跨多個關聯實體和關係進行信息聚合,以提供綜合性見解。在檢索階段,系統會基於用户查詢,從圖數據庫中篩選與主題相關的實體集合,並聚合它們之間的多維度關係,生成上下文數據作為響應依據。

圖6 中提取的高層級關鍵詞有 “agriculture”,“production”等非常抽象且高級的概念,這些內容需要通過結合實體與關係進行檢索甚至相關推理才能給出完整的答案,與低層級檢索相比,高層次檢索可以為 LLM 提供更高層次的主題內容

2. High Level Retrieval Example

  • Step1: 林沖的主要事蹟有哪些?
hl_keywords: 0 ='林沖', 1 ='主要事蹟', 2 ='人物形象'
  • Step2: 向量庫召回相關Relation (top-k :60)

image.png

  • Step3: 由候選Relation擴展到候選Entity

依Relation的召回順序取出src\_id和tgt\_id對應的實體並去重,並根據設置的max_token進行截斷。

image-2.png

  • Step4: 候選Relation擴展到候選Chunk

依Relation的召回順序取出chunk\_id對應的chunk並去重,並根據設置的max\_token進行截斷。

  • Step5: 將召加的 Enitity(name, type, description), Relation(src\_name, target\_name, keywords, description), Chunk (content) 拼接為context_str,作為RAG的召回知識
  • Step6: LLM 生成回覆
  1. 身份轉變:林沖原是東京八十萬禁軍的槍棒教頭,因得罪高俅被陷害而被流放至滄州。在流放途中,受魯智深的幫助,最終投奔了柴進,並後來加入了梁山泊。

  2. 加入梁山泊:林沖因遭受高俅的迫害而被迫逃離京城,尋求庇護於柴進處。之後,他前往梁山泊併成為梁山的重要頭領之一。他還協助推翻了原來的首領王倫,立晁蓋為新的領導人。

  3. 軍事行動:
     - 參與攻打祝家莊的軍事行動,作為第二撥的軍事行動領導。
     - 在高唐州之戰中直接與敵方交戰。
     - 執行宋江命令,在宛州前線作戰,是攻取宛州的關鍵人物之一。
     - 參與迎戰遼國援軍的戰鬥。
     - 在獨鬆關的戰鬥中表現出色,成功地傷了蔣印。

  4. 個人英勇表現:
     - 林沖多次在重要戰役中展現了他的英勇和忠誠,與多位將領如關勝、王煥以及賀統軍等對決,並取得了顯著的戰績。
     - 他參與了多次軍事行動,包括尋找盧俊義、護送宿太尉進遼國頒詔和計劃參與對遼國軍隊的攻擊等。

  5. 患病與療養:林沖後來染患風病癱瘓,留在六和寺中休養。

  6. 忠誠與影響力:儘管在面對招安時內心有所質疑,但林沖對梁山泊的貢獻和在英雄中的地位是無可替代的。他的英勇和忠誠在《水滸傳》中留下了一段傳奇。

  這些事蹟展示了林沖從一名禁軍教頭到成為梁山重要頭領的過程,以及他在多次戰鬥中的英勇表現和重要作用。

3. 總結

LightRAG 相比 GraphRAG 在提升查詢效率降低計算成本方面具有顯著優勢。它通過直接構建知識圖譜和支持增量更新,解決了 GraphRAG 在數據更新和處理速度上的不足。

同時,雙層檢索策略使得 LightRAG 在不依賴複雜社區層次聚類的情況下,能夠高效且全面地檢索相關實體和關係,進一步提高了結果的上下文相關性和精確度

這些優勢使得 LightRAG 成為一種更加高效、靈活且成本可控的解決方案。

不足:

1️⃣創建知識圖譜對提取效果要求較高,實際體驗線上模型好於單機開源模型,因此有一定成本

注:本節示例創建知識庫使用的Qwen2.5-32B,生成時使用的線上模型 Qwen2.5-Max。

2️⃣LightRAG/GraphRAG 中Chunk 以List形式合併入實體和關係。一般關係(邊)的合併情況不多,但對於類似小説角色這樣的實體(點),其出現頻率高,相關chunk非常多,當以實體為中心(low level retrieval)召回實體,繼而召回相關原文片段時,無法對實體內的Chunk進行細分排序

比如“林沖”這個實體下有140+個 chunk,其無法在實體召回後排序內部chunk相關度,結果就是根據MAX_TOKEN配置被截斷的chunk其實上相關度不高。


九、選型決策樹:你的問題需要哪種RAG?

向量檢索和圖檢索各自具有獨特的優勢,適用於不同類型的任務。

樸素RAG通過向量檢索的方式,能夠快速、有效地匹配相關文本片段,特別適用於處理大量非結構化文本數據時,能夠迅速從大規模數據中提取關鍵信息。

然而,面對涉及複雜實體關係和多層次上下文理解的任務時,圖檢索則展現了其獨特的優勢。GraphRAG通過構建知識圖譜,能夠在查詢過程中深入理解實體之間的關係,提供更為豐富和準確的上下文支持,確保查詢結果的完整性和關聯性。

因此,目前也有基於混合檢索的 RAG 系統,結合了向量檢索和圖檢索的優點,使得兩者在任務中的互補性得以充分發揮。結合這兩種技術,可以在處理簡單查詢時藉助向量檢索的高效性,而在複雜的多層次查詢中依賴圖檢索提供深度的語義理解。

根據具體任務的需求選擇合適的檢索方法,可以顯著提升問答系統的整體性能,確保生成結果的高質量與準確性。


十、實戰與案例分析:知識圖譜RAG對複雜query的提升

注:NaiveRAG的實戰在第2-7講中已做過介紹,在此不額外贅述

(一)LightRAG實戰

1. 配置

  • Dataset:

水滸傳_utf8.txt (約912K Tokens)

  • 切分及召回

NaiveRAG Chunk 1200 Token; top_k: 10

LightRAG Chunk 1200 Token ; relation/entity top_k: 60

1. 使用LightRAG構建知識圖譜

Step 0: 環境準備

vLLM

pip install vllm

LightRAG

pip install lightrag-hku

infinity

pip install infinity-emb

LazyLLM

pip3 install lazyllm
  • Git clone https://github.com/HKUDS/LightRAG並安裝lightrag pip install "lightrag-hku[api]"
  • 下載語料 水滸傳.txt 轉為utf8,並重命名放入比如 "novel/shuihu.txt"
  • 主動創建保存知識圖譜的目錄

Step 1: 使用VLLM/Ollama等工具部署openai 兼容格式的LLM服務

注:由於文章較長,嘗試過多個平台的在線模型,即使換不同的語料,依然會有觸發“內容安全限制”的情況。所以只能選擇本地部署的LLM。

下載模型(Qwen 2.5-32B-Instruct),推薦從ModelScope下載。

python -m vllm.entrypoints.openai.api_server --model /mnt/lustre/share_data/lazyllm/models/Qwen2.5-32B-Instruct/ --served-model-name qwen2 --max_model_len 16144 --host 0.0.0.0 --port 12345

image.png

Step 1-2: 部署embedding 服務或使用已有平台的openai兼容格式的embedding在線服務

下載模型(bge-large-zh-v1.5),推薦從ModelScope下載。

infinity_emb v2 --model-id "/mnt/lustre/share_data/lazyllm/models/bge-large-zh-v1.5" --port 19001 --served-model-name bge-large

測試一下服務

注:vLLM啓動的服務對應的openai格式的base_url為 : http://{ip}:{port}, 若用curl測試則url為 http://{ip}:{port}/embeddings
curl --location 'http://127.0.0.1:19001/embeddings' --header "Authorization: Bearer TEST" \

--header 'Content-Type: application/json' --data '{

"model": "bge-large",

"input": "基於知識圖譜的RAG系統",

"dimension": "1024",

"encoding_format": "float"

}'

Step 2: 參照 examples/lightrag\_openai\_compatible_demo.py 修改LLM服務和embedding服務的配置

配置llm\_model\_func和embedding\_func 的三個參數:model, base\_url, api_key。

1️⃣設置處理的文件名和保存知識圖譜的文件夾路徑

  • WORKING_DIR 為 知識圖譜保存的文件夾路徑
  • PATH\_TO\_TXT 為 待解析的小説 <水滸傳>,注意檢查是否為utf8格式

2️⃣修改llm_model的配置

  • base_url 改為 http://{ip}:{port}/v1
  • model_name 改為 vLLM中配置的 qwen2

3️⃣修改openai_embed的配置

  • base_url 改為 http://{ip}:{port}
  • model_name 改為 infinity中配置的 bge-large

(代碼GitHub鏈接:https://github.com/LazyAGI/Tutorial/blob/main/rag/codes/Chapter19/lightrag\_data\_to_kg.py)

Step 3:調整examples/lightrag\_openai\_compatible_demo.py 中的main 函數。

設置知識圖譜的存儲目錄WORKING\_DIR 和文本文件的路徑 PATH\_TO_TXT。

async def main():
    WORKING_DIR = "****"
    PATH_TO_TXT = "shuihu.txt"
    try:
        embedding_dimension = await get_embedding_dim()
        print(f"Detected embedding dimension: {embedding_dimension}")

        rag = LightRAG(
            working_dir=WORKING_DIR,
            llm_model_func=llm_model_func,
            embedding_func=EmbeddingFunc(
                embedding_dim=embedding_dimension,
                max_token_size=8192,
                func=embedding_func,
            ),
        )
        with open(PATH_TO_TXT, "r", encoding="utf-8") as f:
            await rag.ainsert(f.read())
    except Exception as e:
        print(f"An error occurred: {e}")

Step 4: 運行Demo,並檢查WORKING\_DIR中是否有graph\_chunk\_entity\_relation.graphml 生成,其大小約為5M左右表示成功。

2. LightRAG基於知識圖譜生成回答

(代碼GitHub鏈接:https://github.com/LazyAGI/Tutorial/blob/main/rag/codes/Chapter19/lightrag\_kg\_qa_demo.py)

async def initialize_rag():
    rag = LightRAG(
        working_dir=WORKING_DIR,
        embedding_func=EmbeddingFunc(
                        embedding_dim=1024,
                        max_token_size=8192,
                        func=lambda texts: openai_embed(
                            texts=texts,model="bge-large",
                            base_url="http://127.0.0.1:19001", 
                            api_key=api_key,)
                    ),
        llm_model_func=openai_complete,
        llm_model_name="qwen2",
        llm_model_kwargs={"base_url":"http://0.0.0.0:12345/v1", 
                                         "api_key":api_key},
    )
    await rag.initialize_storages()
    await initialize_pipeline_status()
    return rag
async def main():
    try:
        rag = await initialize_rag()
        with open(PATH_TO_TXT, "r", encoding="utf-8") as f:
            content = f.read()
        await rag.ainsert(content)
        mode = "hybrid"
        print(await rag.aquery("魯智深打的是誰?", param=QueryParam(mode=mode)))
    except Exception as e:
        print(f"發生錯誤: {e}")
    finally:
        if rag:
            await rag.finalize_storages()

image.png

(二)案例分析

1. Case1 實體查詢類query

背景

魯達(名)魯提轄(官職),魯智深(在五台山出家後的法名)。 在前六章,僅有"魯達"和"魯提轄",而隨着故事推進 "魯智深"出現越來越多,到最後80章全部以"魯智深"出現。

Case1-1

Case 1-2

表現近似Case1

Case 1-3

2. Case 2 摘要總結類的query

背景:

林沖的故事情節涉及章節較多:其加入梁山前的章節是中心人物集中出現,而後零散提及。

Case 2-1

Case 2-2

CASE 2-3


十一、LazyLLM融合LightRAG

整體流程圖:

設置LightRAG知識圖譜的存儲目錄working\_dir和文本文件的路徑txt\_path,並配置LazyLLM知識庫目錄dataset_path

(代碼GitHub鏈接:https://github.com/LazyAGI/Tutorial/tree/main/rag/codes/chapter19/lightrag\_lazyllm\_demo.py)

import os
import asyncio
import lazyllm
from lazyllm import pipeline, parallel, bind, Retriever
from lightrag import LightRAG, QueryParam
from lightrag.kg.shared_storage import initialize_pipeline_status
from lightrag.utils import setup_logger, EmbeddingFunc
from lightrag.llm.openai import openai_complete, openai_embed

class LightRAGRetriever:
    def __init__(self, working_dir, txt_path, mode="hybrid"):
        self.working_dir = working_dir
        self.txt_path = txt_path
        self.mode = mode
        self.api_key = "empty"
        self.rag = None 
        self.loop = asyncio.new_event_loop()
        setup_logger("lightrag", level="INFO")

        if not os.path.exists(working_dir):
            os.makedirs(working_dir)

        self.loop.run_until_complete(self.initialize_rag())

    async def initialize_rag(self):
        self.rag = LightRAG(
            working_dir=self.working_dir,
            embedding_func=EmbeddingFunc(
                embedding_dim=1024,
                max_token_size=8192,
                func=lambda texts: openai_embed(
                    texts=texts,
                    model="bge-large",
                    base_url="http://0.0.0.0:19001",
                    api_key=self.api_key,
                )
            ),
            llm_model_func=openai_complete,
            llm_model_name="qwen2",
            llm_model_kwargs={"base_url": "http://0.0.0.0:12345/v1", "api_key": self.api_key},
        )
        await self.rag.initialize_storages()
        await initialize_pipeline_status()

        with open(self.txt_path, "r", encoding="utf-8") as f:
            content = f.read()
        await self.rag.ainsert(content)

    def __call__(self, query):
        return self.loop.run_until_complete(
            self.rag.aquery(
                query,
                param=QueryParam(mode=self.mode, top_k=3, only_need_context=True)
            )
        )

    def close(self):
        if self.rag:
            try:
                self.loop.run_until_complete(self.rag.finalize_storages())
            except Exception as e:
                print(f"關閉LightRAG時出錯: {e}")
            finally:
                self.loop.close()
                self.rag = None

def main():
    kg_retriever = None
    try:
        documents = lazyllm.Document(dataset_path="****")
        prompt = ('請你參考所給的信息給出問題的答案。')

        print("正在初始化知識圖譜檢索器...")
        kg_retriever = LightRAGRetriever(
            working_dir="****",
            txt_path="****"
        )
        print("知識圖譜檢索器初始化完成!")

        bm25_retriever = lazyllm.Retriever(
            doc=documents,
            group_name="CoarseChunk",
            similarity="bm25_chinese",
            topk=3
        )

        def bm25_pipeline(query):
            nodes = bm25_retriever(query)
            return "".join([node.get_content() for node in nodes])

        def context_combiner(*args):
            bm25_result = args[0]
            kg_result = args[1]   
            return (
                f"知識圖譜召回結果:\n{kg_result}"
                f"BM25召回結果:\n{bm25_result}\n\n"
            )

        with lazyllm.pipeline() as ppl:
            with parallel() as ppl.multi_retrieval:
                ppl.multi_retrieval.bm25 = bm25_pipeline
                ppl.multi_retrieval.kg = kg_retriever
            ppl.context_combiner = context_combiner
            ppl.formatter = (lambda nodes, query: dict(context_str=nodes, query=query)) | bind(query=ppl.input)
            ppl.llm = (
                lazyllm.OnlineChatModule().prompt(
                    lazyllm.ChatPrompter(
                        instruction=prompt,
                        extra_keys=['context_str']
                    )
                )
            )

        lazyllm.WebModule(ppl, port=23466).start().wait()

    except Exception as e:
        print(f"\n處理過程中發生錯誤: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    main()

樸素RAG召回文本:

又只一拳,太陽穴上正着,卻似做了一個全堂水陸的道場,磬兒、鈸兒、鐃兒,一齊響。魯達看時,只見鄭屠挺在地下,口裏只有出的氣,沒有入的氣,動彈不得。魯提轄假意道:    “你這廝詐死,灑家再打。”只見麪皮漸漸的變了。    魯達尋思道:“俺只指望痛打這廝一頓,不想三拳真個打死了他,。灑家須吃官司,又沒人送飯,不如及早撒開。”拔步便走,回頭指着鄭屠屍道:“你詐死,灑家和你慢慢理會。”    一頭罵,一頭大踏步去了。街坊鄰舍並鄭屠的火家,誰敢向前來攔他。魯提轄回到下處,急急捲了一些衣服盤纏、細軟銀兩,但是舊衣粗重都棄了。提了一根齊眉短棒,奔出南門,一道煙走了。    且説鄭屠家中眾人,救了半日不活,嗚呼死了。老小鄰人徑來州衙告狀。正直府尹升廳,接了狀子,看罷道:“魯達系是經略府的提轄,不敢擅自徑來捕捉凶身。”府尹隨即上轎,來到經略府前,下了轎子。把門軍士入去報知,府中聽得,教請到廳上,與府尹施禮罷。經略問道:“何來?”府尹稟道:“好教相公得知。府中提轄魯達,無故用拳打死市上鄭屠。不曾稟過相公,不敢擅自捉拿凶身。”經略聽説,吃了一驚,尋思道:“這魯達雖好武藝,只是性格粗鹵,今番做出人命事,俺如何護得短?須教他推問使得。”經略回府尹道:“魯達這人,原是我父親老經略處的軍官,為因俺這裏無人幫護,撥他來做個提轄。既然犯了人命罪過,你可拿他依法度取問。如若供招明白,擬罪已定,也須教我父親知道,方可斷決。怕日後父親處邊上要這個人時,卻不好看。”府尹稟道:“下官問了情由,合行申稟老經略相公知道,方可斷遣。”府尹辭了經略相公,出到府前,上了轎,回到州衙裏,措廳坐下。便喚當日緝捕使臣押下文書,捉拿犯人魯達。    當時王觀察領了公文,將帶二十來個做公的人,徑到魯提轄下處。但見:    扶肩搭背,交頸並頭。紛紛不辨賢愚,擾擾難分貴賤。張三蠢胖,不識字只把頭搖;李四矮矬,看別人也將腳踏。白頭老叟,盡將枴棒拄髭鬚;綠鬢書生,卻把文房抄款目。行行總是蕭何法,句句俱依律令行。    魯達看見眾人看榜,捱滿在十字路口,也站在人叢裏。聽時,魯達卻不識字,只聽得眾人讀道:“代州雁門縣依奉太原府指揮使司,該準渭州文字,捕捉打死鄭屠犯人魯達,即系經略府提轄。如有人停藏在家宿食,與犯人同罪;若有人捕獲前來,或告到官,支給賞錢一千貫文。”魯提轄正聽到那裏,只聽得背後有人大叫道:“張大哥。你如何到這裏?”攔腰抱住,扯離了十字路口。    不是這個人看見了,橫倒拖拽將去,有分教:魯提轄剃除頭髮,削去髭鬚,倒換過殺人姓名,薅惱殺諸佛羅漢。直教:    禪杖打開危險路,戒刀殺盡不平人。畢竟扯住魯提轄的是甚人,且聽下回分解。若非雨病雲愁,定是懷憂積恨。    那婦人拭着眼淚,向前來深深的道了三個萬福。那老兒也都相見了。魯達問道:“你兩個是那裏人家?為甚啼哭?”那婦人便道:“官人不知,容奴告稟:奴家是東京人氏。因同父母來這渭州,投奔親眷,不想搬移南京去了。母親在客店裏染病身故,子父二人,流落在此生受。此間有個財主,叫做鎮關西鄭大官人,因見奴家,便使強媒硬保,要奴作妾。誰想寫了三千貫文書,虛錢實契,要了奴家的身體,未及三個月,他家大娘子好生利害,將奴趕打出來,不容完聚,着落店主人家追要原典身錢三千貫。父親懦弱,和他爭執不得,他又有錢有勢。當初不曾得他一文,如今那討些錢還他?沒計奈何,父親自小教得奴家些小曲兒,來到這裏酒樓上趕座。每日但得些錢來,將大半還他,留些少子父們盤纏。這兩日酒客稀少,違了他錢限,怕他來討時,受他羞恥。子父們想起這些苦楚來,無處告訴,因此啼哭。不想誤觸犯了官人,望乞恕罪,高抬貴手。”    魯提轄又問道:“你姓甚麼,在那個客店裏歇?那個鎮關西鄭大官人在那裏住?”老兒答道:“老漢姓金,排行第二。孩兒小字翠蓮。鄭大官人便是此間狀元橋下賣肉的鄭屠,綽號鎮關西。老漢父子兩個,只在前面東門裏魯家客店安下。”魯達聽了道:“呸!俺只道那個鄭大官人,卻原來是殺豬的鄭屠。    這個醃籌潑才,投托着俺小種經略相公門下做個肉鋪户,卻原來這等欺負人!”回頭看李忠、史進道:“你兩個且在這裏,等灑家去打死了那廝便來。”史進、李忠抱住勸道:“哥哥息怒,明日卻理會。”兩個三回五次勸得他住。    魯達又道:“老兒,你來!灑家與你些盤纏,時日便回東京去如何?”父子兩個告道:“若是能夠回鄉去時,便是重生父母,再長爺孃。只是店主人家如何肯放?鄭大官人須着落他要錢。”魯提轄道:“這個不妨事,俺自有道理。

LightRAG召回內容(Entities部分):

-----Entities(KG)-----```json[{"id": "1", "entity": "金翠蓮", "type": "person", "description": "金翠蓮是金老的女兒,受到鄭屠的不公平對待。<SEP>金翠蓮是魯達提到的一個女性,據推測她可能是被鄭屠欺騙或傷害的對象。", "rank": 3, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "2", "entity": "魯達", "type": "person", "description": "魯達是一個強力的武官,積極幫助金老擺脱困境並懲罰了鄭屠。<SEP>魯達是一名經略府的提轄,以其武藝高強和性格粗獷著稱,在故事中因打死鄭屠而逃走。<SEP>魯達是經略府的提轄,曾與史進在茶坊相遇,並願意幫助史進尋找他的師父。<SEP>魯達,也稱魯提轄,因打死鄭屠而被捉拿在逃,是故事中的主要人物。", "rank": 18, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "3", "entity": "兩個都頭", "type": "person", "description": "兩個都是官府中的官員,負責抓捕史進等人。", "rank": 2, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "4", "entity": "史進", "type": "person", "description": "史進是一位與魯提轄一起行動的個人,也被稱為史大郎,其師父是打虎將李忠。<SEP>史進是一位有義氣的英雄,他在保護朱武三人免受官府逼迫的過程中,採取了緊張的行動,並最終決定離開家鄉去尋找他的師父。<SEP>史進是一名清白的好漢,拒絕落草為寇,離開少華山,尋找他的師父王教頭。<SEP>史進是與魯提轄一同飲酒的同伴之一,提供了幫助金氏父女的一部分錢財。", "rank": 17, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "5", "entity": "楊春", "type": "person", "description": "楊春是史進朋友中的另一位頭領,積極參與史進保護朱武等人並對抗官府的行動。", "rank": 1, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "6", "entity": "事件: 魯達還房錢", "type": "event", "description": "魯達提出支付鄭屠的錢,以釋放金老。", "rank": 0, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "7", "entity": "代州雁門縣", "type": "geo", "description": "代州雁門縣是魯達逃亡過程中到達的一座城市。", "rank": 1, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "8", "entity": "事件公告", "type": "event", "description": "州衙廳發佈的公告,懸賞捉拿魯提轄的信息在代州雁門縣發佈。", "rank": 2, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}]

LightRAG召回內容(Relationships部分):

-----Relationships(KG)-----```json[{"id": "1", "entity1": "金翠蓮", "entity2": "魯達", "description": "魯達為了幫助金翠蓮及其父親而採取行動。<SEP>魯達提及金翠蓮,認為她是被鄭屠欺騙的受害者。", "keywords": "受害者保護,忠義,救助", "weight": 14.0, "rank": 21, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "2", "entity1": "兩個都頭", "entity2": "史進", "description": "兩個都頭代表官府來抓捕史進,與史進及其同夥發生對抗。", "keywords": "對抗,抓捕", "weight": 9.0, "rank": 19, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "3", "entity1": "史進", "entity2": "楊春", "description": "史進和楊春共同行動以對抗官府並保護山寨成員。", "keywords": "合作,聯盟", "weight": 8.0, "rank": 18, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "4", "entity1": "事件公告", "entity2": "魯達", "description": "公告號召人們協助捉拿魯提轄。", "keywords": "司法通緝", "weight": 9.0, "rank": 20, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "5", "entity1": "代州雁門縣", "entity2": "魯達", "description": "魯達在逃亡過程中到達代州雁門縣。", "keywords": "逃亡途點", "weight": 9.0, "rank": 19, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "6", "entity1": "事件公告", "entity2": "州衙廳", "description": "州衙廳發佈有關魯達的逃犯公告。", "keywords": "司法公告", "weight": 7.0, "rank": 4, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}]```

LightRAG召回內容(Document Chunks部分):

-----Document Chunks(DC)-----```json[{"id": "1", "content": "上。魯達再入一步,踏住胸脯,提着那醋缽兒大小拳頭,看着這鄭屠道:\n\n    “灑家始投老種經略相公,做到關西五路廉訪使,也不枉了叫做鎮關西。你是個賣肉的操刀屠户,狗一般的人,也叫做鎮關西!你如何強騙了金翠蓮?”撲的只一拳,正打在鼻子上,打得鮮血迸流,鼻子歪在半邊,卻便似開了個油醬鋪,鹹的、酸的、辣的,一發都滾了出來。鄭屠掙不起來,那把尖刀也丟在一邊,口裏只叫:“打得好!”魯達罵道:“直娘賊,還敢應口!”提起拳頭,就眼眶際眉梢只一拳,打得眼稜縫裂,烏珠迸出,也似開了個彩帛鋪的,紅的、黑的、絳的,都綻將出來。兩邊看的人,懼怕魯提轄,誰敢向前來勸?\n\n    鄭屠當不過,討饒。魯達喝道:“咄!你是個破落户,若是和俺硬到底,灑家倒饒了你;你如何對俺討饒,灑家偏不饒你。”又只一拳,太陽穴上正着,卻似做了一個全堂水陸的道場,磬兒、鈸兒、鐃兒,一齊響。魯達看時,只見鄭屠挺在地下,口裏只有出的氣,沒有入的氣,動彈不得。魯提轄假意道:\n\n    “你這廝詐死,灑家再打。”只見麪皮漸漸的變了。\n\n    魯達尋思道:“俺只指望痛打這廝一頓,不想三拳真個打死了他,。灑家須吃官司,又沒人送飯,不如及早撒開。”拔步便走,回頭指着鄭屠屍道:“你詐死,灑家和你慢慢理會。”\n\n    一頭罵,一頭大踏步去了。街坊鄰舍並鄭屠的火家,誰敢向前來攔他。魯提轄回到下處,急急捲了一些衣服盤纏、細軟銀兩,但是舊衣粗重都棄了。提了一根齊眉短棒,奔出南門,一道煙走了。\n\n    且説鄭屠家中眾人,救了半日不活,嗚呼死了。老小鄰人徑來州衙告狀。正直府尹升廳,接了狀子,看罷道:“魯達系是經略府的提轄,不敢擅自徑來捕捉凶身。”府尹隨即上轎,來到經略府前,下了轎子。把門軍士入去報知,府中聽得,教請到廳上,與府尹施禮罷。經略問道:“何來?”府尹稟道:“好教相公得知。府中提轄魯達,無故用拳打死市上鄭屠。不曾稟過相公,不敢擅自捉拿凶身。”經略聽説,吃了一驚,尋思道:“這魯達雖好武藝,只是性格粗鹵,今番做出人命事,俺如何護得短?須教他推問使得。”經略回府尹道:“魯達這人,原是我父親老經略處的軍官,為因俺這裏無人幫護,撥他來做個提轄。既然犯了人命罪過,你可拿他依法度取問。如若供招明白,擬罪已定,也須教我父親知道,方可斷決。怕日後父親處邊上要這個人時,卻不好看。”府尹稟道:“下官問了情由,合行申稟老經略相公知道,方可斷遣。”府尹辭了經略相公,出到府前,上了轎,回到州衙裏,措廳坐下。便喚當日緝捕使臣押下文書,捉拿犯人魯達。\n\n    當時王觀察領了公文,將帶二十來個做公的人,徑到魯提轄下處。只見房主人道:“卻才?了些包裹,提了短棒出去了。小人只道奉着差使,又不敢問他。”王觀察聽了,教打開他房門看時,只有些舊衣舊裳和些被卧在裏面。王觀察就帶了房主人,東西四下裏去跟尋,州南走到州北,捉拿不見。王觀察又捉了兩家鄰舍並房主人,同到州衙廳上回話道:“魯提轄懼罪在逃,不知去向,只拿得房主人並鄰舍在此。”府尹見説,且教監下;一面教拘集鄭屠家鄰佑人等,點了仵作", "file_path": "unknown_source"}, …….

RAG多路召回合併後輸入給LLM的信息:

{'messages': [{'role': 'system', 'content': 'You are an AI assistant, developed by SenseTime.\n請你參考所給的信息給出問題的答案.\nHere are some extra messages you can referred to:\n\n### context_str:\n知識圖譜召回結果:\n-----Entities(KG)-----\n\n```json\n[{"id": "1", "entity": "金翠蓮", "type": "person", "description": "金翠蓮是金老的女兒,受到鄭屠的不公平對待。<SEP>金翠蓮是魯達提到的一個女性,據推測她可能是被鄭屠欺騙或傷害的對象。", "rank": 3, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "2", "entity": "魯達", "type": "person", "description": "魯達是一個強力的武官,積極幫助金老擺脱困境並懲罰了鄭屠。<SEP>魯達是一名經略府的提轄,以其武藝高強和性格粗獷著稱,在故事中因打死鄭屠而逃走。<SEP>魯達是經略府的提轄,曾與史進在茶坊相遇,並願意幫助史進尋找他的師父。<SEP>魯達,也稱魯提轄,因打死鄭屠而被捉拿在逃,是故事中的主要人物。", "rank": 18, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, {"id": "3", "entity": "兩個都頭", "type": "person", "description": "兩個都是官府中的官員,負責抓捕史進等人。", "rank": 2, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}, ......]\n```\n\n-----Relationships(KG)-----\n\n```json\n[{"id": "1", "entity1": "金翠蓮", "entity2": "魯達", "description": "魯達為了幫助金翠蓮及其父親而採取行動。<SEP>魯達提及金翠蓮,認為她是被鄭屠欺騙的受害者。", "keywords": "受害者保護,忠義,救助", "weight": 14.0, "rank": 21, "created_at": "2025-06-10 00:06:37", "file_path": "unknown_source"}......]\n```\n\n-----Document Chunks(DC)-----\n\n```json\n[{"id": "1", "content": "上。魯達再入一步,踏住胸脯,提着那醋缽兒大小拳頭,看着這鄭屠道:\\n\\n    “灑家始投老種經略相公,做到關西五路廉訪使,也不枉了叫做鎮關西。你是個賣肉的操刀屠户,狗一般的人,也叫做鎮關西!你如何強騙了金翠蓮?”撲的只一拳,正打在鼻子上,打得鮮血迸流,鼻子歪在半邊,卻便似開了個油醬鋪,鹹的、酸的、辣的,一發都滾了出來。鄭屠掙不起來,那把尖刀也丟在一邊,口裏只叫:“打得好!”魯達罵道:“直娘賊,還敢應口!”提起拳頭,就眼眶際眉梢只一拳,打得眼稜縫裂,烏珠迸出,也似開了個彩帛鋪的,紅的、黑的、絳的,都綻將出來。兩邊看的人,懼怕魯提轄,誰敢向前來勸?\\n\\n    鄭屠當不過,討饒。魯達喝道:“咄!你是個破落户,若是和俺硬到底,灑家倒饒了你;你如何對俺討饒,灑家偏不饒你。”又只一拳,太陽穴上正着,卻似做了一個全堂水陸的道場,磬兒、鈸兒、鐃兒,一齊響。魯達看時,只見鄭屠挺在地下,口裏只有出的氣,沒有入的氣,動彈不得。魯提轄假意道:\\n\\n    “你這廝詐死,灑家再打。”只見麪皮漸漸的變了。\\n\\n    魯達尋思道:“俺只指望痛打這廝一頓,不想三拳真個打死了他,。灑家須吃官司,又沒人送飯,不如及早撒開。”拔步便走,回頭指着鄭屠屍道:“你詐死,灑家和你慢慢理會。”\\n\\n    一頭罵,一頭大踏步去了。街坊鄰舍並鄭屠的火家,誰敢向前來攔他。魯提轄回到下處,急急捲了一些衣服盤纏、細軟銀兩,但是舊衣粗重都棄了。提了一根齊眉短棒,奔出南門,一道煙走了。\\n\\n    且説鄭屠家中眾人,救了半日不活,嗚呼死了。......]\n```\n\nBM25召回結果:\n”又只一拳,太陽穴上正着,卻似做了一個全堂水陸的道場,磬兒、鈸兒、鐃兒,一齊響。魯達看時,只見鄭屠挺在地下,口裏只有出的氣,沒有入的氣,動彈不得。魯提轄假意道:\r\n\r\n    “你這廝詐死,灑家再打。”只見麪皮漸漸的變了。\r\n\r\n    魯達尋思道:“俺只指望痛打這廝一頓,不想三拳真個打死了他,。灑家須吃官司,又沒人送飯,不如及早撒開。”拔步便走,回頭指着鄭屠屍道:“你詐死,灑家和你慢慢理會。”\r\n\r\n    一頭罵,一頭大踏步去了。街坊鄰舍並鄭屠的火家,誰敢向前來攔他。魯提轄回到下處,急急捲了一些衣服盤纏、細軟銀兩,但是舊衣粗重都棄了。提了一根齊眉短棒,奔出南門,一道煙走了。\r\n\r\n    ......'}, {'role': 'user', 'content': '魯智深打死的是誰?'}], 'model': 'SenseChat-5', 'stream': True}

參考文獻:

微軟Graph RAG:https://www.microsoft.com/en-us/research/project/graphrag/

LightRAG:https://arxiv.org/pdf/2410.05779


更多技術內容,歡迎移步 "LazyLLM" 討論!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.