📌 本文重點
- 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」
這幾種方法的共通問題:
-
重複內容被 embed 很多次
尤其是網頁側邊欄、導覽列、footer,幾乎每個 chunk 都有一份。 -
LLM 成本被當 tokenizer 用
用 LLM 來重寫、分段、產生摘要,等於把整個網站內容吃一遍,token 直接爆掉。 -
不好 debug
一個查詢跑出來一堆 chunk,很難追: - 這個 chunk 為什麼被這樣切?
- 是哪個頁面、哪個段落?
W-RAC 的核心想法:先把網頁拆成結構化「可標號的小單元」,LLM 只負責決定「哪些單元湊成一個 chunk」,完全不改寫原文。
這樣可以:
- LLM 只看「結構化 outline + 短內容片段」,token 使用大幅下降
- chunk 是由原始小單元組合而成,可追蹤來源、可重建原文
- 重複小單元只存一次,減少向量庫容量
💡 關鍵: 先抽取可定址小單元,再用 LLM 只做分組規劃,可以在保留 RAG 效果的前提下,把 chunking 相關的 LLM token 成本壓到最低。
核心功能:W-RAC 在做什麼?
1. 把網頁拆成「可定址單元」
具體做法:
- 用瀏覽器自動化抓頁面(例如 Playwright)
- 保留 DOM 結構,轉成一棵樹:
- 節點:標題、段落、表格列、列表項目等
- 每個節點給一個 ID(例如
page_123.h2_3.p_2) - 把每個節點變成一個「原子單元」,包含:
idtag(h1/h2/p/li/td…)text(文字內容)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 則是:
- 給 LLM 的不是全文,而是「節點清單 + 結構資訊」:
- 節點 ID
- 簡短前幾個字(preview)
tag/ 標題層級- 要 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」長什麼樣,是否太長或太短
簡單的監控策略:
- 在 RAG pipeline 中記錄:
query/ 命中chunk_id/ 來源page_id - 定期統計:
- 哪些頁面 chunk 命中率高但回答不精準 → 調整該頁 chunk 最大長度
- 某些 tag(例如 table)被切得太散 → 調整「表格應視為一組」的規則
行動:把
node_id和chunk_id一起寫入向量庫的 metadata,之後才能在 dashboard 上做查詢與可視化。
適合誰用:三個典型場景
1. 公司知識庫 / 文件中心
情境:
- 你有 Confluence、Notion、GitBook 或自建 docs 站
- 想做內部問答助手,但頁面一多,embedding 成本驚人
W-RAC 的落地方式:
- 用 Playwright 把內部 docs 網站轉成 HTML
- 用 BeautifulSoup 抓出
h1–h3、段落、列表,做節點抽取 - 用小模型做 chunk 分組
- 把完成的 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 分組」當作前處理模組接進去。
最快上手路徑(簡化版流程)
- 抓一頁 HTML
- 用 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()
“`
- 用 BeautifulSoup 拆節點
- 把
h1–h3、p、li、table row 都變成節點 -
幫每個節點生成
id與path -
呼叫 LLM 做分組
- 使用前面提供的提示詞
-
控制每次輸入的節點數量(例如 100–200 個),避免 context 太大
-
依照
node_id組 chunk -
按分組結果串
text,加入 metadata(node_ids、page_url) -
寫入向量庫 + 接到現有 RAG
- 查詢時流程不變,只是每個 chunk 背後多了完整來源資訊
線上觀察 chunk 品質與調優策略
觀察重點:
- 回答不準時,回頭看 chunk:
- 是否同一個 chunk 裡混了太多主題?
- 是否重要上下文被切開?
- 調整策略:
- 提示詞裡明確告訴 LLM:
- 表格應盡量保持在同一 chunk
- 同一個 h2 底下不要分太多 chunk
- 調整每個 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 策略
