更強的RAG:向量資料庫和知識圖譜的結合

balahoho發表於2024-10-10

傳統 RAG 的侷限性

經典的 RAG 架構以向量資料庫(VectorDB)為核心來檢索語義相似性上下文,讓大語言模型(LLM)不需要重新訓練就能夠獲取最新的知識,其工作流如下圖所示:

image-20240929145459119

這一架構目前廣泛應用於各類 AI 業務場景中,例如問答機器人、智慧客服、私域知識庫檢索等等。雖然 RAG 透過知識增強一定程度上緩解了 LLM 幻覺問題,但是依然面臨準確度不高的問題,它受限於資訊檢索流程固有的限制(比如資訊檢索相關性、搜尋演算法效率、Embedding模型)以及大量對 LLM 能力依賴,使得在生成結果的過程中有了更多的不確定性。

下圖來自 RAG 領域一篇著名的論文:《Seven Failure Points When Engineering a Retrieval Augmented Generation System》 ,它總結了在使用 RAG 架構時經常會遇到的一些問題(紅框部分,原作者稱之為7個失敗點)。

它們包括:

  • 內容缺失(Missing Content),使用者想要的答案並不在知識庫中,LLM會給出無意義的回答。
  • 缺失排名靠前的文件(Missed Top Ranked),受限於文字切分方式、大小、嵌入模型等影響,從向量資料庫中檢索出來的 top-k 不一定是最精確的答案。
  • 不在上下文中(Not in Context),和上一條類似,經過各種處理後最終取出來的上下文質量較低。
  • 格式錯誤(Wrong Format),檢索出來的結果沒有特定格式,而 LLM 沒有很好地對其識別分析。
  • 未提取(Not Extracted),Prompt 包含大量無關資訊影響了 LLM 判斷,即上下文包含很多噪音。
  • 答案不完整(Incomplete),生成的答案不夠完整。
  • 不正確的差異(Incorrect Speciticity),答案在響應中返回,但不夠具體或過於具體,無法滿足使用者的需求。

以上問題目前都有一些最佳化方法來提升回答準確度,但依然離我們的預期有差距。

因此除了基於語義匹配文字塊之外,業內也探索新的資料檢索形式,比如在語義之上關注資料之間的關聯性。這種關聯性區別於關係模型中的邏輯性強依賴,而是帶有一定的語義關聯。比如人和手機是兩個獨立的實體,在向量資料庫中的相似性一定非常差,但是結合實際生活場景,人和手機的關係是非常密切的,假如我搜尋一個人的資訊,我大可能性也關心他的周邊,例如用什麼型號的手機、喜歡拍照還是玩遊戲等等。

基於這樣的關係模型,從知識庫檢索出來的上下文在某些場景中可能更有效,例如“公司裡面喜歡使用手機拍照的人有哪些”。

用知識圖譜來呈現資料關係

為了有效地描述知識庫中的抽象資料關係,引入了知識圖譜的概念,它不再使用二維表格來組織資料,而是用圖結構來描述關係,這裡面的關係沒有固定的正規化約束,類似下面這種人物關係圖:

更強的RAG:向量資料庫和知識圖譜的結合 ![](https://img2024.cnblogs.com/blog/614524/202410/614524-20241010193535967-5879819.png)

上圖中有最重要的三個元素:物件(人物)、屬性(身份)、關係,對於儲存這樣的資料有專門的資料庫產品來支援,即圖資料庫。

圖資料庫(GraphDB)

圖資料庫是屬於 NoSQL 的一種,它有著較為靈活的 schema 定義,可以簡單高效表達真實世界中任意的關聯關係。圖資料庫中的主要概念有:

  • 實體(Entity),也稱之為頂點或節點,圖結構中的物件
  • 邊(Edge),連線兩個實體的一條路徑,即實體之間的關係
  • 屬性(Property),用來具體描述實體或邊的特徵

以上概念可對應到前面的人物關係圖。

具體到 schema 定義和資料操作層面,以流行的圖資料庫 NebulaGraph 為例:

# 建立圖空間
CREATE SPACE IF NOT EXISTS test(vid_type=FIXED_STRING(256), partition_num=1, replica_factor=1);
USE test;

# 建立節點和邊
CREATE TAG IF NOT EXISTS entity(name string);
CREATE EDGE IF NOT EXISTS relationship(relationship string);
CREATE TAG INDEX IF NOT EXISTS entity_index ON entity(name(256));

# 寫入資料
INSERT VERTEX entity (name) VALUES "1":("神州數碼");
INSERT VERTEX entity (name) VALUES "2":("雲基地");
INSERT EDGE relationship (relationship) VALUES "1"->"2":("建立了");
...

圖資料庫的查詢語法遵循 Cypher 標準:

MATCH p=(n)-[*1..2]-() RETURN p LIMIT 100;

以上語句從 NebulaGraph 中找到最多100個從某個節點出發,透過1到2條邊連線的路徑,並返回這些路徑。

如果用視覺化圖結構展示的話,它是這個樣子:

GraphRAG

如果借用 VectorRAG 的思想,透過圖資料庫來實現檢索增強的話就演變出了 GraphRAG 架構,整體流程和 VectorRAG 並無差異,只是新知識的儲存和檢索都用知識圖譜來實現,解決 VectorRAG 對抽象關係理解較弱的問題。

藉助一些AI腳手架工具可以很容易地實現 GraphRAG,以下是用 LlamaIndex 和圖資料庫 NebulaGraph 實現的一個簡易 GraphRAG 應用:

import os
from llama_index.core import KnowledgeGraphIndex, SimpleDirectoryReader
from llama_index.core import StorageContext
from llama_index.graph_stores.nebula import NebulaGraphStore
from llama_index.llms.openai import OpenAI
from pyvis.network import Network
from llama_index.core import (
    Settings,
    SimpleDirectoryReader,
    KnowledgeGraphIndex,
)

# 引數準備
os.environ['OPENAI_API_KEY'] = "sk-proj-xxx"
os.environ["NEBULA_USER"] = "root"
os.environ["NEBULA_PASSWORD"] = "nebula"
os.environ["NEBULA_ADDRESS"] = "10.3.xx.xx:9669"

space_name = "heao"
edge_types, rel_prop_names = ["relationship"], ["relationship"]  
tags = ["entity"] 

llm = OpenAI(temperature=0, model="gpt-3.5-turbo")

Settings.llm = llm
Settings.chunk_size = 512

# 連線Nebula資料庫例項
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)

hosts=graph_store.query("SHOW HOSTS")
print(hosts)

# 抽取知識圖譜寫入資料庫
documents = SimpleDirectoryReader(
    "data"
).load_data()

kg_index = KnowledgeGraphIndex.from_documents(
    documents,
    storage_context=storage_context,
    max_triplets_per_chunk=2,
    space_name=space_name,
    edge_types=edge_types,
    rel_prop_names=rel_prop_names,
    tags=tags,
    max_knowledge_sequence=15,
)

# 資訊檢索生成答案
query_engine = kg_index.as_query_engine()
response = query_engine.query("神州數碼雲基地在哪裡?")
print(response)

VectorRAG 擅長處理具有一定事實性的問題,對複雜關係理解較弱,而 GraphRAG 剛好彌補了這一點,如果把這兩者進行結合,理論上能得到更優的結果。

HybridRAG — 新一代 RAG 架構

我們可以把資料在 VectorDB 和 GraphDB 各放一份,分別透過向量化檢索和圖檢索得到與問題相關結果後,我們將這些結果連線起來,形成統一的上下文,最後將組合的上下文傳給大語言模型生成響應,這就形成了 HybridRAG 架構。
image-20240929145637811

由英偉達團隊Benika Hall等人在前段時間發表的論文《HybridRAG: Integrating Knowledge Graphs and Vector Retrieval Augmented Generation for Efficient Information Extraction》提出了這一設想,並使用金融服務行業中的資料整合、風險管理、預測分析等場景進行對比驗證。在需要從相關文字文件中獲取上下文以生成有意義且連貫的響應時 VectorRAG 表現出色,而 GraphRAG 能夠從金融檔案中提取的結構化資訊生成更準確的且具有上下文感知的響應,但 GraphRAG 在抽象問答任務或問題中沒有明確提及實體時通常表現不佳。

該論文最後給出了 VectorRAG、GraphRAG和 HybridRAG 的測試結果:

表格中F代表忠實度,用來衡量生成的答案在多大程度上可以從提供的上下文中推斷出來。AR代表答案相關性,用來評估生成的答案在多大程度上解決了原始問題。CP代表上下文精度,CR代表上下文召回率。

實現簡單的 HybridRAG

以下示例用 Llama_index 作為應用框架,用 TiDB Vector 作為向量資料庫,用 NebulaGraph 作為圖資料庫實現了簡單的 HybridRAG 流程:

import os
from llama_index.core import KnowledgeGraphIndex, VectorStoreIndex,StorageContext,Settings
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.graph_stores.nebula import NebulaGraphStore
from llama_index.llms.openai import OpenAI
from llama_index.vector_stores.tidbvector import TiDBVectorStore

TIDB_CONN_STR="mysql+pymysql://xx.root:xx@gateway01.eu-central-1.prod.aws.tidbcloud.com:4000/test?ssl_ca=isrgrootx1.pem&ssl_verify_cert=true&ssl_verify_identity=true"

os.environ["OPENAI_API_KEY"] = "sk-proj-xxx"
os.environ["NEBULA_USER"] = "root"
os.environ["NEBULA_PASSWORD"] = "nebula"
os.environ["NEBULA_ADDRESS"] = "10.3.xx.xx:9669"

llm = OpenAI(temperature=0, model="gpt-3.5-turbo")

Settings.llm = llm
Settings.chunk_size = 512

class HybridRAG:
    def __init__(self):
        # 初始化向量資料庫
        tidbvec = TiDBVectorStore(
            connection_string=TIDB_CONN_STR,
            table_name="semantic_embeddings",
            distance_strategy="cosine",
            vector_dimension=1536, # The dimension is decided by the model
            drop_existing_table=False,
        )
        tidb_vec_index = VectorStoreIndex.from_vector_store(tidbvec)
        self.vector_db = tidb_vec_index
        
        # 初始化知識圖譜
        graph_store = NebulaGraphStore(
            space_name="heao",
            edge_types=["relationship"],
            rel_prop_names=["relationship"],
            tags=["entity"],
        )
        storage_context = StorageContext.from_defaults(graph_store=graph_store)
        kg_index = KnowledgeGraphIndex.from_documents(
            [],
            storage_context=storage_context,
            max_triplets_per_chunk=2,
            max_knowledge_sequence=15,
        )
        self.kg = kg_index
        
        # 初始化語言模型
        self.llm = llm
        
        # 初始化嵌入模型
        self.embeddings = OpenAIEmbedding()
        
    def vector_search(self, query):
        # 在向量資料庫中搜尋相關文字
        results = self.vector_db.as_retriever().retrieve(query)
        print(results)
        return [result.node for result in results]
    
    def graph_search(self, query):
        # 在知識圖譜中搜尋相關資訊
        results = self.kg.as_retriever().retrieve(query)
        print(results)
        return [result.node for result in results]
    
    def generate_answer(self, query):
        vector_context = self.vector_search(query)
        graph_context = self.graph_search(query)
        combined_context = "\n".join(vector_context) + "\n" + graph_context
        
        prompt = f"Question: {query}\nContext: {combined_context}\nAnswer:"
        return self.llm.generate(prompt)

# 使用示例
hybrid_rag = HybridRAG()
question = "TiDB從哪個版本開始支援資源管控特性?"
answer = hybrid_rag.generate_answer(question)
print(answer)

以上實現中只是簡單的拼裝了從向量資料庫和圖資料庫中的檢索結果,實際應用中可引入相關reranker模型進一步做精排提高上下文精準度。不管最終使用什麼樣的 RAG 架構,我們要達到的目的始終是保障傳遞給大語言模型的上下文是更完整更有效的,從而讓大語言模型給出更準確的回答。

總結

RAG 架構已經是提升大語言模型能力的有效方案,鑑於在不同的業務場景中正確率無法穩定保持,因此也衍生出了一系列變化後的 RAG 架構,如 Advanced RAG、GraphRAG、HybridRAG、RAG2.0 等等,它們之間有的是互補關係,有的是最佳化增強,也有另起爐灶的路線,可以看出在 LLM 應用最佳化上依然處於快速變化當中。

相關文章