標籤: chunking

  • 用 W-RAC 讓你的 RAG 省錢又好查

    📌 本文重點

    • W-RAC 目標是在不犧牲 RAG 效果下大幅壓低 chunking 成本
    • 先把網頁拆成可定址的小單元,再用 LLM 只做「分組決策」
    • 保留結構化資訊有助於 debug、調優與控制向量庫容量

    一件事先講清楚:W-RAC 的目的,就是在不犧牲 RAG 效果的前提下,把「為了 chunking 花給 LLM 的錢」砍到最低。

    參考論文:Web Retrieval-Aware Chunking (W-RAC)


    為什麼你現在的 chunking 很可能在燒錢

    多數團隊做 RAG,會遇到幾個典型做法:

    • 固定長度切分(例如 500 tokens 一刀)
    • rule-based(照標題、段落、HTML tag 切)
    • 直接丟給 LLM「幫我分合理的 chunk」

    這幾種方法的共通問題:

    1. 重複內容被 embed 很多次
      尤其是網頁側邊欄、導覽列、footer,幾乎每個 chunk 都有一份。

    2. LLM 成本被當 tokenizer 用
      用 LLM 來重寫、分段、產生摘要,等於把整個網站內容吃一遍,token 直接爆掉。

    3. 不好 debug
      一個查詢跑出來一堆 chunk,很難追:

    4. 這個 chunk 為什麼被這樣切?
    5. 是哪個頁面、哪個段落?

    W-RAC 的核心想法:先把網頁拆成結構化「可標號的小單元」,LLM 只負責決定「哪些單元湊成一個 chunk」,完全不改寫原文。

    這樣可以:

    • LLM 只看「結構化 outline + 短內容片段」,token 使用大幅下降
    • chunk 是由原始小單元組合而成,可追蹤來源、可重建原文
    • 重複小單元只存一次,減少向量庫容量

    💡 關鍵: 先抽取可定址小單元,再用 LLM 只做分組規劃,可以在保留 RAG 效果的前提下,把 chunking 相關的 LLM token 成本壓到最低。


    核心功能:W-RAC 在做什麼?

    1. 把網頁拆成「可定址單元」

    具體做法:

    1. 用瀏覽器自動化抓頁面(例如 Playwright)
    2. 保留 DOM 結構,轉成一棵樹:
    3. 節點:標題、段落、表格列、列表項目等
    4. 每個節點給一個 ID(例如 page_123.h2_3.p_2
    5. 把每個節點變成一個「原子單元」,包含:
    6. id
    7. tag(h1/h2/p/li/td…)
    8. text(文字內容)
    9. path(在頁面中的位置)

    你可以實作的步驟:

    pip install playwright beautifulsoup4
    playwright install
    

    用 Playwright 拉 HTML,再用 BeautifulSoup 做 DOM 清洗、節點提取,最後存成 JSON:

    {
      "id": "page_123.h2_3.p_2",
      "tag": "p",
      "path": ["body", "main", "section[2]", "h2", "p[2]"],
      "text": "本方案適用於企業內部知識庫..."
    }
    

    行動:先做「乾淨的節點抽取」,還不要想 embedding 和 LLM,確保每個網頁能拆成穩定、可追蹤的小單元。


    2. 用 LLM 做「分組決策」,而不是改寫文本

    傳統 agent 會:把整頁文字丟進 LLM,請它「改寫 + 切 chunk」。

    W-RAC 則是:

    1. 給 LLM 的不是全文,而是「節點清單 + 結構資訊」:
    2. 節點 ID
    3. 簡短前幾個字(preview)
    4. tag / 標題層級
    5. 要 LLM 回傳的,只是ID 分組規劃,例如:
    [
      {"chunk_id": 1, "node_ids": ["...h2_1", "...h2_1.p_1", "...h2_1.ul_1"]},
      {"chunk_id": 2, "node_ids": ["...h2_2", "...h2_2.p_1"]}
    ]
    

    範例提示詞(可直接改用自己的模型):

    你是一個文件分組器。給你一個網頁節點清單,每個節點有:id、tag、text_preview、heading_level。
    
    目標:
    - 將相關的節點分成多個 chunk
    - 每個 chunk 內容長度約 300–800 字
    - 優先讓同一個小節(同一個 h2/h3 底下)的節點在同一個 chunk
    
    輸出格式:只回傳 JSON 陣列,每個元素包含:
    - chunk_id:整數
    - node_ids:字串陣列
    
    不要產生任何說明文字。
    

    接下來你在程式裡做:

    • 根據 node_ids 把原子單元的 text 串起來
    • 生成真正要 embed 的 chunk 文本

    行動:選一個便宜的小模型(例如本地 LLM 或雲端小模型),先在 1–2 個頁面上跑一輪「ID 分組」,確認輸出格式與 chunk 長度合理,再批次上線。


    3. 保留結構化資訊,提升可觀測性與調優效率

    因為每個 chunk 只是「小單元的組合」:

    • 每個 chunk 知道自己由哪些 node_id 組成
    • 每個 node_id 可以反查 DOM path → 原頁面位置

    你可以做到:

    • 查詢輸出時,在後台顯示:
    • chunk 來源頁面 URL
    • 對應的標題、段落位置
    • 線上觀察「常被命中的 chunk」長什麼樣,是否太長或太短

    簡單的監控策略:

    1. 在 RAG pipeline 中記錄:query / 命中 chunk_id / 來源 page_id
    2. 定期統計:
    3. 哪些頁面 chunk 命中率高但回答不精準 → 調整該頁 chunk 最大長度
    4. 某些 tag(例如 table)被切得太散 → 調整「表格應視為一組」的規則

    行動:把 node_idchunk_id 一起寫入向量庫的 metadata,之後才能在 dashboard 上做查詢與可視化。


    適合誰用:三個典型場景

    1. 公司知識庫 / 文件中心

    情境:

    • 你有 Confluence、Notion、GitBook 或自建 docs 站
    • 想做內部問答助手,但頁面一多,embedding 成本驚人

    W-RAC 的落地方式:

    1. 用 Playwright 把內部 docs 網站轉成 HTML
    2. 用 BeautifulSoup 抓出 h1–h3、段落、列表,做節點抽取
    3. 用小模型做 chunk 分組
    4. 把完成的 chunk 丟進現有向量庫(如 OpenSearch、PGVector、Weaviate)

    效果:

    • 導覽列、側邊欄只存一次,不會每個 chunk 重複
    • 每個問題能對應到具體章節,方便文件 owner 微調內容

    💡 關鍵: 對於大量公司文件,W-RAC 可以避免重複 embed 導覽與樣板內容,顯著降低向量儲存與 embedding 成本。

    2. 官網 FAQ / 產品說明頁

    情境:

    • 產品 FAQ 分散在多個頁面、Accordion、tab 裡
    • 用固定長度切,很容易一個 chunk 內混到不同問題

    W-RAC 做法:

    • 把每個 Q/A 區塊視為一個原子單元
    • LLM 分組時以「一問一答」為最小單位

    好處:

    • 用戶問「退款怎麼算?」時,命中的 chunk 幾乎就是整個退款 FAQ,不會混到完全不相干的條款

    3. 大量公開網頁做 RAG(爬站型應用)

    情境:

    • 你在做垂直搜尋 / 行業資料聚合
    • 動輒幾十萬頁 HTML,要控制成本

    W-RAC 的優點在這裡會放大:

    • LLM 只負責分組計畫,成本可比「全 LLM 分 chunk」少一個數量級(論文的主張)
    • 分組邏輯可重跑:
    • 想換 chunk 長度,只要重新跑 LLM 分組,不必重新爬網

    行動:挑一個實際場景(知識庫、FAQ、或爬站),先對 50–100 個頁面做 W-RAC pipeline,算出:LLM token vs 傳統做法的差異,作為是否全面導入的依據。


    怎麼開始:從技術棧到實作指引

    建議技術棧

    類別 推薦選項 備註
    抓網頁 Playwright / Puppeteer Playwright 對登入、動態頁面支援較好
    HTML 解析 BeautifulSoup / lxml 把 DOM 轉成節點樹、抽取文字與 tag
    分組 LLM 任一小模型(如本地 Qwen/LLama) 只做規劃決策,不改寫文本,成本壓得很低
    向量庫 PGVector / Qdrant / Weaviate 支援 metadata 查詢較重要,以便 debug
    嵌入模型 開源 embedding 或雲端 embedding 固定長度輸入即可,和 W-RAC 本身無強耦合

    行動:先決定你現有的向量庫與 embedding 方案,再把「抓網頁 + W-RAC 分組」當作前處理模組接進去。


    最快上手路徑(簡化版流程)

    1. 抓一頁 HTML
    2. 用 Playwright:

    “`python
    from playwright.sync_api import sync_playwright

    with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto(“https://your-site.com/faq”)
    html = page.content()
    browser.close()
    “`

    1. 用 BeautifulSoup 拆節點
    2. h1–h3pli、table row 都變成節點
    3. 幫每個節點生成 idpath

    4. 呼叫 LLM 做分組

    5. 使用前面提供的提示詞
    6. 控制每次輸入的節點數量(例如 100–200 個),避免 context 太大

    7. 依照 node_id 組 chunk

    8. 按分組結果串 text,加入 metadata(node_idspage_url

    9. 寫入向量庫 + 接到現有 RAG

    10. 查詢時流程不變,只是每個 chunk 背後多了完整來源資訊

    線上觀察 chunk 品質與調優策略

    觀察重點:

    1. 回答不準時,回頭看 chunk
    2. 是否同一個 chunk 裡混了太多主題?
    3. 是否重要上下文被切開?
    4. 調整策略
    5. 提示詞裡明確告訴 LLM:
      • 表格應盡量保持在同一 chunk
      • 同一個 h2 底下不要分太多 chunk
    6. 調整每個 chunk 的目標字數(例如從 800 改成 500)

    行動:在你的 RAG 後台加一個「檢視來源 chunk」按鈕,點下去就顯示該 chunk 的 node 列表與原頁面位置,方便你和 PM / 內容 owner 一起 review。


    小結:把 LLM 用在刀口上

    W-RAC 的重點不是多厲害,而是很務實:

    • LLM 只做「分組規劃」這種高價值決策
    • 文本本身盡量保持原樣,避免幻覺與重寫成本
    • 一旦你有了「結構化可定址單元」,後面調優都變簡單

    如果你正在為 RAG 成本與效果卡關,先不要換模型,先把 chunking 換成類似 W-RAC 的流程,很可能就能省下一大筆 embedding/LLM 費用,還順便讓系統更好 debug。

    🚀 你現在可以做的事

    • 選一個實際頁面,用 Playwright + BeautifulSoup 做出「乾淨節點抽取」JSON
    • 用一個便宜的小模型,照文中提示詞跑一輪「ID 分組」,實際組出 chunk 並寫入向量庫
    • 在你的 RAG 後台加上 chunk_id / node_id 的 metadata 顯示與檢視來源 chunk 按鈕,開始觀察與調整 chunk 策略