iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
生成式 AI

阿,又是一個RAG系列 第 10

Day9: longllmlingua2 與 Node PostProcessor

  • 分享至 

  • xImage
  •  

Situation:

  • 我們昨天試了 Tavily research 想要看看可不可以直接查到好的 context ,但發現查回來的內容本身就帶有很多雜訊,應該不適合直接當成我們的 dataset
    • 想要有一個可以把查回來的資訊做一些精簡的模塊
  • 我們昨天還把 Tavily research 當成 tool 直接接給 llm 後,發現他直接爆 call 了 8 次,每次 6 個網頁,等於一次性的來了 8 * 6 = 48 頁
    • 一頁一頁 call llm 來 summary 在有些情況下速度跟花費都沒辦法接受
    • rerank 先篩然後一大串一起 summary 則可以考慮

Task

  • 總之,我們需要一個 把檢索來的 document 整理成比較精簡的算法,而且檢索來的 document 量不會太小,在意速度的話就不能一個一個 summary , 可能要加一層過濾
  • 今天要探索的是 LLMLingua-2,主要參考的是 llama-index 封裝的版本
    • llamaindex的範例
    • 介紹
    • 總之就是 input 一個 list of node 給他,他會回我們一個精簡的 node
    • 用的是 xlm-roberta-large 所以速度不會太慢,還支持中文
      • 支持中文的意思就是不會完全不能跑但是效果應該還是有打折
    • 裡面自帶了一系列步驟: Re-Ranking -> Fine-grained Prompt Compression -> Subsequence Recovery
      • 反正我們就先 call call 看,不能用再回頭考慮土炮

Action

  1. Setup
  • 我們今天一樣會需要 tavily
  • 然後安裝 llama-index 的 longllmlingua
    • pip install llama-index-postprocessor-longllmlingua
  • code
    import os
    from dotenv import find_dotenv, load_dotenv
    _ = load_dotenv(find_dotenv())
    
    TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
    from llama_index.tools.tavily_research.base import TavilyToolSpec
    
    tavily_tool = TavilyToolSpec(
        api_key=TAVILY_API_KEY,
    )
    
  1. tavily.search
  • 我們先用 tavily 直接搜幾個 document 回來
    question = '徵象(Signs)及症狀(Symptoms)之區別?'
    tavily_response = tavily_tool.search(question, max_results=3)
    type(tavily_response), len(tavily_response)  # (list, 3)
    tavily_response[0]
    
  • result
    • https://ithelp.ithome.com.tw/upload/images/20250924/20177855HHWXDUF1Q2.jpg
  1. NodeWithScore
  • LongLLMLinguaPostprocessor 的 input 是 list of llama_index 的 NodeWithScore,我們把 tavily 回傳的 list of document 轉成 NodeWith Score
    from llama_index.core.schema import TextNode, NodeWithScore
    
    nodes = [
        NodeWithScore(
            node=TextNode(
                text=doc.text,
                metadata=doc.metadata,
                id_=doc.doc_id,
            )
        )
        for doc in tavily_response
    ]
    
  1. LongLLMLinguaPostprocessor
  • 接著我們初始化 LongLLMLinguaPostprocessor
    # pip install llama-index-postprocessor-longllmlingua
    import time
    from llama_index.postprocessor.longllmlingua import LongLLMLinguaPostprocessor
    
    start = time.time()
    
    compressor_llmlingua2 = LongLLMLinguaPostprocessor(
        model_name="microsoft/llmlingua-2-xlm-roberta-large-meetingbank",
        device_map="auto",
        use_llmlingua2=True,
    )
    
    end = time.time()
    
    print(f'dur: {end - start:.2f} sec')
    
  • 這個第一次因為要載 roberta 所以會要比較久,之後的話大概 10 秒內可以完成
  1. call it
  • code
    from llama_index.core.schema import QueryBundle
    import time
    
    start = time.time()
    results = compressor_llmlingua2._postprocess_nodes(
        nodes, query_bundle=QueryBundle(query_str=question)
    )
    end = time.time()
    
    print(f'dur: {end - start:.2f} sec')
    results
    
  • 他的 input 是 list of node 還有原始的 query,output 是精簡過後的 list of node
  • result
    • https://ithelp.ithome.com.tw/upload/images/20250924/201778551NYQzbL08X.jpg
    • 這邊我們看到他把三個 url 的內容壓縮成 1 篇,Metadata 還是留存本來的 3 個 url
    • 精簡的方法是提取式的,有點感覺像是逐 token predict 說這個字要不要留,
    • 整個執行一次是 0.68 sec,測試機器是華碩的天選6 pro,一般的筆電
  1. single node compression
  • code

    single_node_list = [nodes[0]]
    print(single_node_list)
    
    start = time.time()
    results = compressor_llmlingua2._postprocess_nodes(
        single_node_list, query_bundle=QueryBundle(query_str=question)
    )
    end = time.time()
    
    print(f'dur: {end - start:.2f} sec')
    print(results)
    
  • 壓縮前:

    • https://ithelp.ithome.com.tw/upload/images/20250924/20177855O2VAkM8slj.jpg
  • 壓縮後

    • https://ithelp.ithome.com.tw/upload/images/20250924/2017785547RVBkn94e.jpg
  • 約 0.26 sec 跑完

  • 可以看到確實是砍掉了一些無關的內容,不過還是不能直接用

  1. 我們來測一個這種提取式 summary 都會想要測的例子
  • code
    question = '大杯珍珠奶茶微糖微冰的簡稱是什麼?'
    
    nodes = [
        NodeWithScore(
            node=TextNode(
                text='大杯珍珠奶茶微糖微冰',
                metadata={'url': 'url://12345'},
                id_='yoyoyo',
            )
        )
    ]
    start = time.time()
    results = compressor_llmlingua2._postprocess_nodes(
        nodes, query_bundle=QueryBundle(query_str=question)
    )
    end = time.time()
    
    print(results)
    
  • 結果:
    • 他在 initial 的時候其實可以給一個參數是 target_token 用來控制提取後的文本要少於多少字
    • 不過這是控制 maximun 不是 minimun
    • 經過一串測試,target_token=19 的結果是無壓縮全文,target_token=18 的結果是空字串
      • 沒有如預期的給出大奶微微
      • QQ

Summary

  • 我們今天試了一個看起來很猛的方法叫 LLMLingua-2
    • 他確實把我們的兩個需求都考慮到了
      • 想要精簡檢索回來的 chunk
      • 針對大量來源的情況下要考慮到速度以及 cost 問題
        • 每個 url 都逐一 call llm summary 的話,real time 情況下速度會無法接受,如果是 call chat-gpt 荷包也會哭泣
    • 提取式壓縮的話雖然有可能會斷章取義,但是比起 call llm summary 就相對不需要擔心
    • 效果也就差強人意,他整體的重點放在速度(壓縮)而不是精度
      • 這種直接拿來試而且還有 model 的,馬上完全可以用的話應該要去買樂透

其他

  • 剛接觸 RAG 的時候,看了很多切 chunk 調參數的實驗比較,實際切了之後就覺得 chunk size 之類的參數不管怎麼調裡面都是很多無關的內容,真的沒影響嗎,此外也試了基於語意的分割方式,然後沒有真的比較好
    • 然後就把這個問題放一邊不管了
  • 這次的目標是要造 dataset,所以就順道關心一下這個 context 的內文
  • 一開始還沒有找到相關的算法,覺得很疑惑,難道都沒有這個整理 context 的需求嗎,直到發現了 LLMLingua-2
  • 其他做類似的事的在metadata_extraction也有
    • 有人在做類事的事才比較放心自己不是在一個沒意義的問題上鑽牛角尖
  • 今天就先到這,明天來試試土炮的方法

Reference


上一篇
Day8: Tavily 與 FunctionAgent
下一篇
Day10: CitationQueryEngine 與 Workflow
系列文
阿,又是一個RAG14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言