自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于知識圖譜的LangChain應用實戰(zhàn)

發(fā)布于 2024-10-18 14:53
瀏覽
0收藏

圖檢索增強生成(Graph RAG)正逐漸流行起來,成為傳統(tǒng)向量搜索方法的有力補充。這種方法利用圖數(shù)據(jù)庫的結(jié)構(gòu)化特性,將數(shù)據(jù)以節(jié)點和關系的形式組織起來,從而增強檢索信息的深度和上下文關聯(lián)性。


基于知識圖譜的LangChain應用實戰(zhàn)-AI.x社區(qū)


示例知識圖譜

圖在表示和存儲多樣化且相互關聯(lián)的信息方面具有天然優(yōu)勢,能夠輕松捕捉不同數(shù)據(jù)類型間的復雜關系和屬性。而向量數(shù)據(jù)庫在處理這類結(jié)構(gòu)化信息時則顯得力不從心,它們更擅長通過高維向量處理非結(jié)構(gòu)化數(shù)據(jù)。在 RAG 應用中,結(jié)合結(jié)構(gòu)化的圖數(shù)據(jù)和非結(jié)構(gòu)化的文本向量搜索,可以讓我們同時享受兩者的優(yōu)勢,這也是本文將要探討的內(nèi)容。

知識圖譜的確很有用,但如何構(gòu)建一個呢? 構(gòu)建知識圖譜通常是利用圖數(shù)據(jù)表示的強大功能中最困難的一步。它需要收集和整理數(shù)據(jù),這需要對領域知識和圖建模有深刻的理解。為了簡化這一過程,我們開始嘗試使用大型語言模型(LLM)。LLM 憑借其對語言和上下文的深刻理解,可以自動化知識圖譜創(chuàng)建過程中的大部分工作。通過分析文本數(shù)據(jù),這些模型能夠識別實體,理解它們之間的關系,并提出如何在圖結(jié)構(gòu)中最佳表示這些實體?;谶@些實驗,我們已經(jīng)將圖構(gòu)建模塊的首個版本集成到了 LangChain 中,本文將展示其應用。

相關代碼已在 GitHub 上發(fā)布。

Neo4j 環(huán)境搭建

為了跟隨本文的示例,您需要搭建一個 Neo4j 實例。最簡單的方法是在 Neo4j Aura 上啟動一個免費實例,它提供了 Neo4j 數(shù)據(jù)庫的云版本。當然,您也可以通過下載 Neo4j Desktop 應用程序來創(chuàng)建一個本地數(shù)據(jù)庫實例。

os.environ["OPENAI_API_KEY"] = "sk-"
os.environ["NEO4J_URI"] = "bolt://localhost:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "password"

graph = Neo4jGraph()

此外,您還需要一個 OpenAI 密鑰,因為我們將在本文中使用他們的模型。

數(shù)據(jù)導入

在本次演示中,我們將使用伊麗莎白一世的維基百科頁面。我們可以利用 LangChain 加載器 輕松地從維基百科獲取并分割文檔。

# 讀取維基百科文章
raw_documents = WikipediaLoader(query="Elizabeth I").load()

# 定義分塊策略
text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)
documents = text_splitter.split_documents(raw_documents[:3])

現(xiàn)在是時候根據(jù)獲取的文檔來構(gòu)建圖譜了。為此,我們開發(fā)了一個 LLMGraphTransformer 模塊,它極大地簡化了在圖數(shù)據(jù)庫中構(gòu)建和存儲知識圖譜的過程。

llm=ChatOpenAI(temperature=0, model_name="gpt-4-0125-preview")
llm_transformer = LLMGraphTransformer(llm=llm)

# 提取圖數(shù)據(jù)
graph_documents = llm_transformer.convert_to_graph_documents(documents)

# 存儲到 neo4j
graph.add_graph_documents(
  graph_documents, 
  baseEntityLabel=True, 
  include_source=True
)

您可以指定知識圖譜生成鏈使用哪種 LLM。目前,我們只支持 OpenAI 和 Mistral 的函數(shù)調(diào)用模型。不過,我們計劃未來會擴展 LLM 的選擇范圍。在這個例子中,我們使用的是最新的 GPT-4。需要注意的是,生成的圖譜質(zhì)量很大程度上取決于您使用的模型。理論上,您應該選擇能力最強的模型。LLM 圖轉(zhuǎn)換器返回的圖文檔可以通過 add_graph_documents 方法導入到 Neo4j。baseEntityLabel 參數(shù)為每個節(jié)點添加了一個額外的 __Entity__ 標簽,以增強索引和查詢性能。include_source 參數(shù)則將節(jié)點與其原始文檔關聯(lián)起來,便于數(shù)據(jù)追溯和理解上下文。

您可以在 Neo4j 瀏覽器中查看生成的圖譜。

基于知識圖譜的LangChain應用實戰(zhàn)-AI.x社區(qū)

結(jié)合混合(向量 + 關鍵字)和圖檢索方法。

請注意,這張圖片僅為了清晰展示,只展示了生成圖譜的一部分。

RAG 的混合檢索

在圖譜生成之后,我們將采用一種混合檢索方法,結(jié)合向量和關鍵字索引以及圖檢索技術,用于 RAG 應用。

基于知識圖譜的LangChain應用實戰(zhàn)-AI.x社區(qū)

結(jié)合混合(向量 + 關鍵字)和圖檢索方法。

上圖展示了一個檢索過程,從用戶提出問題開始,然后由 RAG 檢索器處理。這個檢索器結(jié)合了關鍵字和向量搜索來篩選非結(jié)構(gòu)化文本數(shù)據(jù),并將其與從知識圖譜中提取的信息結(jié)合起來。由于 Neo4j 同時支持關鍵字和向量索引,您可以使用單一數(shù)據(jù)庫系統(tǒng)實現(xiàn)所有三種檢索方式。這些來源的數(shù)據(jù)將被送入 LLM,以生成并提供最終答案。

非結(jié)構(gòu)化數(shù)據(jù)檢索器

您可以使用 Neo4jVector.from_existing_graph 方法為文檔添加關鍵字和向量檢索功能。該方法為混合搜索方法配置了關鍵字和向量搜索索引,目標是標記為 Document 的節(jié)點。如果缺少文本嵌入值,它還會自動計算。

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

然后,您可以使用 similarity_search 方法來調(diào)用向量索引。

圖檢索器

另一方面,配置圖檢索器雖然更為復雜,但提供了更大的靈活性。在這個例子中,我們將使用全文索引來識別相關節(jié)點,然后返回它們的直接鄰域。

基于知識圖譜的LangChain應用實戰(zhàn)-AI.x社區(qū)

圖檢索器示意圖

圖檢索器首先識別輸入中的相關實體。為了簡化,我們指導 LLM 識別人物、組織和地點。為了實現(xiàn)這一點,我們將使用 LCEL 配合新加入的 with_structured_output 方法。

# 從文本中提取實體
class Entities(BaseModel):
    """識別實體相關信息。"""

    names: List[str] = Field(
        ...,
        descriptinotallow="文本中出現(xiàn)的所有人物、組織或商業(yè)實體的名稱",
    )

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "您正在從文本中提取組織和人物實體。",
        ),
        (
            "human",
            "請按照給定格式從以下輸入中提取信息:{question}",
        ),
    ]
)

entity_chain = prompt | llm.with_structured_output(Entities)

讓我們來實際測試一下:

entity_chain.invoke({"question": "阿梅莉亞·埃爾哈特在哪里出生?"}).names
# ['阿梅莉亞·埃爾哈特']

很好,現(xiàn)在我們能夠在問題中識別出實體,接下來我們將使用全文索引將這些實體映射到知識圖譜中。首先,我們需要定義一個全文索引,并創(chuàng)建一個函數(shù)來生成全文查詢,這個查詢允許一定程度的拼寫錯誤,這里我們不詳細展開。

graph.query(
    "CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]")

def generate_full_text_query(input: str) -> str:
    """
    為給定的輸入字符串生成全文搜索查詢。

    該函數(shù)構(gòu)建一個適用于全文搜索的查詢字符串。它通過將輸入字符串分割成單詞,并對每個單詞附加一個相似性閾值(允許2個字符變化),然后使用 AND 運算符將它們組合起來。這對于將用戶問題中的實體映射到數(shù)據(jù)庫值非常有用,并且能夠容忍一些拼寫錯誤。
    """
    full_text_query = ""
    words = [word for word in remove_lucene_chars(input).split() if word]
    for word in words[:-1]:
        full_text_query += f"{word}~2 AND"
    full_text_query += f"{words[-1]}~2"
    return full_text_query.strip()

現(xiàn)在,讓我們整合所有步驟。

# 全文索引查詢
def structured_retriever(question: str) -> str:
    """
    收集問題中提到的實體的鄰域信息
    """
    result = ""
    entities = entity_chain.invoke({"question": question})
    for entity in entities.names:
        response = graph.query(
            """CALL db.index.fulltext.queryNodes('entity', $query, 
            {limit:2})
            YIELD node,score
            CALL {
              MATCH (node)-[r:!MENTIONS]->(neighbor)
              RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS 
              output
              UNION
              MATCH (node)<-[r:!MENTIONS]-(neighbor)
              RETURN neighbor.id + ' - ' + type(r) + ' -> ' +  node.id AS 
              output
            }
            RETURN output LIMIT 50
            """,
            {"query": generate_full_text_query(entity)},
        )
        result += "\n".join([el['output'] for el in response])
    return result

structured_retriever 函數(shù)首先識別用戶問題中的實體,然后遍歷這些實體,使用 Cypher 模板檢索相關節(jié)點的鄰域信息。讓我們來實際測試一下!

print(structured_retriever("伊麗莎白一世是誰?"))
# 伊麗莎白一世 - BORN_ON -> 1533年9月7日
# 伊麗莎白一世 - DIED_ON -> 1603年3月24日
# 伊麗莎白一世 - TITLE_HELD_FROM -> 英格蘭和愛爾蘭女王
# 伊麗莎白一世 - TITLE_HELD_UNTIL -> 1558年11月17日
# 伊麗莎白一世 - MEMBER_OF -> 都鐸王朝
# 伊麗莎白一世 - CHILD_OF -> 亨利八世
# 等等...

最終檢索器

正如我們一開始提到的,我們將結(jié)合非結(jié)構(gòu)化和圖檢索器來創(chuàng)建最終的上下文,這將傳遞給 LLM。

def retriever(question: str):
    print(f"搜索查詢:{question}")
    structured_data = structured_retriever(question)
    unstructured_data = [el.page_content for el in vector_index.similarity_search(question)]
    final_data = f"""結(jié)構(gòu)化數(shù)據(jù):
{structured_data}
非結(jié)構(gòu)化數(shù)據(jù):
{"#Document ".join(unstructured_data)}
    """
    return final_data

由于我們使用的是 Python,我們可以使用 f-string 輕松地將輸出合并。

定義 RAG 鏈

我們已經(jīng)成功實現(xiàn)了 RAG 的檢索組件。接下來,我們將引入一個提示,它利用混合檢索器提供的上下文來生成響應,從而完成 RAG 鏈的實現(xiàn)。

template = """根據(jù)以下上下文回答問題:
{context}

問題:{question}
"""
prompt = ChatPromptTemplate.from_template(template)

chain = (
    RunnableParallel(
        {
            "context": _search_query | retriever,
            "question": RunnablePassthrough(),
        }
    )
    | prompt
    | llm
    | StrOutputParser()
)

最后,我們可以測試我們的混合 RAG 實現(xiàn)。

chain.invoke({"question": "伊麗莎白一世屬于哪個家族?"})
# 搜索查詢:伊麗莎白一世屬于哪個家族?
# '伊麗莎白一世屬于都鐸王朝。'

我還加入了一個查詢重寫特性,使得 RAG 鏈能夠適應允許后續(xù)問題的對話環(huán)境。鑒于我們使用了向量和關鍵字搜索方法,我們需要重寫后續(xù)問題以優(yōu)化搜索過程。

chain.invoke(
    {
        "question": "她何時出生?",
        "chat_history": [("伊麗莎白一世屬于哪個家族?",
        "都鐸王朝")],

    }
)
# 搜索查詢:伊麗莎白一世何時出生?
# '伊麗莎白一世出生于1533年9月7日。'

您可以看到,'她何時出生?' 首先被重寫為 '伊麗莎白一世何時出生?'。然后使用重寫后的查詢來檢索相關上下文并回答問題。

總結(jié)

隨著 LLMGraphTransformer 的引入,生成知識圖譜的過程現(xiàn)在應該更加順暢和易于訪問,這使得任何想要通過知識圖譜提供的深度和上下文來增強其基于 RAG 的應用的人更容易上手。這只是一個開始,因為我們計劃進行更多的改進。

本文轉(zhuǎn)載自 ??AI小智??,作者: AI小智

已于2024-10-21 09:35:04修改
收藏
回復
舉報
回復
相關推薦