iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
生成式 AI

阿,又是一個RAG系列 第 19

Day18: structured output challenge

  • 分享至 

  • xImage
  •  

Intro

  • 我們昨天產出了一份 toy problem:= structured_output_dataset.json
  • 任務描述:
    • 比賽共計 80 題
    • input 是一個 python 的 string
      • 範例: "3.依《靈樞.經脈》所記載:「是主筋所生病者,痔、瘧、狂、癲疾、頭顖、項痛,目黃、淚出,鼽\n衄,項、背、腰、尻、膕、腨、腳皆痛,小趾不用」,指何經的病證內容?\n \nA.膀胱經\nB.膽經\nC.三焦經\nD.脾經"
    • 期望的 output 是一個 json
      • 範例:
      "qid": "3",
      "stem": "依《靈樞.經脈》記載,\"其直者,從巔入絡腦,還出別下項,循肩膊內,挾脊抵腰中\",\n指下列\n何經的循行內容?\n\n"
      "A": "膀胱經",
      "B": "膽經",
      "C": "胃經",
      "D": "肝經",
      
    • 同時我們會給予模型我們期望的輸出欄位
      class MCQ(BaseModel):
          """單選題結構,包含題號(qid)、題幹(stem)、以及 A、B、C、D 四個選項"""
          qid: int = Field(..., description='題號')
          stem: str = Field(..., description='題幹')
          A: str = Field(..., description="本題的A選項")
          B: str = Field(..., description="本題的B選項")
          C: str = Field(..., description="本題的C選項")
          D: str = Field(..., description="本題的D選項")
          ans: Optional[str] = Field(default=None, description='答案')
      
    • 參賽選手包含:
      • gemma3:12b
      • llama3.1:8b
      • 此外我們會視戰況新增魔王:
        • gpt-5-mini, gpt-4.1, gpt-oss:20b
    • 或是調整資料集難度:
      • 例如:截斷 或是 混入前/後文
  • 這場比賽究竟鹿死誰手,讓我們繼續看下去...

Goal

  • 我們今天主要的任務就是先把 llm 的 predict 跑出來,流程如下:
    • 我們會先進行一輪簡易的實驗,簡易測試基本的性能,用來決定這個實驗值不值得跑
    • 有了第一輪的經驗後,我們會選定要跑完 80 題測試的選手,並且把預測結果跑出來

Deliverables

Key Insights / Pitfalls

  • 我們接下來都會用簡稱來稱呼對應模型,不再叫全名,因為這樣比較親切
    • gemma3:12b : 我們叫他 gemma
    • llama3.1:8b : 我們叫他 llama
    • gemma3:12b 開啟 json_mode: 我們叫他 json_gemma
    • gpt-5-mini: 我們叫他 mini

簡易測試篇

  • 首先針對 llama,我們直接調用 llama-index 封裝的 structured LLMs

    • sllama=llama.as_structured_llm(Pydantic)
    • 回答如下:
    {'qid': 1,
     'stem': '答案是 C',
     'A': '上星、日月',
     'B': '合谷、太衝',
     'C': '內關、外關',
     'D': '上關、下關',
     'ans': None}
    
    • 這個看就知道不穩
  • 接著,我們調用 llama-index 封裝的 structured_predict

    • response = llama.structured_predict(Pydantic, prompt, text=query)
    • 這個就是在 structured LLMs 的基礎上可以讓我們進一步客製 prompt
      • structured LLMs 底下也是調用 structured_predict
    • prompt 部分,我們直接把範例的英文改成我們的 case
    • 對照如下:
      • 範例: "Extract an MCQ from the following text. If you cannot find an answer, use the default value None and the date as the invoice ID: {text}"
      • prompt_en_llama: "Extract a multiple-choice question (MCQ) from the following text. If the original text does not provide an answer, omit the answer field entirely and do not attempt to guess it: {text}"
    • 回覆結果:
    {'qid': 1,
     'stem': '',
     'A': '上星、日月',
     'B': '合谷、太衝',
     'C': '內關、外關',
     'D': '上關、下關',
     'ans': None}
    
    • 看來還是不太行
  • 下一步,我們把 prompt 從本來的英文,要求 chatgpt 翻譯為中文

    • prompt_zh_llama: "從以下文字中擷取一題選擇題 (MCQ)。如果原始文字沒有提供答案,則完全省略答案欄位,且不要嘗試推測答案:{text}"
    • 回覆結果:
    {'qid': 1,
     'stem': '常見針灸配穴法中,所指的「四關穴」,為下列何穴位之組合?',
     'A': '上星、日月',
     'B': '合谷、太衝',
     'C': '內關、外關',
     'D': '上關、下關',
     'ans': None}
    
    • 看起來成長了不少,難道這驗證了江湖傳聞,直接套用框架的範例 prompt 其實效果都很差嗎?
  • 此外,我們還有請 chatgpt 幫我們寫了一份落落長的 prompt := prompt_gpt_llama
    - 點我看
    - 大致上就是:角色扮演、嚴格規則、Few-shot、反覆強調

  • 鏡頭一轉我們把目光聚焦在 gemma 上

    • 前天有提到 gemma 哥本身是不支援 tool_calling 的,但我們還是可以直接 prompt 他,他的 prompt 如下:
    prompt_gemma = PromptTemplate(
        "這是 MCQ 的 JSON schema:\n"
        f"{schema}\n"
        "從以下文字中擷取一題選擇題 (MCQ)。如果原始文字沒有提供答案,則完全省略答案欄位,且不要嘗試推測答案\n\n以下開始:\n"
        "-----\n"
        "{text}\n"
        "-----\n"
        "結果:\n"
    )
    
    • 基本上就是prompt_zh_llama人工加上 schema
    • 這是他的回覆:
    \```json
    {
      "qid": 1,
      "stem": "常見針灸配穴法中,所指的「四關穴」,為下列何穴位之組合?",
      "A": "上星、日月",
      "B": "合谷、太衝",
      "C": "內關、外關",
      "D": "上關、下關"
    }
    \```
    
    • 除了不知道會不會哪天輸出個沒辦法 parser 的 json 以外,基本上也沒什麼問題
  • 然後我們把json_mode開起來,呼叫 json_gemma

    • 回覆:
    {
      "qid": 1,
      "stem": "常見針灸配穴法中,所指的「四關穴」,為下列何穴位之組合?",
      "A": "上星、日月",
      "B": "合谷、太衝",
      "C": "內關、外關",
      "D": "上關、下關"
    }
    
    • 哦,讚
    • 這邊有個小坑:
      • 在 llama-index 中,如果是呼叫 ollama 的 llm,是可以直接開 json_mode 的
        • llm = Ollama(model=model_name, json_mode=json_mode)
        • 如果你用: additional_kwargs={'response_format': {"type": "json_object"}})
          • 這個不 work
      • 但如果是 openai 的 llm 情況會反過來,要用 additional_kwargs
  • 到這邊我們對實驗結果大概有了一些預期

  • 上面所有的程式碼都可以在 notebook 找的到

實驗設置與規則

  • 我們會一題一題的呼叫我們的各選手,並且把答案存出來

    • 設置 200 秒不回或者有任何中間 error 的話這題就直接判錯
      • 這個 error 包括 json parser 不了
    • 共計 80 題
  • 選手清單:

    • 選手1: llama + prompt_en
    • 選手2: llama + prompt_zh
    • 選手3: llama + prompt_gpt
    • 選手4: json_gemma + prompt_gemma
    • 選手5: gemma + prompt_gemma
  • 想法:

    • 在組 1~3 我們主要是想要看一下
      • 究竟是不是直接用 default prompt 就好了,不用在那邊改來改去
      • 簡單的把本來的英文 prompt 直接翻譯成中文,對中文任務有沒有幫助
      • 落落長的 prompt ,包含角色扮演、嚴格規則、Few-shot、重複強調,究竟又可以提升多少
    • 組 4~5 就是趁這個機會來驗一下 json_mode 是不是真的比較差

Quick Results

  • 測試環境為筆電的 5070

  • 目前跑完大致看到的結果如下:

  • llama + prompt_en: 因為 Timeout 直接 miss 了 7 題

    • 這告訴我們簡易好上手的 ollama 其實有時候也不太穩定
    • 這組應該會是表現最差的,本來就沒什麼期待
      • 主要是後面做 llm-as-a-judge 的時候想要有一些 bad case 這樣才驗的了 judge 有沒有做事
    • 80 題的運行時間為 1137.87 sec
  • llama + prompt_zh: 同樣因為 Timeout miss 了 7 題

    • 但是 miss 的題號跟選手1的不一樣,應該也是 ollama 的關係
    • 80 題的運行時間為 1096.81 sec
  • llama + prompt_gpt: 這個 prompt 最長,卻沒有任何miss

    • 80題的運行時間為: 355.10 sec
      • 沒有打錯,後續在查一下 ollama 的 log 看看是不是有什麼誤會
  • json_gemma + prompt_gemma:

    • 沒有 miss
    • 80 題 814.24 sec
  • gemma + prompt_gemma:

    • 沒有 miss
    • 80 題 798.14 sec
    • 但我還沒開始 parse json
  • predict: 來人工看結果

  • 今天就這樣啦,這場比賽究竟鹿死誰手讓我們繼續看下去

Plan for Tomorrow

  • 現在我們有了 ground-truth, context, response,明天打算學一下 llm-as-a-judge 的基本方法

其他

  • 選手的排序就是我預期的最差到最好,話講完,誰反對!

上一篇
Day17: exam_and_structured_output_dataset
下一篇
Day19: evaluator in llama-index
系列文
阿,又是一個RAG20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言