iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
AI 自動化

Data Machi 30 天學習系列:從 Chat 到 Product,打造真正能工作的企業 AI系列 第 8

Day 07|一份 PDF,怎麼變成 AI 可以搜尋的知識?

  • 分享至 

  • xImage
  •  

昨天我們把 RAG 理解成「回答前先查文件」,但真正開始做時,很快就會遇到下一個問題:PDF 本身並不是一個適合直接搜尋的知識庫。

即使人類可以打開一份 80 頁報告慢慢看,系統也不能每次收到問題,就把整份文件全部塞進模型。

因此,一份文件要變成 AI 可以搜尋的知識,通常會經過幾個步驟:

PDF
 ↓
Parse
解析文件內容
 ↓
Chunk
切成較小的文件片段
 ↓
Embedding
轉換成向量
 ↓
Index
建立可搜尋的索引
 ↓
Retrieval
找出與問題最相關的內容

RAG 的品質,往往不只取決於最後使用哪一個大型語言模型,而是在這些前處理步驟中,就已經決定了一大半。

第一步:解析(Parse),把文件內容取出來

PDF 看起來像一份文件,但它的內部可能包含:

  • 一般文字
  • 圖片
  • 表格
  • 雙欄排版
  • 掃描頁面
  • 頁首頁尾
  • 特殊字型或編碼

解析器(Parser)的工作,就是盡可能把其中的文字與結構抽取出來,讓後面的系統可以繼續處理。

對純文字 PDF 來說,這件事相對簡單;但如果文件包含雙欄排版、複雜表格或大量圖片,解析結果就可能出現順序錯亂。

例如原始文件可能是:

退款期限:商品購買後 30 天內
退款條件:需保留原始發票

但解析錯誤後,可能變成:

退款期限:
退款條件:
商品購買後
需保留原始發票
30 天內

對人來說可能還猜得出意思,但對後面的搜尋與模型而言,文件語意已經開始被破壞。

這也是為什麼不能把:

「成功讀到 PDF」等同於「成功理解 PDF」。

後面真正動手實作時,第一件事不應該是立刻問模型問題,而是先檢查解析後的文字是否合理。

第二步:文件切割(Chunking),把長文件切成可搜尋片段

假設一份文件有幾萬字,直接把整份內容做成一個向量幾乎沒有意義。

系統需要把內容切成較小的文件片段(Chunk),讓每一個 Chunk 代表一段相對完整的語意。

例如原始文件:

員工差旅管理辦法

第一條:國內差旅住宿費每日上限為 3,000 元。

第二條:海外差旅住宿費依各國家與城市標準計算。

第三條:交通費需提供相關付款證明。

系統可能把它切成:

Chunk 1
第一條:國內差旅住宿費每日上限為 3,000 元。

Chunk 2
第二條:海外差旅住宿費依各國家與城市標準計算。

Chunk 3
第三條:交通費需提供相關付款證明。

這樣當使用者詢問:

「國內出差住宿可以報多少?」

系統就不需要搜尋整份文件,而是找出最可能包含答案的 Chunk。

但 Chunk 並不是越小越好。

Chunk 太大會發生什麼?

如果一個 Chunk 包含很多不同主題,搜尋回來的內容可能混入大量不相關資訊。

例如:

Chunk 1
住宿規範 + 交通規範 + 餐費規範 + 海外差旅規範 + 請假規範

即使搜尋成功找到這一塊,模型仍然需要從很多資訊裡判斷哪一段才是真正的答案。

Chunk 太小又會發生什麼?

另一個極端是切得太細:

Chunk 1:國內差旅
Chunk 2:住宿費
Chunk 3:每日上限
Chunk 4:3,000 元

這時雖然每一塊都很小,但完整語意也被拆散了。

因此 Chunking 真正要解決的問題是:

如何讓每一個文件片段夠小,可以精準搜尋;又夠完整,可以保留原本的語意。

為什麼還需要 Overlap?

文件切割時,另一個常見概念叫做 Overlap(重疊區段)

假設文件內容是:

員工申請海外差旅時,需要提前取得主管核准。
若住宿費超過當地標準,還需要取得部門主管的額外批准。

如果剛好從兩句中間切開:

Chunk 1
員工申請海外差旅時,需要提前取得主管核准。

Chunk 2
若住宿費超過當地標準,還需要取得部門主管的額外批准。

第二個 Chunk 中的「若」其實依賴前面的海外差旅情境。

因此實務上常讓相鄰 Chunk 保留一小段相同內容:

Chunk 1
員工申請海外差旅時,需要提前取得主管核准。

Chunk 2
員工申請海外差旅時,需要提前取得主管核准。
若住宿費超過當地標準,還需要取得部門主管的額外批准。

這就是 Overlap 的概念。

它的目的不是單純製造重複資料,而是降低重要語意剛好被切在邊界上的風險。

第三步:Embedding,把文字轉成可比較的向量

文件切成 Chunks 之後,下一步就是 Embedding。

Embedding 的目的,是把文字轉換成一組數字,讓系統可以用數學方式比較兩段文字的語意是否相近

可以先不用管向量裡到底有幾百或幾千個數字,只要理解一個核心概念:

意思越接近的文字,在向量空間中的位置通常也會越接近。

例如文件中寫的是:

「顧客可於商品購買後 30 日內辦理退貨。」

使用者詢問:

「商品可以在幾天內退?」

兩邊沒有使用完全一樣的文字。

傳統只比對關鍵字的搜尋方式,可能因為「退貨」與「退」不同而漏掉結果;但 Embedding 會嘗試把文字轉換成語意表示,因此有機會判斷這兩段內容其實在討論相同的事情。

可以把它想成:

「商品可以在幾天內退?」
              ↓
          Embedding
              ↓
      [0.12, -0.47, 0.83, ...]


「商品購買後 30 日內可以退貨」
              ↓
          Embedding
              ↓
      [0.15, -0.43, 0.79, ...]

真正的向量會複雜得多,但我們現在不需要理解其中每個數字代表什麼。

只需要知道:

Embedding 讓系統從「文字長得像不像」,進一步比較「意思像不像」。

第四步:Index,讓查詢可以快速找到內容

當所有 Chunks 都完成 Embedding 後,系統會把這些向量,以及對應的原始文字與來源資訊保存起來。這個可供查詢的結構,就是 Index(索引)。因此,每次收到新問題時,問題本身也會被轉成向量,再與文件 chunks 比較相似度,找出最相關的幾段。
https://ithelp.ithome.com.tw/upload/images/20260819/20169646tH0ubcVLTP.png

可以想像成:

Chunk 001
文字:國內住宿費每日上限 3,000 元
來源:travel_policy.pdf
頁數:Page 3
向量:[......]

Chunk 002
文字:海外住宿費依各國家標準計算
來源:travel_policy.pdf
頁數:Page 3
向量:[......]

Chunk 003
文字:交通費需提供付款證明
來源:travel_policy.pdf
頁數:Page 4
向量:[......]

當使用者提出新的問題:

「國內出差住宿最多可以報多少?」

這個問題也會先被轉換成 Embedding。

接著系統比較:

Question Embedding
        ↓
與 Index 中的文件向量比較
        ↓
計算相似度
        ↓
找出最相關的 Chunks

最後可能找到:

Top 1
國內住宿費每日上限 3,000 元

Top 2
海外住宿費依各國家標準計算

Top 3
交通費需提供付款證明

其中排名最高的內容,就會成為後續交給大型語言模型的重要 Context。

因此完整流程其實可以拆成兩個階段。

建立知識庫

PDF
 ↓
Parse
 ↓
Chunk
 ↓
Embedding
 ↓
Index

使用知識庫

使用者問題
 ↓
Question Embedding
 ↓
搜尋 Index
 ↓
Relevant Chunks
 ↓
加入 Prompt Context
 ↓
LLM
 ↓
回答

到這裡,前一天介紹的 RAG 流程就完整串起來了。

Data Machi 實作前先注意一件事

很多 RAG 問題最後都會被誤判成:

「是不是模型回答能力不好?」

但真正的原因可能根本不在模型。

例如:

  • PDF 根本沒有正確解析
  • Chunk 切得不合理
  • Embedding 沒有正確建立
  • Index 裡還留著舊文件
  • 搜尋階段沒有把正確段落找回來
  • 找到了正確段落,但排序太後面沒有送進模型

因此,除錯 RAG 時建議遵守一個非常重要的順序:

先看 Retrieval
        ↓
確認正確資料有沒有被找到
        ↓
再看 Generation
        ↓
確認模型有沒有正確使用資料

這個順序很重要。

假設使用者問:

「公司的國內住宿費上限是多少?」

正確答案明明在文件中,但 Retrieval 找回來的是:

Chunk 1:交通費規範
Chunk 2:海外住宿規範
Chunk 3:員工請假規範

這時候即使換成能力更強的 LLM,也解決不了真正的問題。

因為:

模型從一開始就沒有拿到正確資料。

如果這時一直修改 Prompt,只是在試圖讓模型更漂亮地回答錯誤的 Context。

進階理解|Chunking 沒有一組萬用參數

開始實作 RAG 後,你很容易在網路上看到這類建議:

Chunk Size = 500
Overlap = 100

或:

Chunk Size = 1000
Overlap = 200

但真正重要的不是背下某一組數字。

Chunking 最重要的是理解片段大小與重疊區段之間的取捨

可以把 Chunk 想成「把一本長文件切成一張張可以搜尋的小卡片」。

如果切太大:

一張卡片裡會混入太多不同資訊。

如果切太小:

同一條規則的前因後果可能被拆開。

Overlap 則像是讓相鄰卡片保留少量共同內容,避免重要資訊剛好被剪成兩半。

更重要的是,不同文件本來就可能適合不同的切割方式。

FAQ

FAQ 通常天然就有:

Question
+
Answer

因此可以考慮以「一問一答」作為一個 Chunk。

法律條款或政策文件

這類內容往往需要保留:

條款名稱
+
適用條件
+
例外
+
完整規範

如果切得太細,很容易失去上下文。

操作手冊

操作文件則可能比較適合按照:

章節
→ 標題
→ 操作步驟

進行切割,而不是只按照固定字數。

因此真正的做法不是問:

「RAG 最佳 Chunk Size 是多少?」

而是問:

「針對我的真實問題,正確的文件片段能不能被搜尋回來?」

這也是後面我們設計 RAG 測試時真正需要驗證的事情。

延伸閱讀|知識庫也需要維運

知識庫並不是建立一次之後就永遠不用管理。

假設公司原本的政策是:

國內住宿費上限:3,000 元

半年後改成:

國內住宿費上限:3,500 元

如果只是把新版文件加入知識庫,卻沒有刪除舊版本,搜尋時就可能同時得到:

舊版:3,000 元
新版:3,500 元

模型這時就必須面對互相衝突的證據。

除此之外,還有幾個實務上很常見的問題:

  • 文件更新後,舊向量是否有同步更新?
  • 文件刪除後,對應的 Chunks 是否一起刪除?
  • 同一份文件是否被重複建立索引?
  • 是否保存文件版本或更新日期?
  • Server 重新部署後,Index 是否仍然存在?

尤其最後一點很容易在 Demo 階段被忽略。

如果 Index 只存在伺服器的暫存空間裡:

建立索引
   ↓
RAG 可以正常搜尋
   ↓
重新部署 Server
   ↓
暫存資料消失
   ↓
知識庫也跟著消失

這提醒我們:

建立索引只是知識庫的開始,版本、更新、刪除與持久化才是長期維運的一部分。

這些問題我們不需要在今天一次全部解決,但之後當 Data Machi 從 Demo 走向真正可以長期使用的系統時,就會再次遇到它們。


今天的重點:
RAG 不是把 PDF 丟給 AI 就結束,而是一條 Parse → Chunk → Embedding → Index → Retrieval 的資料流程。

下一篇,我們會更深入看 Retrieval:為什麼傳統關鍵字搜尋和語意搜尋各有優缺點,以及什麼時候應該混合使用。

我們下集見囉!


上一篇
Day 06|什麼是檢索增強生成(RAG)?讓 AI 從閉卷考試變成開卷考試
系列文
Data Machi 30 天學習系列:從 Chat 到 Product,打造真正能工作的企業 AI8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言