LLM RAG系列

RAG系列

本文介紹了RAG以及RAG pipeline的整個流程,包括請求轉換、路由和請求構造、索引和檢索、生成和評估等,其中引用了大量有價值的論文。

參考Advanced RAG Series: Generation and Evaluation中的5篇文章,並豐富了相關內容。

image

請求轉換

請求轉換是爲了提高查詢結果的準確性而對用戶請求進行重構、優化的過程。

爲什麼需要RAG?

  • 問題1:LLMs並不瞭解你的數據,且無法獲取與此相關的最新數據,它們是事先使用來自互聯網的公共信息訓練好的,因此並不是專有數據庫的專家也不會針對該數據庫進行更新。

    image
  • 問題2:上下文窗口-每個LLM都有一個tokens的最大限制(通常平均爲100tokens,約75個單詞),用於限制用戶每次提交的tokens數據,這會導致丟失上下文窗口之外的上下文,進而影響準確性、產生檢索問題和幻覺等。

    image
  • 問題3:中間遺失-即使LLMs可以一次性接收所有的數據,但它存在根據信息在文檔中的位置來檢索信息的問題。研究表明如果相關信息位於文檔中間(而非開頭或結尾時)時就會導致嚴重的性能降級。
    image

因此,我們需要RAG。

請求轉換

請求分解

由於用戶問題可能太含糊、太特殊或缺少上下文,因此LLM可能無法很好地處理這些問題。通常會建議在將請求發送到嵌入模型之前對其進行重構。下面是一些重構方式:

  • 重寫-檢索-讀取: 這種方式注重對用戶的查詢進行重構(而不僅僅是採用retriever或reader,左側圖示)。它使用一個LLM生成一個查詢,然後使用web查詢引擎來檢索內容(中間圖示)。此外還需要在pipeline中使用一個小型語言模型來進一步對齊結果(右側圖示)。

    image
  • 對問題進行濃縮或改寫:通常用於會話中,通過把對話改寫成一個好的獨立問題來給聊天機器人提供上下文。Langchain的一個提示模板示例如下:

    "Given the following conversation and a follow up question, rephrase the follow up \ question to be a standalone question.
    
    Chat History: {chat_history}
    
    Follow Up Input: {question}
    
    Standalone Question:"
    
  • RAG Fusion: 將RAG和倒數排名融合(RRF)結合。生成多個查詢(從多個角度添加上下文),並使用倒數分數對查詢結果重新排序,然後將文檔和分數進行融合,從而得到更全面和準確的回答。

    image
  • Step-Back Prompting:這是一種更加技術性的提示技術,通過LLM的抽象來衍生出更高層次的概念和首要原則。這是一個對用戶問題的迭代過程,用於生成一個"後退一步"的問題(step back question),然後使用該問題對應的回答來生成最終的回答。
    image

  • Query Expansion:這是一個通過爲LLM提供一個查詢並生成新的內容來擴展查詢的過程。適用於文檔檢索,特別是Chain-of-Thought(CoT 思維鏈)提示。

    使用生成的答案進行擴展:該方式中,讓LLM基於我們的查詢生成一個假設的回答,然後將該回答追加到我們的查詢中,並執行嵌入搜索。通過使用假設的答案來擴展查詢,這樣在進行嵌入搜索時可以檢索到其他相關向量空間,進而可以提供更準確的回答。

    使用多個查詢進行擴展:使用LLM基於我們的查詢來生成額外的類似查詢,然後將這些額外的查詢和原始查詢一起傳給向量數據庫進行檢索,從而可以大大提升準確性。注意需要通過對提示進行迭代來評估哪些提示會產生最佳結果。

    image

僞文檔(Psuedo documents)

僞文檔嵌入 (HyDE):當向向量數據庫詢問問題時,該數據庫有可能不會很好地標記相關性。因此更好的方式是先創建一個假設的回答,然後再查詢匹配的向量。需要注意的是,雖然這比直接查詢到答案的嵌入匹配要好,但對於高度不相關的問題和上下文,產生幻覺的可能性也會更高,因此需要對過程進行優化,並注意這些邊緣情況。

image

作爲RAG流程的第一個步驟,查詢轉換並不存在正確或錯誤的方式。這是一個帶有實驗性質的領域,只有通過構建才能知道哪些方式最適合你的使用場景。

路由和請求構造

路由

路由爲了將請求發送到與與請求內容相關的存儲。

image

由於環境中可能存在多個數據庫和向量存儲,而答案可能位於其中其中任何一個,因此需要對查詢進行路由。基於用戶查詢和預定義的選擇,LLM可以決定:

  • 正確的數據源

  • 需要執行的動作:例如,概括 vs 語義搜索

  • 是否並行執行多個選擇,並校對結果(多路由功能)

下面是一些路由請求的方式:

  • 邏輯路由:在這種情況下,我們讓LLM根據預定義的路徑來決定參考知識庫的哪個部分。這對於創建非確定性鏈非常有用,一個步驟的輸出產生下一個步驟。該方式用於通過LLM來選擇知識庫

    image

    下面是一個邏輯路由的例子,用於根據用戶的編程語言來選擇合適的數據源:

    from typing import Literal
    
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.pydantic_v1 import BaseModel, Field
    from langchain_openai import ChatOpenAI
    
    # Data model
    class RouteQuery(BaseModel):
        """Route a user query to the most relevant datasource."""
    
        datasource: Literal["python_docs", "js_docs", "golang_docs"] = Field(
            ...,
            description="Given a user question choose which datasource would be most relevant for answering their question",
        )
    
    # LLM with function call 
    llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
    structured_llm = llm.with_structured_output(RouteQuery)
    
    # 構造提示,根據用戶問題中的編程語言類型類選擇合適的數據源
    system = """You are an expert at routing a user question to the appropriate data source.
    
    Based on the programming language the question is referring to, route it to the relevant data source."""
    
    prompt = ChatPromptTemplate.from_messages(
        [
            ("system", system),
            ("human", "{question}"),
        ]
    )
    
    # 定義一個 router,通過給LLM(structured_llm)輸入提示(prompt)來產生結果
    router = prompt | structured_llm
    

    下面是使用方式。用戶給出question,然後調用router.invoke讓LLM找出合適的數據源,最終會在result.datasource中返回python_docs

    question = """Why doesn't the following code work:
    
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages(["human", "speak in {language}"])
    prompt.invoke("french")
    """
    
    result = router.invoke({"question": question})
    
  • 語義路由:使用基於上下文的提示來增強用戶查詢的強大方法。可以幫助LLMs快速、經濟地選擇路由(可以預定義或自定義選項),產生確定性的結果。該方式用於通過LLM選擇提示

    image

    下面是一個使用語義路由的例子,它嵌入了兩個提示模板,分別用於處理物理和數學問題,然後通過匹配用戶問題和提示模板的相似性程度來選擇合適的提示模板,然後應用到LLM中。

    from langchain.utils.math import cosine_similarity
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.prompts import PromptTemplate
    from langchain_core.runnables import RunnableLambda, RunnablePassthrough
    from langchain_openai import ChatOpenAI, OpenAIEmbeddings
    
    # 創建兩個提示模板,一個用於解決物理問題,另一個用於解決數學問題
    physics_template = """You are a very smart physics professor. \
    You are great at answering questions about physics in a concise and easy to understand manner. \
    When you don't know the answer to a question you admit that you don't know.
    
    Here is a question:
    {query}"""
    
    math_template = """You are a very good mathematician. You are great at answering math questions. \
    You are so good because you are able to break down hard problems into their component parts, \
    answer the component parts, and then put them together to answer the broader question.
    
    Here is a question:
    {query}"""
    
    # 嵌入提示
    embeddings = OpenAIEmbeddings()
    prompt_templates = [physics_template, math_template]
    prompt_embeddings = embeddings.embed_documents(prompt_templates)
    
    # Route question to prompt 
    def prompt_router(input):
        # 嵌入查詢
        query_embedding = embeddings.embed_query(input["query"])
        # 計算查詢和提示模板的相似度
        similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
        most_similar = prompt_templates[similarity.argmax()]
        # 根據相似度來選擇最合適的提示模板 
        print("Using MATH" if most_similar == math_template else "Using PHYSICS")
        return PromptTemplate.from_template(most_similar)
    
    
    chain = (
        {"query": RunnablePassthrough()}
        | RunnableLambda(prompt_router)
        | ChatOpenAI()
        | StrOutputParser()
    )
    
    # 用戶提問,什麼是黑洞
    print(chain.invoke("What's a black hole"))
    

更多參見Rag From Scratch: Routing

請求構造

請求構造是爲了解決針對特定類型數據庫查詢的問題。

image

在定義好路由之後是否就可以跨數據存儲發送請求?如果使用的是非結構化數據存儲就可以,但實際中,大部分數據都保存在結構化數據庫中,因此在構造請求的同時需要考慮到數據庫的類型。

image

我們很容易會假設和LLMs交互使用的是自然語言,但這是不對的,查詢採用的語言類型取決於和數據存儲的交互方式,因此在構建查詢時需要牢記數據庫的查詢語言(從使用SQL的關係型數據庫到使用相關結構化元數據的非結構化數據)。

image

a. 自查詢檢索器-Self-query retriever (文本->元數據的過濾器):向量數據庫中帶有清晰元數據文件的非結構化數據可以啓用此類retriever。任何用戶問題(自然語言格式)都可以被拆分爲一個查詢和一個過濾器(如根據年、電影、藝術家)。通過提升發送到LLM的數據質量,可以大大提升Q&A的工作流表現。

image

b. 文本-> SQL: 通常LLMs對Text2SQL的表現不佳,去年有很多初創公司就將焦點放在如何解決此類問題上。從創建虛構的表和字段到用戶查詢中的拼寫錯誤等原因都可能導致LLMs執行失敗。鑑於這種數據庫相當普遍,因此出現了很多幫助LLM準確創建SQL查詢的方式,但都與特定的知識庫相關,因此不存在可以用於多種數據庫的通用方式。對於一個新的數據庫,只能通過構建、迭代、修復來優化LLMs的表現。

  • Create Table + Select 3:在瞭解更多高級技術之前,這是獲得模型在數據庫上的表現基線的最直接的方法。在Prompt設計中,對於每張表,可以包括一個CREATE TABLE描述,並在一個SELECT語句中提供三個示例行。
image
  • 少量樣本示例:爲LLM提供少量Q&A示例來幫助它理解如何構建請求,通過這種方式可以提升10~15%的準確性。根據示例的質量和使用的模型,添加更多的示例可以獲得更高的準確性。在有大量示例的情況下,可以將其保存在向量存儲中,然後通過對輸入查詢進行語義搜索,動態選擇其中的一部分。

    image
  • 此外還有一篇不錯的博客展示了fine-tuning帶來的巨大提升:
    image

  • RAG + Fine-tuning:相比於只是爲了讓語言模型能夠理解而將整個schema添加到提示中,使用經過調優的schema RAG和ICL的模型可以將準確性提高20%以上。
    image

  • 用戶拼寫錯誤:通過搜索合適的名詞而不是使用包含正確拼寫的向量存儲,是減少用戶特定錯誤的一種好方法。這在早期的Text2SQL中是一個特別令人煩惱的問題。

c. 文本-> Cypher:與圖數據庫有關,用於表達無法使用表格形式表示的關係。Cypher是這類數據庫的查詢語言。text-2-Cypher 是一項複雜的任務,對於此類任務,推薦使用GPT4。

知識圖譜可以有效提高檢索的準確性,因此數據格式不應該侷限於表和2D結構。

image

下面是構造請求的例子

Examples Data source References
Text-to-metadata-filter Vectorstores Docs
Text-to-SQL SQL DB Docs, blog, blog
Text-to-SQL+ Semantic PGVecvtor supported SQL DB Cookbook
Text-to-Cypher Graph databases Blog, Blog, Docs

請求轉換、構造和路由可以幫助我們和請求的數據庫進行交互。接下來,我們將進入稱爲索引(Indexing)的工作流程部分,在那裏,我們將更深入地研究拆分、索引嵌入以及如何通過配置這些功能來實現準確檢索。

索引

在LLM中,文檔會以chunk進行切分,索引用於找到與查詢相關的chunk

在上文中,我們討論了在構造查詢時需要考慮到與數據庫交互所使用的語言。這裏要講的索引類似被查詢的數據,索引的實現方式有很多種,但目的都是爲了在不丟失上下文的情況下方便LLM的理解。由於對應用戶問題的答案可能位於文檔中的任何地方,且考慮到LLMs在實時數據、上下文窗口和"中間遺失"問題中的不足,因此有效劃分chunk並將其添加到上下文中非常重要。

chunk劃分和嵌入是實現準確檢索的索引核心部分。簡單來說,嵌入向量是將一個大而複雜的數據集轉化爲一組能夠捕捉所嵌入的數據本質的數字。這樣就可以將用戶請求轉化爲一個嵌入向量(一組數字),然後基於語義相似性來檢索信息。它們在高緯度空間的呈現如下(相似的詞的距離相近):

image

回到chunk劃分,爲了方便理解,假設有一個大型文檔,如電子書。你希望從這本書中解答問題,但由於它遠超LLMs的上下文窗口,我們可能需要對其分塊,然後將於用戶問題相關的部分提供給LLM。而當執行分塊時,我們不希望因爲分塊而丟失故事的角色上下文,此時就會用到索引。

下面是一些索引方式:

chunk優化

首先需要考慮數據本身的長度,它定義了chunk的劃分策略以及使用的模型。例如,如果要嵌入一個句子,使用sentence transformer可能就足夠了,但對於大型文檔,可能需要根據tokens來劃分chunk,如使用 text-embedding-ada-002

其次需要考慮的是這些嵌入向量的最終使用場景。你需要創建一個Q&A機器人?一個摘要工具?還是作爲一個代理工具,將其輸出導入到其他LLM中進一步處理?如果是後者,可能需要限制傳遞到下一個LLM的上下文窗口的輸出長度。

image

下面提供了幾種實現策略:

基於規則

使用分隔符(如空格、標點符號等)來切分文本:

  • 固定長度:最簡單的方式是根據固定數目的字符來劃分chunk。在這種方式下,緩解上下文遺失的方式是在每個chunk中添加重疊的部分(可自定義)。但這種方式並不理想,可以使用langchain的CharacterTextSplitter進行測試

    image

    然後是所謂的結構感知拆分器,即基於句子、段落等劃分chunk。

  • NLTK語句分詞器(Sentence Tokenizer):將給定的文本切分爲語句。這種方式雖然簡單,但仍然受限於對底層文本的語義理解,即便在一開始的測試中表項良好,但在上下文跨多個語句或段落的場景下仍然不理想(而這正是我們希望LLM查詢的文本)。

    image
  • Spacy語句分割器(Sentence Splitter):另一種是基於語句進行拆分,在希望引用小型chunks時有用。但仍存在和NLTK類型的問題。

    image

遞歸結構感知拆分(Recursive structure aware splitting)

結合固定長度和結構感知策略可以得到遞歸結構感知拆分策略。Langchain文檔中大量採用了這種方式,其好處是可以更好地控制上下文。爲了方便語義搜索,此時chunk大小不再相等,但仍然不適合結構化數據

image

內容感知拆分

對於非結構化數據來說,上面幾種策略可能就足夠了,但對於結構化數據,就需要根據結構本身類型進行拆分。這就是爲什麼有專門用於Markdown、LaTeX、HTML、帶表格的pdf、多模式(即文本+圖像等)的文本拆分器。

image

多表示索引

相比於將整個文檔進行拆分,然後根據語義相似性檢索出 top-k的結果,那如果將文本轉換爲壓縮的檢索單元會怎樣?例如,壓縮爲摘要。

父文檔(Parent Document)

這種場景下可以根據用戶的請求檢索到最相關的chunk並傳遞該chunk所屬的父文檔,而不是僅僅將某個chunk傳遞給LLM。通過這種方式可以提升上下文並增強檢索能力。但如果父文檔比LLM的上下文窗口還要大怎麼辦?爲了匹配上下文窗口,我們可以將較大的chunks和較小的chunks一起傳遞給LLM(而不是整個父文檔)。

image

上面vectorstores中存儲的是較小的 chunks,使用InMemoryStore存儲較大的 chunk。

在使用時文檔會被分爲大小兩種chunk,其中小chunk都有其所屬的大chunk。由於"chunk越小,其表達的語義精確性更高",因此在查詢時,首先檢索到較小的chunk,而較小的chunk的元數據中保存了其所屬的大chunk,因而可以將小chunk和其所屬的大chunk一起傳遞給LLM。

密集檢索(Dense X Retrieval)

密集檢索是一種使用非語句或段落chunk進行上下文檢索的新方法。在下面論文中,作者將其稱之爲"proposition",一個proposition包含:

  • 文本中的不同含義:需要捕獲這些含義,這樣所有propositions一起就能在語義上覆蓋整個文本。
  • 最小單元:即不能再進一步拆分propositions。
  • 上下文相關和自包含:即每個propositions應該包含所有必需的上下文。
image image

Proposition級級別的檢索比語句級別的檢索和篇章級別的檢索分別高35%和22.5%(顯著提高)。

特定嵌入

領域特定和/或高級嵌入模型。

  • Fine-tuning:對嵌入模型進行微調可以幫助改進RAG pipeline檢索相關文檔的能力。這裏,我們使用LLM生成的查詢、文本語料庫以及兩者之間的交叉參考映射來磅數嵌入模型理解需要查找的語料庫。微調嵌入模型可以幫助提升大約5~10%的表現。

    下面是Jerry Liu對微調嵌入模型的建議:

    1. 在項目開始時,需要在嵌入文檔前對基礎模型進行微調
    2. 由於生產環境中的文檔分佈可能會發生變化,因此在微調查詢適配器(query adapter)時需要確保嵌入的文檔不會發生變化。
  • ColBERT:這是一個檢索模型,它能夠基於BERT在大規模數據集上實現可擴展的搜索(毫秒級別)。這裏,快速和準確檢索是關鍵。

    它會將每個段落編碼爲token級嵌入的矩陣(如下藍色所示)。當執行一個搜索時,它會將用戶查詢編碼爲另一個token級嵌入的矩陣(如下綠色所示)。然後基於上下文匹使用"可擴展的向量相似性(MaxSim)操作"來匹配查詢段落。

    image

    後期交互是實現快速和可擴展檢索的關鍵。雖然交叉編碼器會對每個可能的查詢和文檔對進行評估,從而提高準確性,但對於大規模應用程序而言,該特性會導致計算成本不斷累積。爲了實現大型數據集的快速檢索,需要提前計算文檔嵌入,因而需要平衡計算成本和檢索質量。

分層索引

斯坦福大學研究人員基於不同層次的文檔摘要樹提出了RAPTOR模型,即通過對文本塊的聚類進行摘要來實現更準確的檢索。文本摘要涵蓋了更大範圍的上下文,跨越不同的尺度,包括主題理解和細粒度的內容。檢索增強的文本摘要涵蓋了更大範圍不同主題理解和粒度的上下文。

論文聲稱,通過遞歸摘要進行檢索可以顯著提升模型表現。"在涉及複雜多步驟推理的問答任務中,我們展示了最佳結果。例如,通過將RAPTOR檢索與GPT-4相結合,我們可以在QuALITY基準測試的最佳表現上提高20%的絕對準確率。"

image

llamaindex提供了這種實現

檢索

檢索可以看做是對索引到的數據的進一步提煉。

在完成數據檢索之後,下一步需要根據用戶的請求來獲取相關數據。最常見和最直接的方法是從之前索引的數據(最近的鄰居)中識別並獲取與用戶查詢在語義上最接近的chunks。類似如下向量空間:

image

檢索並不是一步到位的,而是從查詢轉換開始的一些列步驟,以及如何通過檢索來提升獲取到相關的chunks之後的檢索質量。假設我們已經根據相似性搜索,從向量數據庫中找到前k個chunks,但由於大部分chunks存在重複內容或無法適應LLM的上下文窗口。因此在將chunks傳遞給LLM之前,我們需要通過一些檢索技術來提升上下文的質量。

LLMs的世界中,並不存在一勞永逸的方法,需要根據使用場景和chunks的特性來找到合適的技術。下面是一些典型的方法:

Ranking

Reranking

如果我們想要從數據庫的chunk中查找答案,可以選擇Reranking,它是一種可以給LLM提供最相關上下文的有效策略。有多種實現方式:

  • 提升多樣性:最常見的方法是最大邊緣相關性(Maximum Marginal Relevance-MMR),這可以通過因子a)與請求的相似度或b)與已選的文檔的距離等因子來實現。

    這裏有必要提一下Haystack的DiversityRanker:

    1. 首先計算每個文檔的嵌入向量,然後使用一個sentence-transformer模型進行查詢搜索
    2. 將語義上和查詢最接近的文檔作爲第一個選擇的文檔
    3. 對於剩下的每個文檔,會計算和所選擇文檔的平均相似性
    4. 然後選擇和所選擇文檔最不相似的文檔
    5. 然後重複上述步驟,直到選出所有文檔,這樣就得到了一個整體多樣性從高到低排序的文檔列表。

    下面介紹了幾種重排序的方式,稱爲reranker。

    image
  • LostInTheMiddleReranker:這是Haystack的另一個解決LLM不擅長從文檔中間檢索信息的問題的方法。它通過重排序來讓最佳的文檔位於上下文窗口的開頭和結尾。建議在相關性和多樣性之後再使用該Reranker。
    image

    image
  • CohereRerank:通過Cohere的Rerank endpoint實現,它會將一開始的搜索結果和用戶的請求進行比較,並基於請求文本和文檔之間的語義相似性來重新計算結果,而不僅僅根據向量的查詢結果。

    image
  • bge-rerank:除了要選擇出最適合你的數據的嵌入模型外,還需要重點關注可以和該嵌入模型配合使用的檢索器(retriever)。我們使用命中率和平均倒數排名(Mean Reciprocal Rank-MRR)作爲retriever的評估指標。命中率是指在前k個檢索到的chunks中找到正確答案的頻率,MRR是排名中最相關的文檔在排名中的的位置。從下面可以看到,JinaAI-Base嵌入中用到的bge-rerank-large 或 cohere-reranker 看起來非常適用於該數據集。下表中需要注意的是,嵌入模型並不是最好的rerankers。Jina最擅長嵌入,而bge reranker則最擅長重新排序。
    image

  • mxbai-rerank-v1:最新的一種重排序模型是由Mixedbread團隊開發的開源項目,稱爲SOTA。其表現聲稱要優於Cohere和bge-large。

    image
  • RankGPT:這是使用現有的LLMs(如GPT3.5)重排檢索文檔的方法之一,且重排質量好於Cohere。爲了解決檢索上下文大於LLM的上下文窗口的問題,該方法使用了一種"滑動窗口"策略,即在滑動窗口內實現漸進排名。這種方式擊敗了其他大部分reranker,其中包括Cohere。這種方式下需要注意的是延遲和成本,因此適用於優化小型開源模型。

    image

提示壓縮(Prompt Compression)

這裏有必要介紹一下Prompt Compression,它和Reranking關係密切。這是一種通過壓縮無關信息(即和用戶查詢無關)來降低檢索到的文檔的技術,有如下方式:

  • LongLLMLingua:該方式基於Selective-ContextLLMLingua 框架,是用於提示壓縮的SOTA方法,針對成本和延遲進行了優化。除此之外,LongLLMLingua採用了一種"採用問題感知由粗到細的壓縮方法、文檔重新排序機制、動態壓縮比例以及壓縮後的子序列恢復等策略來提升大型語言模型對關鍵信息的感知能力"的方式來提升檢索質量。該方法特別適用於長上下文文檔,解決"中間遺失"問題。

    image
  • RECOMP:使用"壓縮器",該方法使用文本摘要作爲LLMs的上下文來降低成本並提升推理質量。兩個壓縮器分別表示: a)提取壓縮器,用於從檢索的文檔中選擇相關語句;b)抽象壓縮器,用於根據多個文檔的合成信息來創建摘要。

    image
  • Walking Down the Memory Maze:該方法引入了MEMWALKER的概念,即按照樹的格式來處理上下文。該方法會對上下文劃分chunk,然後對每個部分進行概括。爲了回答一個查詢,該模型會沿着樹結構來迭代提示,以此來找出包含問題答案的段。特別適用於較長序列。

    image

RAG-fusion

這是 Adrian Raudaschl提出的一種方法,使用Reciprocal Rank Fusion (RRF)和生成查詢來提高檢索質量。該方法會使用一個LLM根據輸入生成多個用戶查詢,併爲每個查詢運行一個向量搜索,最後根據RRF聚合並優化結果。在最後一步中,LLM會使用查詢和重排序列表來生成最終輸出。這種方法在響應查詢時具有更好的深度,因此非常流行。

image

改進

CRAG (Corrective Retrieval Augmented Generation)

該方法旨在解決從靜態和有限數據中進行次優檢索的侷限性,它使用一個輕量的檢索評估器以及外部數據源(如web搜索)來補充最終的生成步驟。檢索評估器會對檢索的文檔和輸入進行評估,並給出一個置信度,用於觸發下游的knowledge動作。

image

下面報告結果可以看出其極大提升了基於RAG方法的表現:

image

FLARE (Forward Looking Active Retrieval)

該方法特別適用於長文本生成,它會生成一個臨時的"下一個語句",並根據該語句是否包含低概率tokens來決定是否執行檢索。該方法可以很好地決定什麼時候進行檢索,可以降低幻覺以及非事實的輸出。

image

上圖展示了FLARE的工作原理。當用戶輸入x ,並檢索出結果Dx,FLARE會迭代生成一個臨時的"下一句"(灰色字體),然後檢查它是否包含低概率的tokens(下劃線表示),如果包含(步驟2和3),則系統會檢索相關的文檔並重新生成語句。

考慮到需要優化的變量數目,檢索可能比較棘手,但通過對用戶場景和數據類型進行映射,可以大大降低成本、延遲並提升準確性。

下面將面對文本生成(Generation),並思考一些評估策略。

生成和評估

生成(generation)

使用索引和檢索可以保證輸出的完整性,接下來,需要在爲用戶生成結果之前對結果進行評估,並通過決策步驟來觸發相應的動作。Language Agents (CoALA) 的認知架構是一個很好的框架,可以將其放在上下文中,通過對檢索到的信息的評估來獲得一組可能性。如下圖所示:

image

有如下幾種方法可以實現這一動作選擇決策程序:

Corrective RAG (CRAG)

在上一章(檢索)中有提到過該方法,但它與本章節內容有所重疊,因此有必要展開介紹一下。

image

CRAG使用輕量級"檢索評估器"來增強generation,檢索評估器會爲每個檢索到的文檔生成一個置信值。該值確定了需要觸發的檢索動作。例如,評估器可以根據置信值來爲檢索到的文檔標記到三個桶(正確、模糊、不正確)中的某個桶中。

如果所有檢索到的文檔的置信值都低於閾值,retriever會認爲"不正確",然後會通過觸發外部知識源(如web搜索)動作來生成合格的結果。

如果至少有一個檢索到的文檔的置信值大於閾值,則會假設此次檢索是"正確的",然後會觸發 knowledge refinement來改進檢索到的文檔。 knowledge refinement需要將文檔拆分爲"知識條(knowledge strips)",然後根據相關性對每個strip打分,最相關的知識會被重新組合爲generation使用的內部知識。

當檢索評估器對自己的判斷沒有信心時會標記爲"模糊",此時會同時採用上述策略。見如下決策樹:

image

上述方法跨了四個數據來生成最佳結果。下表中可以看到,CRAG顯著優於RAG。self-CRAG則使這些差距更加明顯,並且展示了CRAG作爲RAG流水線"即插即用"的適應性。另外一個CRAG由於其他方法(如Self-RAG)的點是它可以靈活地替換底層LLM,如果未來可能要採用更加強大的LLM,這一點至關重要。

image

CRAG的一個明顯限制是它嚴重依賴檢索評估器的質量,並且容易受到網絡搜索可能引入的偏見的影響。因此可能需要對檢索評估器進行微調,且需要分配必要的"護欄"來確保輸出的質量和準確性。

Self-RAG

這是另一種可以提升LLM的質量和事實性,同時保持其多功能性的框架。與其檢索固定數目的段落(不管這些段落是相關還是不相關的),該框架則更關注按需檢索和自我反省。

image
  1. 將任一LLM訓練爲可以對自己生成的結果進行自我反省的模型(通過一種稱爲reflection tokens(retrieval 和 critique) 的特殊tokens)。檢索是由retrieval token觸發的,該token是由LLM根據輸入提示和之前的生成結果輸出的。
  2. 然後LLM會通過並行處理這些檢索到的段落來併發生成結果。該步驟會觸發LLM生成用於評估這些結果的critique tokens。
  3. 最後根據"真實性"和"相關性"來選擇用於最終generation的最佳結果。論文中描述了算法,其中tokens的定義如下:
    image

下面Langchain的示意圖展示了基於上述定義的reflection token的Self-RAG 推理決策算法。

image

就表現而言,無論是否進行檢索,Self-RAG都顯著優於基線(參考CRAG的Benchmark)。需要注意的是,可以通過在CRAG之上構建Self-CRAG來進一步提升模型表現。

該框架的成本和延遲受到LLM調用的數量的限制。可以通過一些變通方法,比如一次生成一代,而不是兩次爲每個塊生成一代進行優化。

RRR (Rewrite-Retrieve-Read)

RRR模式[Ma et al., 2023a]引入了 重寫-檢索-讀寫 流程,利用LLM來強化rewriter模塊,從而實現對檢索查詢的微調,進而提高reader的下游任務表現。

該框架假設用戶查詢可以被LLM進一步優化(及重寫),從而實現更精確的檢索。雖然這種方式可以通過LLMs的查詢重寫過程來提升表現,但也存在推理錯誤或無效查詢之類的問題,因此可能不適用於部署在生產環境中。

image

爲了解決上圖中描述的問題,需要在rewriter上添加一個可訓練的小型LLM(如上圖紅色部分所示)來持續提升訓練的表現,使之兼具可擴展性和高效性。這裏的訓練包含兩個階段:"預熱"和增強學習,其關鍵是將可訓練模塊集成到較大的LLM中。

評估

RAG pipeline中實現評估的方式有很多種,如使用一組Q&A作爲測試數據集,並將輸出與實際答案進行對比驗證。但這種方式的缺點是不僅需要花費大量時間,並且增加了針對數據集本身優化pipeline的風險(而非可能存在很多邊緣場景的現實世界)。

RAGAs

RAGAs(RAG Assessment的簡稱)是一個用於評估RAG pipeline的開源框架,它可以通過如下方式評估pipeline:

  • 提供基於"ground truth"(真實數據)來生成測試數據的方式
  • 爲檢索和generation階段以獨立或端到端的方式提供基於指標的評估
image

它使用如下RAG特徵作爲指標:

  1. 真實性:事實一致性
  2. 回答相關性:問題和查詢的相關性
  3. 上下文準確性:校驗相關chunks的排名是否更高
  4. 批判(Critique):根據預定義的特徵(例如無害和正確性)評估提交
  5. 上下文回顧:通過比較ground truth和上下文來校驗是否檢索到所有相關信息
  6. 上下文實體回顧:評估上下文檢索到的和ground truth中的實體數目
  7. 上下文相關性:檢索到的上下文和提示的相關性
  8. 回答語義相似性:生成的答案和實際的語義相似性
  9. 回答正確性:評估生成答案與實際答案的準確性和一致性

上面提供了一組可以全面評估RAG的列表,推薦閱讀Ragas文檔。

Langsmith

Langchain的Langsmith可以作爲上面上下文中的可觀測性和監控工具。

LangSmith 是一個可以幫助調試、測試、評估和監控基於任何LLM空間構建的chains和agents平臺。

通過將Langsmith和RAG進行結合可以幫助我們更深入地瞭解結果。通過Langsmith評估logging和tracing部分可以更好地瞭解到對哪個階段進行優化可以提升retriever或generator。

image

DeepEval

另一種值得提及的評估框架稱爲DeepEval。它提供了14種以上涵蓋RAG和微調的指標,包括G-Eval、RAGAS、Summarization、Hallucination、Bias、Toxicity 等。

這些指標的自解釋信很好地說明了指標評分的能力,使之更易於調試。這是跟上面的RAGAs的關鍵區別。

此外,它還有一些不錯的特性,如集成Pytest (開發者友好),其模塊化組件等也是開源的。

Evaluate → Iterate → Optimize

通過生成和評估階段,我們不僅可以部署一個RAG系統,還可以評估、迭代並針對我們設計的特定場景進行優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章