LlamaIndex 的 Pierre-Loïc Doulcet 在 AI Engineer Singapore 2026 帶了一場 90 分鐘的工作坊《Beyond RAG: Building Agentic Document Workflows with LlamaIndex》,投影片全長 116 頁,由 LlamaIndex 共同創辦人 Jerry Liu 在 X 上公開: 投影片連結在這

小編讀完覺得是近期少見的一份完整 RAG 知識整理。它不是產品簡報,而是把 2024 年那份廣為流傳的「12 個 RAG 痛點」清單拿出來,一條一條檢查兩年後各自變成什麼樣子: 哪些已經是例行工程,哪些其實根本不是原本以為的那個問題。

先介紹一下 LlamaIndex 這家公司,因為它的處境跟這份材料的觀點直接相關。它 2023 年以開源框架起家 (最早叫 GPT Index),是那一批 LLM 應用框架裡跟 LangChain 齊名的兩家之一,核心 repo 目前有 51k star。有意思的是這兩家後來走向不同: LangChain 往 agent 編排發展,LlamaIndex 反而把主力收窄到文件解析,現在官網的自我描述直接寫成「給 AI agent 用的文件理解與 OCR 平台」,產品線是 LlamaParse (雲端)、LiteParse (開源、本機跑)、LlamaExtract (schema 抽取)。

小編會持續關注他們,是因為在 RAG 這件事上,他們是少數還把它當主業在做、而且願意公開方法論和評測資料的廠商: 這份 116 頁投影片是一例,2026 年那份 ParseBench 把資料集和評測程式全部開源是另一例。當然他們終究是廠商,榜首是自家產品,這點文章後面談 ParseBench 時會再提。

Jerry Liu 自己在推文裡給的框架也蠻坦白的: 這份材料同時解釋了「為什麼 LlamaIndex 今天的核心聚焦,會窄化到專門做給 agent 用的文件解析」。換句話說,這份投影片的結論 (瓶頸在文件解析) 跟他們公司的產品重心是同一個判斷,看的時候可以把這件事放在心上。

投影片開場有一頁 Disclaimer 寫得很誠實:

到 2027 年,這裡幾乎所有東西都會是錯的。這個技術堆疊裡「最佳實務」的半衰期不到一年。所以接下來的內容,請當成一套「判斷問題出在 pipeline 哪一層」的思路來看,而不是一份照抄就能用的做法清單。

然後是全篇的論點。先說一下 RAG 的處理順序: 解析文件 → 切成 chunk → 建索引 → 檢索 → 排序 → 交給模型生成,模型在最後面。下面說的「上游」就是指模型之前的那幾個階段。

瓶頸是檢索,不是生成。 檢索裡面,瓶頸是解析 (parsing)。 解析裡面,瓶頸是文件結構被丟掉了。 大部分的調校功夫都花在模型這一層,但真正能拿到的改善,多半在更前面的解析、切塊、檢索那幾層。

這三句是一層一層往前收斂的: 先說問題不在生成而在檢索,再說檢索的問題其實出在解析,最後說解析的問題出在文件結構被丟掉。所以雖然「上游」涵蓋模型之前的所有階段,但一路推到底,最深的那個瓶頸是 parsing。整份投影片就是在論證這條推導。

小編這篇就當導讀,把整份投影片按主題重新組織過,並補上一些脈絡。以下是重點整理:

1. 2024 年的 12 個痛點: 一份到現在還好用的失敗詞彙表

這份清單來自 Wenqi Glantz 2024 年在 Towards Data Science 的 12 RAG Pain Points and Proposed Solutions,其中 7 個出自 Barnett 等人的論文 Seven Failure Points When Engineering a Retrieval Augmented Generation System,另外 5 個來自實務。

投影片給這份清單的定位蠻準的: 它今天的價值不在那些解法,而在當詞彙表用。能把失敗模式叫出名字,除錯就完成一半了。

# 痛點 2026 的解法
1 內容缺失: 文件裡根本沒有答案,模型還是照答 檢索 / 合成 agentic loop + HyDE
2 該撈的沒撈到: 對的 chunk 排在第 11 名,但只取前 10 名 檢索 reranking
3 資訊沒進 context: 答案分散在 5 個 chunk,收攏進 prompt 時只留下 3 個 檢索 撈廣一點 + cross-encoder rerank
4 沒抽取出來: 對的 chunk 就在 prompt 裡,模型還是答錯 合成 / 解析 spatial text 解析
5 格式不對: 要 JSON,拿回來一段散文 合成 schema-first 抽取
6 粒度不對: chunk 切得太細或太粗,跟問題想要的資訊量對不上 檢索 多粒度索引 + 路由
7 答不完整: 多段式問題只答了第一段 檢索 子問題拆解
8 資料匯入的規模: 100K PDF 重新索引一次要 36 小時、約 500 美金 規模 增量匯入 + 內容雜湊去重
9 結構化資料問答: 答案是一個數字,向量檢索只給你關於那個數字的散文 檢索 / 結構 SQL + vector router
10 複雜 PDF: 多欄、註腳、跨頁表格、圖表、掃描件 解析 LiteParse / LlamaParse
11 備援模型: 主模型被限流,備援模型對同一個 prompt 回垃圾 維運 gateway 路由 + CI 強制走備援路徑
12 安全與隱私: 檢索到的內容是不可信輸入 維運 內容淨化 + PII 標記

幾個值得單獨展開的:

痛點 3 的原名是「合併策略的限制」(consolidation strategy limitations)。 這裡的「合併」指的是檢索完、送進 prompt 之前的那道手續: 撈回來一堆 chunk,但 context 塞不下全部,所以要排序、去重、截斷,收攏成最終要給模型看的那份內容。

投影片的例子是撈回 30 個 chunk,答案分散在其中 5 個 (Q1 到 Q4 的營收,加上全年合計),reranker 把 3 個推到前面,另外 2 個在截斷時被丟掉。模型只看到 Q1 到 Q3,就自己加總回答,少了兩季。問題不在檢索沒撈到,而在收攏那一步把該留的丟了。

痛點 4 特別容易被誤判。 投影片的例子是問「DBS 2024 年的美元客戶存款是多少?」,檢索完全正確,對的那一頁就在 prompt 裡,但表格被解析成一行文字:

In $ millions 2025 2024 Singapore dollar 230,646 204,704
US dollar 241,063 223,732 Hong Kong dollar 32,181 33,464 …

模型回答 241,063,因為那是緊接在「US dollar」後面的數字。正確答案是 223,732 (2024 那一欄)。對的頁、錯的欄。這種錯誤在 log 上看起來完全像是「模型不夠聰明」,換模型不會好。

痛點 6 的兩難描述得很精準。 語料庫只會被切一次,但問題的粒度不固定,從「一個數字」到「整份報告」都有。

切成句子: 問「FY24 每股股利是多少」剛好命中那一句; 但問「總結 FY24 表現」就撈回 20 個互不相關的句子,摘要讀起來像一串沒關聯的事實。切成章節則反過來: 問一個數字會撈回兩頁的管理階層討論與分析 (MD&A,年報裡那種長篇營運說明章節),那個數字只是裡面 200 個事實之一,模型會取個平均而不是把它引出來。

投影片說得很直接: 團隊挑一個 chunk size (512 token、recursive splitter) 就上線,問題分布裡被犧牲的那一半,就成了系統無聲的失敗模式。

2. parsing 只佔 1/12,卻讓其中 7 個跟著壞

這張圖是整份投影片的關鍵。12 個痛點裡,歸類在 parsing 的只有 1 個 (複雜 PDF),但它的錯誤會一路往下污染: 解析 → 檢索 (5 個痛點) → 合成 (2 個痛點)。右邊那三組 (結構化問答、規模、維運) 則是相鄰但不會被連累的問題。

投影片的結論: parsing 只佔 12 個裡的 1 個,它的錯誤卻會波及其他 7 個。而且很容易把 parsing 的失敗誤判成檢索或合成的毛病。

3. 2024 年的修法,幾乎都修錯了層

當年大家在做的事: 改 prompt、加更多 few-shot、調 top-k、調 chunk size。投影片一句話總結: 這些全都把合成階段當成要修的東西,而對大部分痛點來說那是錯的一層。

配合這個判斷,投影片給了一張時間分配的對照,小編覺得這張最值得貼在牆上:

這一週該怎麼花
2 天 · 解析 + chunking
2 天 · 檢索 + reranking
1 天 · 合成的 prompt
0 天 · 模型
大部分團隊實際上怎麼花
0 天 · 解析
1 天 · 檢索
2 天 · 合成的 prompt
2 天 · 換模型

投影片同時給了一個數字支撐: 樸素的 RAG (指沒有 rerank、沒有迴圈,檢索完就直接丟給模型生成的最基本作法) 大約有 40% 的時候在檢索這一步就錯了,而 LLM 會很有自信地根據錯的文件回答。兩年前這還是個論點,現在它就是事實。

4. 投資報酬率第一名: cross-encoder reranking

投影片給它的定位是「單一改動裡效果最大的一個」,建議也很明確: 原型階段可以跳過,真實使用者開始看到輸出的那一刻就加上去。

差別在模型類別,不在更好的 embedding:

  • bi-encoder (就是一般的向量搜尋): 分別把 query 和文件編碼成向量再算餘弦相似度。文件向量可以預先算好,所以 1000 萬份文件撈 50 筆大概 5 毫秒。代價是它從來沒有同時看過 query 和文件。
  • cross-encoder (reranking): 把 query 和文件串在一起丟進 transformer,做完整的注意力計算。它能建模否定、範圍、限定詞這類互動關係。代價是慢,所以只能用在前 50 筆候選上,50 對打分約 100 毫秒。

兩階段串起來就是: 檢索負責召回率 (recall),reranking 負責精確率 (precision)。這兩種模型不是彼此的替代品。

實測的提升幅度典型是 nDCG@10 增加 4 到 14 分。nDCG@10 是檢索領域最常用的排序品質指標,衡量「前 10 筆結果裡,相關的有沒有排在前面」,滿分 100。

投影片引用了兩組數字。一組來自 sentence-transformers 在 BEIR (一套涵蓋多個領域的標準檢索評測資料集) 上的測試: 最大的 cross-encoder 平均勝過最好的 bi-encoder 約 4 分,單一資料集上最多 12 分 (gooaq-dev 從 59.12 到 71.35)。另一組是 Voyage rerank-2.5 在 93 個資料集上比 Cohere rerank-v3.5 高 7.9%。

在 LlamaIndex 裡它就是一個 NodePostprocessor,撈廣一點再切回來:

from llama_index.postprocessor.cohere_rerank import CohereRerank

reranker = CohereRerank(model="rerank-v3.5", top_n=5)   # rerank 後保留 5 筆

query_engine = index.as_query_engine(
    similarity_top_k=50,              # 先撈廣
    node_postprocessors=[reranker],
)

可以直接替換的選項還有本機跑的 SentenceTransformerRerank (例如 ms-marco-MiniLM-L-6-v2)、VoyageAIRerank,以及 ColbertRerank。ColBERT 走的是介於兩者之間的第三條路,叫做延遲互動 (late interaction): 它跟 bi-encoder 一樣可以預先算好文件端的向量,但保留每個 token 各自的向量,而不是收斂成單一一個,比對時再讓 query 的每個 token 跟文件的每個 token 逐一配對。準確度接近 cross-encoder,速度快很多。

一個技巧同時處理痛點 2、3,以及部分的 6。成本是多一次呼叫、50 到 200 毫秒。投影片說沒有別的單一改動有這種投入產出比,小編認同。

5. HyDE 和 multi-query 被降級了

這兩個 2024 年被大量使用的技巧,在這份投影片裡只剩下「知道就好」的地位。

它們要解決的問題本身還在: 使用者用使用者的講法問問題,文件用文件的講法寫。在 agent 出現之前,有兩個技巧試著在單次檢索裡把這個落差補起來:

  • HyDE (Hypothetical Document Embeddings): 請 LLM 先草擬一段假的答案段落,把那段話拿去 embedding 再搜尋。補的是「用語落差」,讓查詢的向量落到語料庫裡真實文件所在的那一區。
  • Multi-query + RRF: 請 LLM 產生 N 種不同講法,各自檢索一次,再用 Reciprocal Rank Fusion 依排名融合。補的是「角度落差」,等於從好幾個角度去猜使用者到底想問什麼。

2026 的說法是: 兩個都被 agentic loop 吸收掉了。一個會評分再改寫的 agent 是動態在做同一件事,而且第一次沒撈到的時候還能救回來。投影片的建議是「知道就好,你會在 2024 年代的程式碼裡看到 HyDEQueryTransformQueryFusionRetriever,但 2026 年優先讓 agent 自己迭代」。

6. 投資報酬率第二名: CRAG 與 agentic loop

固定的 pipeline 對每個問題都用同一種方式回答。agent 則是在每一步問自己: 我手上的資訊夠嗎? 撈回來的東西相關嗎? 要不要換個說法再搜一次? 要不要換一個資料來源?

CRAG (Corrective RAG) 是這個想法最小的形狀: 檢索完先評分,再決定要不要生成。

query → retrieve → grade → 高分: 直接合成
                         → 中間: 改寫 query 再檢索一次
                         → 低分: 退回網路搜尋

投影片對「為什麼有效」的解釋值得記下來: 樸素 RAG 永遠會生成,就算 chunk 是垃圾也一樣,模型會用幻覺把缺口補起來。CRAG 強迫在生成之前先做一個決定。而且這個 grader 很便宜:

  • 它是小而快的模型,不是前沿模型
  • 它看到的只是 (query, chunk) 這種配對,形狀跟 reranker 一模一樣
  • 輸出只有一個信心分數,不是一段長篇判斷

省下來的是花在爛 context 上的生成 token,以及那些聽起來很有自信的錯誤答案。

在 LlamaIndex 裡這是一個 Workflow 而不是一條 chain,每個分支是一個 @step:

@step
async def grade(self, ev: Retrieved):
    s = await grader.score(ev.query, ev.nodes)
    if s > 0.7: return Ready(nodes=ev.nodes)
    if s > 0.3: return Refine(query=ev.query)
    return WebFallback(query=ev.query)

什麼時候該加、什麼時候不該加,這頁小編覺得比技術本身還實用。看之前先講清楚兩個詞: 多跳 (multi-hop) 是指答案得串起好幾份文件才拼得出來的問題 (例如「A 公司的供應商裡,有誰同時是 B 案的被告?」),單跳則是一次檢索就找得到; 問題分布的頭部與長尾是把使用者實際問過的問題按出現頻率排序,最常被問的那一小撮叫頭部,剩下那一長串各只出現一兩次的叫長尾。

✓ 該加的時候
  • 問題分布的頭部已經沒問題,壞掉的是長尾
  • 失敗集中在多跳或語意模糊的問題
  • 答錯的代價高過答得慢
  • 你有辦法在保留不參與調校的測試集上量到提升
✗ 不該加的時候
  • 問題分布的頭部都還一團亂,先修檢索
  • 端到端延遲預算低於 2 秒
  • 你沒有 eval,迴圈只會把退步藏起來
  • 你的「迴圈」其實只是在重試,掩蓋一個 bug

要付的代價也講清楚了: 每次迭代多一次 LLM 呼叫,延遲的中位數 (P50) 典型變成 2 到 4 倍; token 隨迭代次數成長,要設上限; 執行路徑是非決定性的,每一步都要 trace。

投影片的原則是: 先把直線 pipeline 做對,再在 eval 顯示有幫助的那些問題上加迴圈。

7. CRAG 之後: 這個迴圈正在從上下兩邊被擠壓

投影片把 2025 到 2026 加進來的三條研究線放在一起講,小編覺得這是全篇最有價值的判斷之一:

  • Adaptive-RAG (Jeong et al., NAACL 2024): 用一個小分類器依複雜度路由,分成不檢索 / 單跳 / 多跳三類。大部分問題根本不需要迴圈。
  • Speculative RAG (Google, 2024): 把工作拆成兩個角色,一個小模型當「草稿員」(drafter),拿到不同的 chunk 子集之後各寫出一份候選答案; 一個大模型當「驗證員」(verifier),從這些候選裡挑一份。因為所有草稿是同時產生的,這是平行而不是串行,所以贏在延遲。
  • Search-R1 / R1-Searcher (2025): 用 RL 訓練推理模型自己學會什麼時候在思考中途呼叫 search()。檢索迴圈最後被內建進模型本身。

再加上 2025 年的 Deep Research (OpenAI、Anthropic、Perplexity): 規劃 → 分支成平行的子搜尋 → 合成。那是一棵樹狀展開的 agentic retrieval,不是一個迴圈。

投影片的總結: 手刻的 CRAG 正在被兩邊擠壓,下面被 Adaptive-RAG 路由掉,上面被 Search-R1 吸進模型裡。workflow 的形狀留下來了,每一步裡面跑什麼則一直在換。

8. 圖表怎麼進索引: caption-and-index

向量搜尋比對的是文字的 embedding,但 2026 年任何有意思的文件都有圖表、示意圖、截圖,而承載資訊的往往就是那張圖。問「圖 3 裡的年增率營收組合是多少」,答案在長條圖裡,不在旁邊的文字。

兩階段作法:

  1. 匯入時: 用視覺模型幫每張圖產生說明文字,把說明文字跟周圍的文字一起索引。
  2. 合成時: 回答的當下把原圖抓回來,直接交給模型。

用文字檢索拿召回率,再把原圖交給 VLM 做合成。

為什麼不直接把圖片做成 embedding? 因為圖片 embedding 模型 (最常見的是 CLIP) 產出的向量,跟你原本文字那套向量不在同一個空間裡。你會多出一條 pipeline,兩邊也沒辦法共用同一個 reranker。說明文字留在文字空間裡就沒有這個問題。

說明文字的品質才是瓶頸。 要用會講出軸名和單位的 VLM,不是只會回「一張顯示資料的長條圖」的那種。而且不要拿說明文字去改寫 VLM 自己讀得到的東西: 說明文字負責檢索,像素負責合成,原圖一定要留著。

適用的領域很明確: 金融、科學、醫療、簡報這類圖表密集的文件。純散文、或者合成階段只有文字模型的話,這一步就是浪費匯入成本。

9. 結構化資料: 用查的,不要用嵌入的

表格被當成文字 embedding 之後就失去形狀了: 列變成句子、欄變成雜訊、聚合運算變成不可能。問「APAC 區 2025 年支出前 5 名的供應商」,向量檢索找得到那張關於供應商的表,但它沒辦法篩選、分組或排名。

投影片的處理方式是把結構化資料當成可查詢的而不是可檢索的:

  • 索引 schema 加上幾行範例資料,讓 LLM 知道資料長什麼形狀
  • 用一個 router 依問題形狀分流: 表格形狀的問題送去查詢語言,散文形狀的問題送去向量檢索
  • 在合成階段把兩邊的結果合併

查詢語言依資料形狀挑: JSON 用 JQ、關聯式表格用 SQL、圖用 Cypher。判斷規則是: 數字、清單、篩選 → 查詢。摘要 → 檢索。

這裡有個容易誤會的地方要先講清楚: 投影片說的「結構化資料」,主要不是 PDF 裡的表格,而是本來就已經結構化的東西。 原文寫得很明白: 「大部分企業資料不是表格,是巢狀 JSON: API dump、webhook、稽核 log、設定檔。」這類資料進來就是 JSON,沒有「解析成寫進資料庫」這一步,你只是不要把它拿去嵌入而已。

所以投影片才特別留了 JQ 這條路: 讓 LLM 寫出 JQ 運算式,再交給 JQ 執行,同一個運算式跑幾次結果都一樣。比起 SQL 的好處是不用 schema migration、不用 ETL、不用資料庫,直接在原始 JSON 上跑。

而所謂「索引 schema 加幾行範例資料」,指的是你索引「資料長什麼樣」的描述 (欄位名、型別、3 到 5 筆範例),不是資料本身。那是每份資料幾百個 token 的小東西,不是一條匯入管線。

投影片對這件事有一句誠實的話: 「router 才是真正的工作。」關鍵字形狀偵測加上一個後備分類器,大部分團隊會跳過這一塊,而它才是讓整件事成立的東西。

小編補充: 那 PDF 裡的表格到底要不要進資料庫?

這是投影片這一頁跳過的問題,但實務上一定會碰到。答案是大部分情況不用,三條路依成本排:

a. 整張表當 Markdown 塞進去
解析成 Markdown 之後表格是一個原子區塊,表頭跟每個 cell 綁在一起,模型本來就讀得懂。幾十列的表,模型自己篩選排序加總的準確度是夠的。
匯入成本: 零
b. 表格轉 JSON,用 JQ 查
parser 給你的表格本來就是列 × 欄,序列化成 list of dicts 存在文件旁邊,查詢時讓 LLM 寫 JQ。塞不進 prompt 但也還沒大到要資料庫的量走這條。
匯入成本: 一份 JSON + schema 描述
c. 抽成 typed rows 進資料庫
用 LlamaExtract 定義 schema 抽成結構化列再進 DB。只有當同一組欄位跨很多份文件重複出現時才值得,例如幾千張發票、每季同一張財報附表。
匯入成本: 真的要做 ETL

給 LLM 寫 JQ 的 prompt 大概長這樣 (小編自己寫的示意版,投影片只給了 Python 那幾行呼叫程式碼):

你會拿到一份 JSON 資料的結構描述和幾筆範例,以及使用者的問題。
請只輸出一行 jq 運算式,不要解釋,不要加上 markdown 標記。

結構描述:
{schema}

範例資料 (前 3 筆):
{sample_rows}

使用者問題:
{question}

真正的判斷標準不是「這是不是表格」,而是「答案需不需要跨很多列聚合、資料塞不塞得進 prompt」。 投影片那個「前 5 名供應商」的例子之所以需要查詢語言,是因為那是幾千筆跨文件的採購紀錄; 如果只是單頁上的 20 列小表,直接把整張表塞進 prompt 就結束了。

順帶一提,這一頁跟投影片自己的 Part IV 有點互相拉扯: 如果解析有好好保留結構、表格是完整的 Markdown,那「表格失去形狀」這個前提就弱掉了。剩下真正成立的理由只有一個: 資料量大到塞不進 context,所以聚合必須在模型外面算。

前面這幾個技巧在投影片裡是按投資報酬率排序的,建議由上往下採用: reranking → query 改寫 → agentic / CRAG 迴圈 → 多模態 caption-and-index → 結構化資料走 JQ / SQL。它們彼此獨立、可以疊加不衝突,但每加一個都要量測提升幅度,然後決定留或不留。投影片特別補了一句: 做完就停,大部分團隊需要的是其中 1 到 3 個,不是五個全上。

10. 兩個框架轉變: long context 沒有取代 RAG,eval 變成獨立的技術堆疊

2024 年那個預測沒有實現。 當時的說法是「百萬 token 的視窗一出來,檢索就只是小 context 時代的變通做法」。結果視窗確實出來了,檢索反而變得更重要,把整個語料庫塞進 context 這條路輸得很徹底,三個原因:

  • 成本: 每個 query 一百萬 token,以前沿模型的價格算,快取有幫助,但只要語料庫稍有規模,這筆帳就算不過來。
  • 治理: 塞進去等於把使用者不該看到的文件也塞進去了。「模型答應會忽略」不是一個權限邊界。
  • 可稽核性: 「AI 為什麼這樣說?」檢索給你一份引用紀錄,long context 只給你一種感覺。

投影片的結論: 檢索最後扮演的角色是稽核軌跡,而不是小 context 的變通做法。 小編覺得這個重新定位比「哪個比較準」的爭論有用多了。

eval 則從一個 notebook 變成一整套技術堆疊。 2024 年是 10 題手寫問題、肉眼看過就上線; 2026 年是從 production query 抽樣建立 eval set、用三個一組的指標評分,然後把 eval 當成每次部署的關卡,分數掉了就擋下來。

那三個指標分別是忠實度 (faithfulness: 答案有沒有忠於檢索到的內容)、相關性 (relevancy: 答案有沒有回答到問題)、召回率 (recall: 該撈的內容有沒有撈到)。工具上 RAGAS 提供核心 RAG 指標的參考實作、DeepEval 做這組三元指標、Langfuse 和 Phoenix 負責 trace、重播、production 抽樣與回歸把關。

兩個提醒講得很直白:

  • 不要用同一個模型來生成和評分,它會同意自己。
  • 不要只評你的系統答得好的問題,要另外維護一組明確的長尾題庫。

最後一句話值得抄下來: 如果你沒辦法量測一個改動有沒有幫助,你手上的是 demo,不是系統。

編按: 關於 LLM-as-judge 怎麼做得更可靠,可以參考小編之前整理的 用檢查清單拆解 LLM-as-Judge

11. Parsing 的六種壞法

到這裡投影片講了一句很有力的話: 前面四十分鐘講的 reranking、HyDE、agentic loop 全都重要,但沒有任何一項救得回一份一開始就被解析壞掉的文件

被檢查的對象是 2024 年大部分團隊在用的 PyMuPDF 或 pdfplumber。投影片對它們的評價蠻公道的: 免費、快、在簡單文件上意外地堪用,單欄的研究論文沒問題; 但年報、合約、法規申報就不行了,因為它們的輸出是「一串大致照閱讀順序排的文字」,而在多欄頁面上,那個順序就是錯的。

接下來六頁一頁一個失敗模式,全部用同一份真實文件 (DBS 2025 年報) 示範:

1. 多欄被合併成一行。 PyMuPDF 大致照著頁面上的閱讀順序走 token,遇到兩欄就會左右交錯。你拿到的會是「淨利息收入 本集團持續交出強勁 成長 8% 因為存款成長橫跨…」這種東西: 兩句不相干的話被逐字穿插在一起。下游後果是 embedding 帶進了不存在的上下文,檢索會撈出文件裡根本沒寫過的句子。

2. 表格變成一串 pipe 分隔的碎片。

欄位標頭跟它的列脫鉤,數字失去單位。投影片的例子裡,Allowances 241 9 >100 讀起來像一個句子,而不是表格的一列。下游後果就是痛點 4: 對的頁、錯的列,agent 很有自信地從錯的那一行拿值。

3. 註腳被內嵌進本文。 註腳字比較小、通常在頁尾,樸素的 parser 直接把它接在本文後面。結果是「淨利息收入上升 14 bps (1)。本集團 CET1 比率改善 (1) 不含出售子公司的一次性收益。」那句但書讀起來像是下一句話,數字則變成沒有脈絡的數字 (bps 是基點,1 bps = 0.01 個百分點; CET1 是銀行的核心資本適足率)。下游後果: agent 把「+14 bps」當成成長主軸引用,完全不知道還有一次性收益這個前提。

4. 圖表被直接跳過。 圖是 image object,文字 parser 走過去就沒了。裡面的軸標籤、刻度值、標註全部不會進索引。你拿到的通常只有圖例字串「FY21 FY22 FY23 FY24 FY25」,沒有數值、沒有趨勢。下游後果: 任何答案在圖裡的問題都會失敗,而且不會報錯。

5. 頁首頁尾污染每一個 chunk。 每一頁上方都是同一個字串「DBS Group Holdings Ltd · Annual Report 2025」,頁尾都是頁碼,每個 chunk 都繼承這兩樣。結果是每個 chunk 的 embedding 都被拉往同一個中心,餘弦相似度的鑑別力下降。下游後果: top-k 的邊界變得很吵,reranker 得把預算花在區分近似重複的 chunk 上。

6. 跨頁的閱讀順序斷掉。 一個句子在第 99 頁開始、第 100 頁結束; 一張表跨兩頁。沒有做頁面縫合的 parser 會吐出兩個互不相連的片段: chunk A 在子句中間結束,chunk B 在子句中間開始,各自被 embedding。表格底部幾列跟它的表頭在不同的 chunk 裡。下游後果: 一個分頁同時引發痛點 3 (資訊沒進 context) 和痛點 4 (沒抽取出來)。

投影片最後補了一句: 隨便挑一份你沒有親自檢查過的 production PDF,這六種至少有一種正在發生。

12. Spatial text: 被跳過的原始型別

這是整份投影片的核心概念。PDF 不是一串文字,它是一組在 2D 畫布上有座標的 token,每一個都帶著 x、y、寬、高、字體和樣式。只要這些資訊在解析階段被保留下來,下游就還有辦法對版面做推理。

作法很單純: 按 y 分組得到列,按 x 分組得到欄。上圖右邊是 LiteParse 的真實輸出,左邊是從 token 還原回來的表格。

扁平文字失去的東西 vs spatial text 給你的東西:

扁平文字失去
  • 欄結構: 多欄頁面會交錯
  • 表格的行與列: 每個 cell 變成孤立的數字
  • 閱讀順序: 側欄、註腳、圖說跟本文混在一起
  • 階層: 標題跟它的段落變得無法區分
  • 位置感: 你沒辦法說「第 4 頁那張表」,因為沒有第 4 頁了
spatial text 給你
  • 可還原的結構: 按 y 分組是列,按 x 分組是欄,按區域分組是章節
  • 依區域過濾: 不用寫 regex 就能把頁首頁尾丟掉
  • 可以引用回像素: 每個 chunk 對應一個可以 highlight 的 bounding box
  • 可以交接給 VLM: 文字不夠用時,退回同一個座標的頁面截圖

重點是輸出保留了足夠的資訊,讓你在下游想要什麼就推導什麼: 要純文字就攤平、要表格就按 y 分組、要丟掉頁尾就按區域過濾。

LiteParse 是 LlamaIndex 開源的實作: Apache 2.0、本機執行、不需要雲端也不需要 LLM、單一 binary 沒有 Python 依賴,內建 Tesseract.js 做 OCR (可以換成自己的 HTTP OCR server),另外可以產生高解析度的頁面截圖給 VLM 用。目前 GitHub 上約 11.8k star

npm i -g @llamaindex/liteparse

lit parse document.pdf        # JSON + bounding boxes
lit screenshot document.pdf   # 給 VLM 用的頁面圖

編按: 小編這篇就是用 LiteParse 解析這份 116 頁投影片的,含 OCR 全部跑完 40 秒。它也可以直接當成 Claude Code 的 skill 安裝: npx skills add run-llama/llamaparse-agent-skills --skill liteparse

13. Markdown 是交給下游的格式,chunking 沿著結構切

spatial text 保存的是幾何,Markdown 保存的是結構。投影片把兩者的分工講得很清楚: spatial text 是你解析成的東西,Markdown 是你交給下游的東西。

一份 PDF 轉成 Markdown 之後保留了: 標題階層 (# ## ###)、表格網格連同表頭與資料列、清單與巢狀清單、引言與粗體斜體這類排版意圖、掛回錨點的註腳、圖片與它的說明文字。

三個下游好處:

  1. chunking 尊重結構: 依標題切,每個 chunk 就是一個完整的章節。MarkdownNodeParser 會保住表格和程式碼區塊不被切開。
  2. 檢索時表格保持原子性: 一張 pipe 表格是一個區塊,表頭那一列跟每個 cell 都黏在一起,不會再有「對的頁、錯的列」。
  3. LLM 本來就讀得懂: 每個前沿模型都在 Markdown 上訓練過,不需要用 prompt 去解釋「這是一張表格」,語法本身就是訊號。

整條 pipeline 就是: 解析 PDF 成帶 bbox 的 spatial token → 偵測區域 (標題、表格、圖、註腳) → 輸出保留這些區域的 Markdown → 依 Markdown 結構做 chunking。

chunking 這一步的觀念也換了。 2024 年的答案是 512 token、50 token overlap、recursive splitter,投影片對它的評語是「永遠是錯的,只是一個站得住腳的預設值,因為它假裝文件是一串字串」。2026 年的答案是沿著文件結構切:

  • 一個章節是一個 chunk
  • 一張表是一個 chunk
  • 一張圖加它的說明是一個 chunk
  • 註腳跟著它的錨點

前提是 parser 有把結構留下來,這也是為什麼 chunking 這個主題在投影片裡被放進 parsing 那一章,而不是留在檢索那一章。

metadata 是 chunking 的另外一半。 投影片給的範例 metadata 是這樣:

chunk.metadata = {
    "document_title": "Annual Report 2025",
    "section_path": "IBG > Segment Performance",
    "page": 37,
    "source_url": "https://...",
    "ingest_ts": "2026-03-12T09:01:00Z",
    "permissions": ["analyst", "internal"],
    "parser": "llamaparse@2026-02",
    "confidence": 0.94,
}

投影片替這些欄位列了三個用途,各自對應 pipeline 的一個階段:

  • 檢索時拿來過濾: 「只要 2024 年以後的 10-K 章節」應該用 ingest_ts 這類 metadata 條件直接擋掉,不該丟給向量搜尋去猜。
  • 合成時拿來引用: pagesection_path 一起帶回答案裡,使用者可以點進去看原文。
  • 稽核時拿來追溯: 有 parser 版本加上 ingest_ts,才能重播任何一個歷史答案當初是怎麼來的。

投影片的結論是: 把 metadata 拿掉,檢索、引用、過濾三件事會一起變差。

14. LlamaParse 與 LlamaExtract,以及該用哪一個

LlamaParse 負責的是 LiteParse 應付不來的那一類文件: 它是雲端託管服務,以視覺模型為核心,按頁計價,預設輸出 Markdown。投影片列的適用時機是掃描件、密集表格、難搞的多欄版面、需要理解圖表內容、需要跨 10k 份以上文件保持一致,而且延遲預算是分鐘級而不是毫秒級。

parser = LlamaParse(
    result_type="markdown",   # 這才是重點
    parse_mode="agentic",     # 每頁走視覺模型
)

LlamaExtract 走的是另一條路: 你定義一個 Pydantic schema,它回給你符合型別的 JSON,解析、檢索、合成全部在它內部完成。schema 就是契約,不合法的輸出在進來之前就被擋掉; 跨文件的欄位和型別都一致; 而且每個值都帶著頁碼和 bbox 的引用。

class Segment(BaseModel):
    name: str
    total_income: float
    yoy_pct: float = Field(description="YoY %")

class AnnualReport(BaseModel):
    fiscal_year: int
    segments: list[Segment]

result = await LlamaExtract().aextract("annual_report_2025.pdf", schema=AnnualReport)
# result.data      → 驗證過的 AnnualReport
# result.citations → 每個欄位的 page + bbox

Extract 還是 Parse + Retrieval? 判準很清楚:

  • 你知道自己要什麼、跨很多文件都是同一組欄位、下游是結構化資料 → Extract。例子: 合約審查抓固定的 12 個條款。
  • 你不知道自己要什麼、問法五花八門、問題形狀是長尾 → Parse + Retrieval。例子: 語料庫上的研究助理。

投影片補了一句: 大部分真實系統兩個都用,已知欄位走 extract,長尾走 parse + retrieval。

還有一個容易忽略的觀念: parsing 是一個階段,不是一個步驟。 你會重新解析、會用新的 schema 重新抽取、parser 變好的時候會需要回溯重跑。所以要為這件事設計: 保留原始文件、保留每一個中間產物、讓 parsing 具備冪等性 (idempotent)。

15. ParseBench: 用公開數字講話

LlamaIndex 在 2026 年做了 ParseBench,用約 2000 頁人工驗證過的企業文件 (保險、金融、政府) 評五個維度: 表格 (行 × 列的結構有沒有還原)、圖表 (軸、數值、序列對不對)、文字完整性 (有沒有漏字或幻覺)、文字樣式 (標題、清單、強調有沒有保留成語意結構)、bbox grounding (每個抽出來的元素能不能指回頁面上的位置)。

評的是語意正確性,不是文字重疊率,這一點是它跟舊式 OCR benchmark 最大的差別。

兩個榜:

  • Hugging Face 上的資料集與 leaderboard 給開放權重模型,可以下載下來在自己機器上跑 Qwen-VL、Dots OCR、Docling 或自己的 fine-tune,不用 API key。
  • Kaggle 上的競賽榜 給前沿與商用模型,目前前段是 Gemini 3 Flash、GPT-5.4、Claude Sonnet 4.6。兩邊用的是同一套評測程式 (eval harness),所以開放模型跟前沿模型的數字可以直接比。

成本這頁的結論是: 好的解析確實比較貴,但曲線大部分是平的,多花的錢沒有想像中多。

效率前緣 (Pareto frontier,指的是在同樣成本下沒有別的選項更準、在同樣準確度下沒有別的選項更便宜的那些點) 落在 LlamaParse Agentic (84.9%,每頁約 1.2 美分) 和 LlamaParse Cost-Effective (71.9%,每頁約 0.4 美分)。前沿 VLM 拿來當 parser 反而不划算: GPT-5 Mini 和 Haiku 4.5 只有 40 出頭的分數,價格卻是高階的。

小編要提醒一下利益關係: 這是 LlamaIndex 自己做的 benchmark,論文作者名單裡就有這場工作坊的講者,而榜首是自家產品。 看數字的時候把這件事放在心上。不過資料集和評測程式都是公開可重跑的,這一點比大部分廠商 benchmark 誠實; 投影片自己也寫了「上線前先自己驗,數字會比廠商報的低」。

16. 四個文件工作流的實際案例

投影片第五部分講的是 LlamaIndex 自家 Workflow 框架的設計 (事件驅動的圖、typed event、扇出與收斂的 API),那屬於框架選型的範疇,小編這裡跳過。真正值得帶走的是四個實際案例,以及背後那些跟框架無關的架構判斷。

合約審查。 幾千份合約,每一份都有同樣那 12 個高風險條款,律師每次都要重讀一遍。改造之後的流程是: 解析 → 判斷文件型別 → 逐條抽取條款 → 逐條打風險分數 → 只有被標記的送到人面前 → 人的決定回饋進最終報告。

這個案例的分解問答很值得抄:

  • 為什麼解析要獨立成一個步驟? 因為你一定會換 parser,要先解耦。
  • 為什麼判斷型別要排在抽取前面? 因為抽取用的 schema 取決於文件是 MSA、NDA 還是 SOW。
  • 為什麼打分要排在人工審查前面? 因為你只想讓人看被標記的條款,不是全部 12 條乘以幾千份。
  • 為什麼人工審查要夾在打分和報告中間? 因為人的修改要能回饋進報告,不能只是旁邊做個記號。

投影片的總結是: 流程通常比較像決策流程的鏡像,而不是資料流程的鏡像。

人工審查應該設計成系統的一部分,而不是掛在系統外面。 這是這一章小編覺得最有價值的一句。真實的文件系統 (合約審查、盡職調查、病歷摘要) 不可能全自動,而常見的做法是流程跑到一半暫停、寄一封信給審查者、期待人會回來,這種做法脆弱、沒有狀態、事後也追不回來。

把人工審查做成流程裡一個正式的狀態 (待審 → 已審,中間的狀態持久保存,人可以隔幾分鐘也可以隔幾天才回來),好處是你可以對它做 eval: 量測人什麼時候同意、什麼時候不同意、他們具體改了什麼,然後把這些回饋進下一版的 eval set。人變成你會量測的系統的一部分,而不是一個掛在外面的保險機制。

Deep research。 「DBS 在 FY26 的定位如何?」其實是五個子問題 (利差、信用、財富管理、地區組合、資本)。子問題要平行跑 (延遲預算)、負責彙整成文的那一步要等全部子問題有結果、引用要一路串到底,而且其中一個子問題失敗不應該讓其他的一起失敗。

盡職調查。 五千份文件的資料室,合約、財報、股權表、聘僱合約各是不同形狀,要抽的事實也不一樣。作法是逐份分類 → 路由到型別專屬的抽取 schema → 累積成一個結構化的事實資料庫 → 跑跨文件一致性檢查 → 把異常送給人看。這裡的重點是「事實資料庫」: 抽出來的東西要是可以查詢的結構化資料,跨文件比對才做得起來。

活的知識庫。 來源文件會被編輯,索引就會漂移,而相關的人往往是從一個壞掉的答案才發現這件事。這套流程監看來源、變更時重新解析、增量更新索引,並把事實層級的差異發布給訂閱者。它跟前三個最大的差別是沒有終點: 這是一個持續運轉的系統,不是一個跑完就結束的腳本。

什麼時候不需要這一套? 投影片講得很乾脆: 如果你的任務是「對單一文件做單次問答」,一個函式就夠了。這種流程編排值得它的複雜度,是在你有分支、需要平行、需要中斷後續跑、或者需要人工審查的時候。

17. 附錄裡幾條值得單獨挑出來的

投影片後面的附錄有幾條實務模式,小編覺得比正文某些段落還實用:

版本化每一樣東西 (parser、index、prompt、schema)。理由講得很好: 「我們星期二換了 parser,星期三準確率掉了 8 個百分點」這句話,只有在星期二那版 parser 還拿得到的時候才 debug 得動。

每個 query 設成本預算。 大部分 production 的成本超支不是「模型變貴了」,而是「某個 agent 陷進檢索迴圈,在一個本來該花 2k token 的問題上用掉 30k」。加一個檢查累計 token 的上限,30 秒就寫得完,也就抓得到; 沒有的話,你會在月底帳單上才發現。

把信心分數當成一級輸出。 高信心的答案直接出,低信心的標記待審。投影片的說法是: 沒有信心分數,每個答案都得審,那你做出來的是很貴的自動完成。

把回饋當成資料。 使用者按倒讚就記下來、審查者改了抽取結果就記下修改前後、agent 重試就記下為什麼。這些 log 會變成下一版的 eval set、下一批 fine-tuning 資料、以及下一個「錢該投哪裡」的判斷依據。

匯入是串流,不是批次。 痛點 8 (資料匯入的規模) 其實已經被現代向量儲存悄悄解決了: pgvector + HNSW、LanceDB、Turbopuffer、Pinecone 都支援增量 upsert (同一筆資料有就更新、沒有就新增,不用整個索引重建),再配上內容雜湊去重和一個工作佇列就夠了。投影片的觀察是大部分團隊還在跑批次,純粹出於習慣。

備援模型是維運問題,不是模型問題。 痛點 11 的處理方式也很直接: 用 gateway 路由 (LiteLLM、OpenRouter 或自己寫的)、主力與備援放在不同供應商、積極快取,然後在 CI 裡強制走一次備援路徑做測試。最後這條是最常被漏掉的,因為備援路徑平常不會被執行到,壞了也沒人知道。

Graph RAG 的取捨。 適合的情況是多跳本來就是問題的本質、實體與關聯的 schema 穩定、實體在整個語料庫裡被重複使用、答錯的代價很高 (盡職調查、法遵、臨床)。不適合的情況是單跳問答 (過度設計) 和 schema 漂移得比語料庫還快。隱藏成本是實體抽錯就會產生錯的關聯,然後走訪出錯誤的結果; 延遲要抓樸素 RAG 的 2 到 3 倍。投影片的建議是: 因為 eval 顯示單跳會漏掉才採用,不是因為它看起來有趣。

多粒度索引。 句子層級適合精準查找和引用,但拿來當合成用的 context 很糟; 章節層級是合成脈絡的最佳選擇,只索引一種粒度的話就選它; 頁層級適合引用和當退路,本身精確度很差。實務建議是三種都索引,再依問題形狀路由或跨粒度 rerank。儲存變 3 倍,但向量很便宜,這個成本值得付。

安全那頁值得所有人看一次。 核心觀念是: 檢索回來的內容是不可信輸入。攻擊者不需要碰到你的 prompt,他只需要在你的語料庫裡放一份文件。

具體要做的有四件事: 把每個檢索到的 chunk 當成使用者填的表單欄位看待,在匯入時就剝除或標記那些長得像指令的文字 (不是等到合成階段才處理); 限制模型能碰的工具,會做檢索的 agent 不應該同時具備把資料送出去的能力; 權限跟著 chunk 走,把授權 metadata 放在每個 chunk 上、檢索時就過濾掉,而不是多租戶共用一個索引然後期待 prompt 守得住; 維護一份紅隊語料庫 (裡面放刻意埋了惡意指令的文件),每次發版都像 eval 一樣跑一次。

工具都是現成的 (Presidio、Lakera、Protect AI、NeMo Guardrails),缺的是「每次檢索都跑」的紀律。

18. 星期一可以做的事

投影片結尾給的行動清單只有四步,刻意做得很小:

  1. 挑一種你目前 pipeline 處理得不好的文件型別
  2. 把它丟進 LlamaParse 或 LiteParse 跑一次
  3. 跟你現在 parser 的輸出做比對
  4. 找出一個你現在的 parser 漏掉的具體東西

理由是這樣你手上會有一個具體的失敗可以對付,通常比漫無目標地優化整條 pipeline 容易得多。一次處理一種文件型別,成效通常比較好。


整份投影片最後收在一句話: 瓶頸換位置了。離開模型,離開檢索器,進到文件本身。

小編覺得這份材料最大的價值不是任何單一技巧,而是它把「力氣該投在哪一層」講清楚了。RAG 的討論很容易集中在向量資料庫、embedding 模型、chunk size 這幾個題目上,但那些都是檢索層的參數。如果 parsing 這一層就已經把表格拆散、把註腳接錯位置、把圖表整個丟掉,那後面每一層調得再細,都是在錯誤的文字上做功。更麻煩的是這種錯誤在監控上完全看不出來: 它不會報錯,只會給你一個很有自信的錯答案。

Sources: