<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Applications on Tarragon</title><link>https://tarrragon.github.io/blog/tags/applications/</link><description>Recent content in Applications on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 01 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/applications/index.xml" rel="self" type="application/rss+xml"/><item><title>Case Study：customer support agent 從 task decomposition 到 eval</title><link>https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/</guid><description>&lt;p>本案例的責任是把模組四前面所有原理章節串成一個端到端的設計過程、示範&lt;strong>遇到實際 LLM 應用任務時、設計反射動作的順序&lt;/strong>。每段都標出引用哪章原理、讓讀者看到 principle 章節怎麼落到具體工作。&lt;/p>
&lt;p>用作走查的任務：PM 交派「做一個 customer support agent、能處理用戶查詢、必要時自動完成操作（如改地址）。」本案例聚焦「改地址」這個高頻 query type 走完整流程。&lt;/p>
&lt;h2 id="本案例的設計反射">本案例的設計反射&lt;/h2>
&lt;p>整個流程分七階段：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>觀察人類工作流&lt;/strong>：訪談、決定 task decomposition&lt;/li>
&lt;li>&lt;strong>典範定位&lt;/strong>：哪段該 deterministic、哪段該 fuzzy&lt;/li>
&lt;li>&lt;strong>工作流設計&lt;/strong>：每個 step 選對應的 LLM / tool / RAG / HITL 形態&lt;/li>
&lt;li>&lt;strong>協議跟自主度決定&lt;/strong>：是 single agent / multi-call / multi-agent&lt;/li>
&lt;li>&lt;strong>Trace instrumentation&lt;/strong>：哪些資訊要記&lt;/li>
&lt;li>&lt;strong>Eval 設計&lt;/strong>：先選座標、再選工具&lt;/li>
&lt;li>&lt;strong>Iteration loop&lt;/strong>：error analysis → 修哪一層 → 看 metric 收斂&lt;/li>
&lt;/ol>
&lt;p>初次設計 LLM 應用時最常省略階段 1、2、5、6、直接跳到階段 3 開始寫 prompt——這條路會走進「prompt 改了 20 版、無法判讀有沒有變好」的迭代無收斂。本案例強調的是設計反射動作的順序、不是寫 prompt 技巧。&lt;/p>
&lt;h2 id="階段-1觀察人類工作流">階段 1：觀察人類工作流&lt;/h2>
&lt;p>PM 給的任務描述是「處理用戶查詢」、但「查詢」涵蓋的範圍可能很大。第一個反射動作是&lt;strong>坐在客服旁邊觀察兩天&lt;/strong>、不是打開 IDE。&lt;/p>
&lt;p>實際做的事：&lt;/p>
&lt;ul>
&lt;li>統計收到的 query 類型分佈（退款 / 改地址 / 查詢訂單狀態 / 抱怨 / 開放問題各佔多少）。&lt;/li>
&lt;li>看每類 query 的 human resolution 流程（哪幾步、要查哪些系統、要遵守哪些 policy）。&lt;/li>
&lt;li>看哪幾類 query 是 high volume + low complexity（最值得自動化）、哪幾類是 low volume + high complexity（自動化 ROI 差）。&lt;/li>
&lt;li>記下 human 在哪些 step 卡住、哪些 step 反覆需要查同樣資料。&lt;/li>
&lt;/ul>
&lt;p>訪談結束、你得到一張 task decomposition map。本案例假設聚焦在「用戶請求改地址」這個高頻 query type：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">User: 「我搬家了、訂單編號 #12345、新地址是 ___」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">1. 解析意圖 + 抽取訊息（訂單編號、新地址）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">2. 查訂單狀態（已出貨？未出貨？已送達？）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3. 查 policy（這個訂單狀態 + user tier 能不能改地址？）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">4. 若可：執行改地址（呼叫物流 / 庫存 API）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">5. 若不可：解釋為什麼、給替代方案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">6. 草擬回覆 email、發出&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>引用原理：這個 decomposition 本身對應 &lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering&lt;/a>（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/deterministic-vs-fuzzy/" data-link-title="Deterministic vs Fuzzy engineering" data-link-desc="LLM 軟體 vs 傳統軟體在資料 / 邏輯 / 行為一致性 / 實驗成本四維度的典範差異、決定哪段該包 guardrail">deterministic-vs-fuzzy&lt;/a> 卡）的「先分解任務、再判讀每段該 deterministic 還是 fuzzy」。&lt;/p>
&lt;h2 id="階段-2典範定位">階段 2：典範定位&lt;/h2>
&lt;p>對每個 step 做典範定位（deterministic / fuzzy）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Step&lt;/th>
 &lt;th>典範&lt;/th>
 &lt;th>為什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1. 解析意圖 + 抽取訊息&lt;/td>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>自由文字 input、需要 LLM 理解&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. 查訂單狀態&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>結構化 query（給 order_id、回 status）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 查 policy&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>規則可窮舉、policy as code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. 執行改地址&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>API call、有 schema 跟錯誤碼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5. 解釋 / 給替代方案&lt;/td>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>要寫人話、要 tailored to 情境&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6. 草擬 email + 發出&lt;/td>
 &lt;td>Fuzzy（草擬）+ Deterministic（發送）&lt;/td>
 &lt;td>寫 email 是 fuzzy、發 API call 是 deterministic&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀的重點是&lt;strong>邊界各歸各位&lt;/strong>：規則跟政策走 code、人話跟意圖解析走 LLM。&lt;/p></description><content:encoded><![CDATA[<p>本案例的責任是把模組四前面所有原理章節串成一個端到端的設計過程、示範<strong>遇到實際 LLM 應用任務時、設計反射動作的順序</strong>。每段都標出引用哪章原理、讓讀者看到 principle 章節怎麼落到具體工作。</p>
<p>用作走查的任務：PM 交派「做一個 customer support agent、能處理用戶查詢、必要時自動完成操作（如改地址）。」本案例聚焦「改地址」這個高頻 query type 走完整流程。</p>
<h2 id="本案例的設計反射">本案例的設計反射</h2>
<p>整個流程分七階段：</p>
<ol>
<li><strong>觀察人類工作流</strong>：訪談、決定 task decomposition</li>
<li><strong>典範定位</strong>：哪段該 deterministic、哪段該 fuzzy</li>
<li><strong>工作流設計</strong>：每個 step 選對應的 LLM / tool / RAG / HITL 形態</li>
<li><strong>協議跟自主度決定</strong>：是 single agent / multi-call / multi-agent</li>
<li><strong>Trace instrumentation</strong>：哪些資訊要記</li>
<li><strong>Eval 設計</strong>：先選座標、再選工具</li>
<li><strong>Iteration loop</strong>：error analysis → 修哪一層 → 看 metric 收斂</li>
</ol>
<p>初次設計 LLM 應用時最常省略階段 1、2、5、6、直接跳到階段 3 開始寫 prompt——這條路會走進「prompt 改了 20 版、無法判讀有沒有變好」的迭代無收斂。本案例強調的是設計反射動作的順序、不是寫 prompt 技巧。</p>
<h2 id="階段-1觀察人類工作流">階段 1：觀察人類工作流</h2>
<p>PM 給的任務描述是「處理用戶查詢」、但「查詢」涵蓋的範圍可能很大。第一個反射動作是<strong>坐在客服旁邊觀察兩天</strong>、不是打開 IDE。</p>
<p>實際做的事：</p>
<ul>
<li>統計收到的 query 類型分佈（退款 / 改地址 / 查詢訂單狀態 / 抱怨 / 開放問題各佔多少）。</li>
<li>看每類 query 的 human resolution 流程（哪幾步、要查哪些系統、要遵守哪些 policy）。</li>
<li>看哪幾類 query 是 high volume + low complexity（最值得自動化）、哪幾類是 low volume + high complexity（自動化 ROI 差）。</li>
<li>記下 human 在哪些 step 卡住、哪些 step 反覆需要查同樣資料。</li>
</ul>
<p>訪談結束、你得到一張 task decomposition map。本案例假設聚焦在「用戶請求改地址」這個高頻 query type：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">User: 「我搬家了、訂單編號 #12345、新地址是 ___」
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">1. 解析意圖 + 抽取訊息（訂單編號、新地址）
</span></span><span class="line"><span class="ln">4</span><span class="cl">2. 查訂單狀態（已出貨？未出貨？已送達？）
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. 查 policy（這個訂單狀態 + user tier 能不能改地址？）
</span></span><span class="line"><span class="ln">6</span><span class="cl">4. 若可：執行改地址（呼叫物流 / 庫存 API）
</span></span><span class="line"><span class="ln">7</span><span class="cl">5. 若不可：解釋為什麼、給替代方案
</span></span><span class="line"><span class="ln">8</span><span class="cl">6. 草擬回覆 email、發出</span></span></code></pre></div><p>引用原理：這個 decomposition 本身對應 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a>（<a href="/blog/llm/knowledge-cards/deterministic-vs-fuzzy/" data-link-title="Deterministic vs Fuzzy engineering" data-link-desc="LLM 軟體 vs 傳統軟體在資料 / 邏輯 / 行為一致性 / 實驗成本四維度的典範差異、決定哪段該包 guardrail">deterministic-vs-fuzzy</a> 卡）的「先分解任務、再判讀每段該 deterministic 還是 fuzzy」。</p>
<h2 id="階段-2典範定位">階段 2：典範定位</h2>
<p>對每個 step 做典範定位（deterministic / fuzzy）：</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>典範</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 解析意圖 + 抽取訊息</td>
          <td>Fuzzy</td>
          <td>自由文字 input、需要 LLM 理解</td>
      </tr>
      <tr>
          <td>2. 查訂單狀態</td>
          <td>Deterministic</td>
          <td>結構化 query（給 order_id、回 status）</td>
      </tr>
      <tr>
          <td>3. 查 policy</td>
          <td>Deterministic</td>
          <td>規則可窮舉、policy as code</td>
      </tr>
      <tr>
          <td>4. 執行改地址</td>
          <td>Deterministic</td>
          <td>API call、有 schema 跟錯誤碼</td>
      </tr>
      <tr>
          <td>5. 解釋 / 給替代方案</td>
          <td>Fuzzy</td>
          <td>要寫人話、要 tailored to 情境</td>
      </tr>
      <tr>
          <td>6. 草擬 email + 發出</td>
          <td>Fuzzy（草擬）+ Deterministic（發送）</td>
          <td>寫 email 是 fuzzy、發 API call 是 deterministic</td>
      </tr>
  </tbody>
</table>
<p>判讀的重點是<strong>邊界各歸各位</strong>：規則跟政策走 code、人話跟意圖解析走 LLM。</p>
<ul>
<li>Policy check 寫成 code（如「user tier + 訂單狀態 → 能否改地址」是 deterministic 規則）。對應反例：把規則塞進 prompt 讓 LLM 判斷、會偶爾跳過規則或誤判 tier。</li>
<li>「能不能做」這類 yes/no 走規則。對應反例：用 LLM 算判斷、debug 困難且非確定性。</li>
<li>「Helpful 的回覆」走 LLM 寫。對應反例：在 code 內 hard-code 模板、變成僵化的客服機器人腔。</li>
</ul>
<p>最容易混的邊界在 step 6：「草擬 email」是 fuzzy（要寫人話、tailor to 情境）、「發送 email」是 deterministic（呼叫 API、處理錯誤碼）。把這兩件事拆開、草擬可以 retry / 改 prompt 不影響發送邏輯、發送有結構化 error 不被 LLM hallucinate 蓋過。Step 4「執行改地址」也類似：tool call 本身 deterministic、但是否該 call 的判讀回到 step 3 的 policy check。</p>
<p>引用原理：<a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a> 的「哪段該 deterministic / 哪段該 fuzzy」決策框架、特別是反模式「邊界用錯」段。</p>
<h2 id="階段-3工作流設計">階段 3：工作流設計</h2>
<p>對每個 step 選對應的工具：</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>設計選擇</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 解析意圖 + 抽取訊息</td>
          <td>Vanilla LLM call + structured output（output 強制 JSON schema：intent / order_id / new_address）</td>
      </tr>
      <tr>
          <td>2. 查訂單狀態</td>
          <td>Tool call → 內部 order API</td>
      </tr>
      <tr>
          <td>3. 查 policy</td>
          <td>Tool call → policy engine（純 deterministic、不過 LLM）</td>
      </tr>
      <tr>
          <td>4. 執行改地址</td>
          <td>Tool call → logistics API、寫操作前要 pre-act HITL（高風險 + 不可逆）</td>
      </tr>
      <tr>
          <td>5. 解釋 / 給替代方案</td>
          <td>LLM call + few-shot（從 case 庫 retrieve「類似情境怎麼解釋」、配 RAG）</td>
      </tr>
      <tr>
          <td>6. 草擬 email + 發出</td>
          <td>LLM call 寫 email + structured output 含 subject/body、發送透過 email API</td>
      </tr>
  </tbody>
</table>
<p>兩個容易選錯的 step 展開：</p>
<p><strong>Step 1 為何要 structured output、不是純 prompt 解析</strong>：抽取結果要餵 step 2-4 的 deterministic tool、order_id 抽錯就整個流程斷。純 prompt 描述「請輸出 JSON」是弱保證、structured output / constrained decoding 是強保證（見 <a href="/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10 constrained decoding 內部</a>）。Trade-off：強格式可能犧牲表達彈性、但這個 step 不需要彈性、要的是可靠。</p>
<p><strong>Step 5 為何配 RAG 而非純 few-shot</strong>：客服 case 涵蓋多種情境（訂單已出貨 / 已送達 / VIP / 一般 user / 不同國家 policy）、固定 few-shot 範例 cover 不全。RAG 從歷史 case 庫即時 retrieve 最相似的解釋範例、屬於 <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> context 軸的 retrieval-augmented prompting。</p>
<p>引用原理：</p>
<ul>
<li>Step 1 的 structured output → <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a></li>
<li>Step 2-4 的 tool 設計 → <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 tool use</a></li>
<li>Step 4 的 pre-act HITL → <a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5 人機協作拓樸</a> pre-act 段。對比講座 Workera appeal 是 post-hoc、本案例選 pre-act 是因為改地址不可逆 + 物流影響大、必須在執行前審</li>
<li>Step 5 的 RAG → <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> + <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> context 軸</li>
</ul>
<h2 id="階段-4協議跟自主度決定">階段 4：協議跟自主度決定</h2>
<p>這個工作流的控制流是線性的（1→2→3→4→5→6）、有條件分支（step 3 結果決定走 4 還是 5）、但每步順序固定。判讀：</p>
<p><strong>該用什麼結構</strong>：</p>
<ul>
<li><strong>不適用 Multi-agent</strong>：步驟順序固定、角色差異不大、orchestration overhead 純增。</li>
<li><strong>不適用 Single agent loop（model 自決下一步）</strong>：本案例假設 single-turn / 短多 turn、步驟順序明確、不需要 agent 自決。若 user 互動多輪 + turn 數不固定（如 user 中途補資訊、改主意、追問）、可考慮 agent loop。</li>
<li><strong>採用 Multi-call pipeline + router</strong>：寫成 deterministic pipeline、step 3 後有 router 分流。</li>
</ul>
<p>引用原理：</p>
<ul>
<li><a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8 multi-agent 拓樸</a> 的「先 multi-call、不夠再 multi-agent」反射</li>
<li><a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> 的 pipeline + router 模式</li>
<li><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 的「先 single-call、不夠再 agent」反射</li>
</ul>
<p><strong>自主度</strong>：</p>
<ul>
<li>Step 1（parse）、5（解釋）、6（草擬 email）：full auto。</li>
<li>Step 2、3（查訂單、查 policy）：full auto（read-only）。</li>
<li>Step 4（執行改地址）：pre-act HITL（高風險 + 不可逆）、有 diff show、user 可以 reject。</li>
<li>Step 6（發 email）：可選 pre-act HITL（看公司風格、保守版要審 email、激進版自動發）。</li>
</ul>
<h2 id="階段-5trace-instrumentation">階段 5：Trace Instrumentation</h2>
<p>工作流上線前、先設計要記哪些資訊。<strong>Eval 跟 debug 都靠 trace、沒 trace 後面什麼都做不了</strong>。</p>
<p>每個 step 要記：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Input（完整）</td>
          <td>Debug 時要重現</td>
      </tr>
      <tr>
          <td>Output（完整）</td>
          <td>比對預期、做 regression set</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>找 bottleneck</td>
      </tr>
      <tr>
          <td>Token cost</td>
          <td>算成本</td>
      </tr>
      <tr>
          <td>Step name + version</td>
          <td>追蹤是哪個版本的 prompt / tool</td>
      </tr>
      <tr>
          <td>Decision branch</td>
          <td>Step 3 的 router 走哪邊</td>
      </tr>
      <tr>
          <td>Error（若有）</td>
          <td>結構化 error、不是 string</td>
      </tr>
  </tbody>
</table>
<p>整段 trace 要綁同一個 conversation_id、可以後面 join 起來看完整流程。</p>
<p>引用原理：<a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a>。</p>
<h2 id="階段-6eval-設計">階段 6：Eval 設計</h2>
<p>先選座標、再選工具。對本案例的每個 eval 需求、用 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 三軸座標</a> 定位。下面列的 threshold 數字（95%、80%、≥4 等）是 illustrative、實際數字隨產品 baseline、user 容忍度、業務代價而定、不是通用標準。</p>
<h3 id="eval-1step-1-抽取準不準">Eval 1：Step 1 抽取準不準</h3>
<ul>
<li><strong>三軸</strong>：Objective（有 ground truth）+ Component（測單 step）+ Quantitative（accuracy）。</li>
<li><strong>工具</strong>：寫 100 個有標註的 query、跑 step 1、看 extraction accuracy（order_id 對 + new_address 對的比例）。</li>
<li><strong>Threshold</strong>：&lt; 95% 不上線。</li>
</ul>
<h3 id="eval-2step-2-4-tool-call-行為正確">Eval 2：Step 2-4 tool call 行為正確</h3>
<ul>
<li><strong>三軸</strong>：Objective + Component + Quantitative。</li>
<li><strong>工具</strong>：mock API、給 step 2-4 各 50 個 case、看 tool call 參數對不對、返回值處理對不對。</li>
<li><strong>Threshold</strong>：100%（這是 deterministic 行為、不該有錯）。</li>
</ul>
<h3 id="eval-3step-5-解釋品質">Eval 3：Step 5 解釋品質</h3>
<ul>
<li><strong>三軸</strong>：Subjective（沒有單一正解）+ Component + Quantitative。</li>
<li><strong>工具</strong>：LLM-as-judge with rubric（clarity / helpfulness / tone）、scale 1-5、aggregate average。</li>
<li><strong>Threshold</strong>：average ≥ 4、no 1-2 比例 &lt; 5%。</li>
</ul>
<h3 id="eval-4step-6-email-品質">Eval 4：Step 6 email 品質</h3>
<ul>
<li><strong>三軸</strong>：Subjective + Component + Quantitative + 加 Qualitative human review。</li>
<li><strong>工具</strong>：LLM judge 給分 + 每週抽 20 封 human review、看是否有 hallucinate 承諾、是否符合公司 tone。</li>
<li><strong>Threshold</strong>：judge 平均 ≥ 4、human review 沒有 critical issue。</li>
</ul>
<h3 id="eval-5e2e-success-rate">Eval 5：E2E success rate</h3>
<ul>
<li><strong>三軸</strong>：Objective + End-to-end + Quantitative。</li>
<li><strong>工具</strong>：跑 200 個 representative case、看「完整完成 + user 沒申訴」的比例。</li>
<li><strong>Threshold</strong>：≥ 85% baseline、降到 &lt; 80% alert。</li>
</ul>
<h3 id="eval-6user-滿意度">Eval 6：User 滿意度</h3>
<ul>
<li><strong>三軸</strong>：Subjective + End-to-end + Quantitative。</li>
<li><strong>工具</strong>：每次互動結束顯示 thumbs up/down + optional 留言、追蹤 weekly。</li>
<li><strong>Threshold</strong>：thumbs up rate &gt; 80%、appeal rate &lt; 5%。</li>
</ul>
<h3 id="eval-7failure-mode-pattern持續做">Eval 7：Failure mode pattern（持續做）</h3>
<ul>
<li><strong>三軸</strong>：Objective / Subjective + End-to-end + Qualitative。</li>
<li><strong>工具</strong>：每週讀 50 個 sampled traces + 100% 讀 failure / appeal traces、找 emerging pattern。</li>
<li><strong>產出</strong>：bug ticket、prompt 修改 hypothesis、policy 補強 hypothesis。</li>
</ul>
<p>引用原理：</p>
<ul>
<li>三軸座標 → <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 eval design framework</a></li>
<li>LLM judge rubric → <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a></li>
<li>Trace 接 eval → <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a></li>
</ul>
<h2 id="階段-7iteration-loop">階段 7：Iteration Loop</h2>
<p>上線後、不是「等出問題」、是<strong>持續 iteration</strong>。典型 iteration cycle：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Production trace + eval result
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[Error analysis：找 emerging pattern]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   Hypothesis：哪一層有問題？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ├── Prompt 層 → 改 prompt → A/B test → 看 eval 收斂
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ├── Tool 層   → 改 tool / schema → 跑 component eval → 收斂
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ├── RAG 層    → 改 chunking / query rewriting → 跑 [retrieval recall](/llm/knowledge-cards/retrieval-recall/) → 收斂
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ├── Policy 層 → 改 deterministic rule → 跑 step 3 component eval → 收斂
</span></span><span class="line"><span class="ln">10</span><span class="cl">   └── Model 層  → 換 model → 跑全 eval set → 收斂
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">[改動進 production]
</span></span><span class="line"><span class="ln">13</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">14</span><span class="cl">[Frozen baseline 留著、新版本跟它比、漂移看得見]</span></span></code></pre></div><p>判讀「該改哪一層」的反射：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>該改的層</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Step 1 抽錯訊息</td>
          <td>Prompt / structured output schema</td>
      </tr>
      <tr>
          <td>Tool call 參數錯</td>
          <td>Prompt 內 tool description / few-shot</td>
      </tr>
      <tr>
          <td>Tool 跑掛</td>
          <td>Tool 實作（不是 LLM 問題）</td>
      </tr>
      <tr>
          <td>RAG retrieve 不到相關案例</td>
          <td>Chunking / embedding / query rewriting</td>
      </tr>
      <tr>
          <td>Policy judgment 錯</td>
          <td>Deterministic rule（不是 LLM 問題）</td>
      </tr>
      <tr>
          <td>Email tone 不對</td>
          <td>Prompt（role / few-shot）</td>
      </tr>
      <tr>
          <td>Email hallucinate 承諾</td>
          <td>Output validator（不只是 prompt）</td>
      </tr>
      <tr>
          <td>整體 latency 太高</td>
          <td>找 trace bottleneck、可能要 cache / 並行</td>
      </tr>
  </tbody>
</table>
<p>引用原理：</p>
<ul>
<li>Prompt 跟 model 層的失敗診斷 → <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> systematic vs random error</li>
<li>整體 fuzzy / deterministic 邊界判讀 → <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a></li>
</ul>
<h2 id="五個容易遺漏的設計反射">五個容易遺漏的設計反射</h2>
<p>實務上常常省略這五個反射動作、走進無收斂迭代：</p>
<h3 id="反射一先觀察再開-ide">反射一：先觀察、再開 IDE</h3>
<p>階段 1 的價值是把 task decomposition 跟真實人類工作流對齊。沒這層對齊、寫出來的 prompt 跟 tool 拆法跟 reality 偏離、三天後重做。階段 1 的兩天比階段 3 的兩週值得。對應反例：「我先寫個 prompt 試試」、跳過觀察直接寫 code。</p>
<h3 id="反射二policy-寫成-codellm-只解析意圖">反射二：Policy 寫成 code、LLM 只解析意圖</h3>
<p>判斷類規則（user tier、訂單狀態、可否操作）走 deterministic code、LLM 只負責「user 想做什麼」這層意圖抽取。這條邊界讓 debug 容易、規則更新不用 prompt iteration。對應反例：「LLM、請判斷這個訂單能不能改地址、規則如下：&hellip;」——把判斷塞進 prompt、debug 困難、規則漂移無從追蹤。對應 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a> 的「邊界用錯」反模式。</p>
<h3 id="反射三trace-是-day-1-設計">反射三：Trace 是 day-1 設計</h3>
<p>從第一天就把 input / output / latency / token / step name / decision branch / error 進 trace、綁同一個 conversation_id。Eval 跟 debug 都靠 trace、沒 trace 後面什麼都做不了。對應反例：「先讓系統跑起來、之後再加 trace」——出 bug 時 debug 從零開始、production trace 不可回溯。</p>
<h3 id="反射四deterministic-行為用-deterministic-check">反射四：Deterministic 行為用 deterministic check</h3>
<p>有 ground truth 的行為（抽取對不對、API 參數對不對、JSON schema 合不合）用 Python 函數驗證、判斷成本低、精度高。LLM judge 留給沒 ground truth 的 subjective 行為。對應反例：用 LLM judge 測「step 1 抽取對不對」——cost 翻倍、精度反而不如 deterministic check。對應 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13</a> 軸誤選一。</p>
<h3 id="反射五保留-frozen-baseline">反射五：保留 frozen baseline</h3>
<p><a href="/blog/llm/knowledge-cards/frozen-baseline/" data-link-title="Frozen baseline" data-link-desc="Eval 系統中固定特定 prompt &#43; model 當長期對照、讓行為漂移可見的標準作法">Frozen baseline</a> 是把某個特定 prompt + 特定 model 跑 production 一段時間後 freeze 起來、每次新版本都跟它比、漂移看得見。對應反例：每次只跟「上一版」比、半年後累積漂移完全不可見、「整體變好了沒」無從回答。</p>
<h2 id="跟其他章節的對應總表">跟其他章節的對應總表</h2>
<p>本案例每階段引用的原理章節彙整：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>引用章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 觀察人類工作流</td>
          <td><a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a></td>
      </tr>
      <tr>
          <td>2. 典範定位</td>
          <td><a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a></td>
      </tr>
      <tr>
          <td>3. 工作流設計（prompt / tool / RAG / HITL）</td>
          <td><a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0</a>、<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1</a>、<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a>、<a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5</a></td>
      </tr>
      <tr>
          <td>4. 結構決定（multi-call vs agent vs multi-agent）</td>
          <td><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>、<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a>、<a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8</a></td>
      </tr>
      <tr>
          <td>5. Trace instrumentation</td>
          <td><a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a></td>
      </tr>
      <tr>
          <td>6. Eval 設計</td>
          <td><a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 eval framework</a>、<a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14</a>、<a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21</a></td>
      </tr>
      <tr>
          <td>7. Iteration loop</td>
          <td><a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 光譜</a> systematic vs random error 段</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步">下一步</h2>
<p>返回：<a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四首頁</a>、或回到 <a href="/blog/llm/04-applications/hands-on/" data-link-title="4.x Hands-on：端到端案例" data-link-desc="把模組四的所有原理串成具體 case study：從 task decomposition、workflow 設計、eval 設計到 iteration loop">hands-on 索引</a>。</p>
]]></content:encoded></item><item><title>4.1 RAG 原理：retrieval + augmentation 模式</title><link>https://tarrragon.github.io/blog/llm/04-applications/rag-principles/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/rag-principles/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a>（Retrieval-Augmented Generation）的核心是「給 LLM 動態外掛一份知識、讓它在生成時拿這份知識當 context」。它的存在解的是 LLM 「靜態參數記憶」的根本限制：模型訓練完之後權重就凍結、無法存取訓練資料外的事實、無法看到 cutoff 之後發生的事、也無法存取私有資料。&lt;/p>
&lt;p>本章把 RAG 拆成不會隨工具世代消失的部分：retrieval 的本質、chunking 的取捨、失敗模式的分類、跟 fine-tuning / long context 三種路線的比較。LangChain、LlamaIndex、Vector database 選型等具體實作不在本章範圍——這些半年一個版本、教程價值低於壽命。本章寫的是「為什麼 retrieval 會這樣設計、什麼時候會失敗、什麼時候改用其他方案」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>解釋為什麼 LLM 需要外掛知識、純靠模型參數記憶解不了什麼問題。&lt;/li>
&lt;li>區分「語意相似」與「字面相似」對 retrieval 的影響、看到 retrieval 結果不理想時、判斷是哪一類失配。&lt;/li>
&lt;li>看到 chunking 參數時、知道背後的 resolution vs context 取捨。&lt;/li>
&lt;li>在「RAG / fine-tuning / long context」三者之間、依任務做合理選擇。&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼模型需要外掛知識">為什麼模型需要外掛知識&lt;/h2>
&lt;p>LLM 的參數記憶是「壓縮過的訓練資料」：權重把預訓練看過的所有文字壓進一個固定大小的數值結構、推論時用這份壓縮表示生成下一個 token。這個結構有三個天然限制：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>訓練 cutoff&lt;/strong>：模型只認識訓練資料截止前的世界、cutoff 之後發生的事完全看不見。Claude 4 cutoff 是 2026/1、2026/5 的新聞模型不知道。&lt;/li>
&lt;li>&lt;strong>私有資料缺席&lt;/strong>：訓練資料是公開來源、私有 codebase、內部文件、個人筆記都不在裡面。再強的模型也不會「知道你 repo 的內部慣例」。&lt;/li>
&lt;li>&lt;strong>長尾事實壓縮損失&lt;/strong>：訓練資料中出現很多次的常識（如 Python 語法）模型記得清楚、出現一兩次的長尾事實（如某個 obscure library 的某個 function）會被壓縮損失。&lt;/li>
&lt;/ol>
&lt;p>RAG 把這三個限制都繞開：retrieval 階段從動態外部 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">retrieval source&lt;/a>（可即時更新、可放私有資料、可保留長尾完整內容）拉出相關片段、augmentation 階段把這些片段塞進 prompt 當 context。模型不需要「知道」這份知識、只需要「讀懂」當下 prompt 裡的這份知識。&lt;/p>
&lt;p>這個結構的根本價值是「把知識從模型權重解耦」。模型負責「語言理解 + 推理」、知識負責「事實儲存 + 動態更新」、兩者各自演化：模型升級不需重建知識庫、知識更新不需重訓模型。具體 retrieval 機制依賴 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding model&lt;/a> 把文字轉成向量、用相似度衡量「相關性」。&lt;/p>
&lt;h2 id="retrieval-的核心問題語意相似-vs-字面相似">Retrieval 的核心問題：語意相似 vs 字面相似&lt;/h2>
&lt;p>Retrieval 解的是「給一個 query、找出相關的 document」這個問題、但「相關」有兩種定義：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>字面相似&lt;/strong>（lexical similarity）：query 跟 document 共用多少 keyword。傳統 search engine 用這套（如 Elasticsearch / OpenSearch 的 BM25 算法、以 keyword 出現頻率加權的傳統檢索演算法、不考慮語意）。&lt;/li>
&lt;li>&lt;strong>語意相似&lt;/strong>（semantic similarity）：query 跟 document 表達的意思接近、即使共用 keyword 少。Embedding-based retrieval 用這套。&lt;/li>
&lt;/ul>
&lt;p>兩種模式的失敗模式恰好互補：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>字面 retrieval&lt;/th>
 &lt;th>語意 retrieval&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Query 跟 document 用同樣 keyword&lt;/td>
 &lt;td>找得到（強項）&lt;/td>
 &lt;td>也找得到（多數情況）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query 用同義詞、document 用另一字&lt;/td>
 &lt;td>找不到&lt;/td>
 &lt;td>找得到（強項）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>文件用 jargon、query 用通俗描述&lt;/td>
 &lt;td>找不到&lt;/td>
 &lt;td>找得到（強項）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>兩個 document 字面像但語意不同&lt;/td>
 &lt;td>都找出來（False+）&lt;/td>
 &lt;td>通常能分開（強項）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>兩個 document 語意一樣但字面差很多&lt;/td>
 &lt;td>找不到一個（False-）&lt;/td>
 &lt;td>都找出來（強項）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Embedding 模型不熟悉的 domain&lt;/td>
 &lt;td>不受影響&lt;/td>
 &lt;td>表現崩、retrieval 像隨機（弱項）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>實務上現代 RAG 多半用「hybrid retrieval」：BM25 + embedding 分數加權合併、補單一模式的失敗模式。但理解兩者本質的差異、能解釋為什麼 retrieval 結果有時很準、有時莫名其妙。&lt;/p>
&lt;p>語意 retrieval 還帶來一個容易忽略的限制：&lt;strong>embedding 模型本身有訓練分佈&lt;/strong>。它在 Wikipedia / Common Crawl 風格的文字上表現好、在你的內部 codebase 風格上表現未必好。Domain shift 是 retrieval 失敗的常見根本原因、不是「embedding 不夠強」、是「embedding 沒見過這類資料」。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a>（Retrieval-Augmented Generation）的核心是「給 LLM 動態外掛一份知識、讓它在生成時拿這份知識當 context」。它的存在解的是 LLM 「靜態參數記憶」的根本限制：模型訓練完之後權重就凍結、無法存取訓練資料外的事實、無法看到 cutoff 之後發生的事、也無法存取私有資料。</p>
<p>本章把 RAG 拆成不會隨工具世代消失的部分：retrieval 的本質、chunking 的取捨、失敗模式的分類、跟 fine-tuning / long context 三種路線的比較。LangChain、LlamaIndex、Vector database 選型等具體實作不在本章範圍——這些半年一個版本、教程價值低於壽命。本章寫的是「為什麼 retrieval 會這樣設計、什麼時候會失敗、什麼時候改用其他方案」。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>解釋為什麼 LLM 需要外掛知識、純靠模型參數記憶解不了什麼問題。</li>
<li>區分「語意相似」與「字面相似」對 retrieval 的影響、看到 retrieval 結果不理想時、判斷是哪一類失配。</li>
<li>看到 chunking 參數時、知道背後的 resolution vs context 取捨。</li>
<li>在「RAG / fine-tuning / long context」三者之間、依任務做合理選擇。</li>
</ol>
<h2 id="為什麼模型需要外掛知識">為什麼模型需要外掛知識</h2>
<p>LLM 的參數記憶是「壓縮過的訓練資料」：權重把預訓練看過的所有文字壓進一個固定大小的數值結構、推論時用這份壓縮表示生成下一個 token。這個結構有三個天然限制：</p>
<ol>
<li><strong>訓練 cutoff</strong>：模型只認識訓練資料截止前的世界、cutoff 之後發生的事完全看不見。Claude 4 cutoff 是 2026/1、2026/5 的新聞模型不知道。</li>
<li><strong>私有資料缺席</strong>：訓練資料是公開來源、私有 codebase、內部文件、個人筆記都不在裡面。再強的模型也不會「知道你 repo 的內部慣例」。</li>
<li><strong>長尾事實壓縮損失</strong>：訓練資料中出現很多次的常識（如 Python 語法）模型記得清楚、出現一兩次的長尾事實（如某個 obscure library 的某個 function）會被壓縮損失。</li>
</ol>
<p>RAG 把這三個限制都繞開：retrieval 階段從動態外部 <a href="/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">retrieval source</a>（可即時更新、可放私有資料、可保留長尾完整內容）拉出相關片段、augmentation 階段把這些片段塞進 prompt 當 context。模型不需要「知道」這份知識、只需要「讀懂」當下 prompt 裡的這份知識。</p>
<p>這個結構的根本價值是「把知識從模型權重解耦」。模型負責「語言理解 + 推理」、知識負責「事實儲存 + 動態更新」、兩者各自演化：模型升級不需重建知識庫、知識更新不需重訓模型。具體 retrieval 機制依賴 <a href="/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding model</a> 把文字轉成向量、用相似度衡量「相關性」。</p>
<h2 id="retrieval-的核心問題語意相似-vs-字面相似">Retrieval 的核心問題：語意相似 vs 字面相似</h2>
<p>Retrieval 解的是「給一個 query、找出相關的 document」這個問題、但「相關」有兩種定義：</p>
<ul>
<li><strong>字面相似</strong>（lexical similarity）：query 跟 document 共用多少 keyword。傳統 search engine 用這套（如 Elasticsearch / OpenSearch 的 BM25 算法、以 keyword 出現頻率加權的傳統檢索演算法、不考慮語意）。</li>
<li><strong>語意相似</strong>（semantic similarity）：query 跟 document 表達的意思接近、即使共用 keyword 少。Embedding-based retrieval 用這套。</li>
</ul>
<p>兩種模式的失敗模式恰好互補：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>字面 retrieval</th>
          <th>語意 retrieval</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Query 跟 document 用同樣 keyword</td>
          <td>找得到（強項）</td>
          <td>也找得到（多數情況）</td>
      </tr>
      <tr>
          <td>Query 用同義詞、document 用另一字</td>
          <td>找不到</td>
          <td>找得到（強項）</td>
      </tr>
      <tr>
          <td>文件用 jargon、query 用通俗描述</td>
          <td>找不到</td>
          <td>找得到（強項）</td>
      </tr>
      <tr>
          <td>兩個 document 字面像但語意不同</td>
          <td>都找出來（False+）</td>
          <td>通常能分開（強項）</td>
      </tr>
      <tr>
          <td>兩個 document 語意一樣但字面差很多</td>
          <td>找不到一個（False-）</td>
          <td>都找出來（強項）</td>
      </tr>
      <tr>
          <td>Embedding 模型不熟悉的 domain</td>
          <td>不受影響</td>
          <td>表現崩、retrieval 像隨機（弱項）</td>
      </tr>
  </tbody>
</table>
<p>實務上現代 RAG 多半用「hybrid retrieval」：BM25 + embedding 分數加權合併、補單一模式的失敗模式。但理解兩者本質的差異、能解釋為什麼 retrieval 結果有時很準、有時莫名其妙。</p>
<p>語意 retrieval 還帶來一個容易忽略的限制：<strong>embedding 模型本身有訓練分佈</strong>。它在 Wikipedia / Common Crawl 風格的文字上表現好、在你的內部 codebase 風格上表現未必好。Domain shift 是 retrieval 失敗的常見根本原因、不是「embedding 不夠強」、是「embedding 沒見過這類資料」。</p>
<h2 id="chunking-的本質取捨">Chunking 的本質取捨</h2>
<p>RAG 若把整份文件當 retrieval 單位、document 太長、retrieval 拿到的太粗、實務上要先切成 chunk。Chunk 大小的選擇是 retrieval 設計最關鍵也最容易誤判的決定。</p>
<p>Chunk 太小（如每段 100 token）的失敗模式：</p>
<ul>
<li>每塊資訊不完整、retrieval 拿到的 fragment 無法獨立理解（如「他在第三章提到這個概念」、但「他」「這個概念」需要前文才解得開）。</li>
<li>跨 chunk 的語意關聯被切斷、retrieval 拿到一個 chunk 但相關的補充資訊在下個 chunk。</li>
<li>同一個概念可能切到多個 chunk、retrieval 拿其中一個是不完整論述。</li>
</ul>
<p>Chunk 太大（如每段 2000 token）的失敗模式：</p>
<ul>
<li>Retrieval 精確度低、一個 chunk 包含多個主題、相似度計算被無關內容稀釋。</li>
<li>塞進 prompt 浪費 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a>、context 利用率差。</li>
<li>重要訊號可能埋在 chunk 中間、被前後 noise 蓋過。</li>
</ul>
<p>「resolution vs context loss」是無法兩全的設計問題：細粒度精確但缺脈絡、粗粒度有脈絡但精度差。不同任務有不同最適點：</p>
<ul>
<li>問答任務（答案是短句）：偏細粒度、500 token 左右常見。</li>
<li>摘要任務（答案需要長段脈絡）：偏粗粒度、1500-2000 token 常見。</li>
<li>Code retrieval：以邏輯單位切（function、class）、不是按 token 數切。</li>
<li>規格 / 法律文件：按章節結構切、保留 hierarchy。</li>
</ul>
<p>Chunking 還有兩個常被忽略的設計維度：</p>
<ul>
<li><strong>Overlap</strong>：相鄰 chunk 之間留 10-20% overlap、避免「重要訊號剛好被切斷」。</li>
<li><strong>語意邊界 vs 字數邊界</strong>：純按字數切會穿過句子或段落中間；按段落 / heading / 邏輯單位切保留語意完整、但實作複雜。</li>
</ul>
<p>寫 code 場景的 retrieval（如 Continue.dev 的 <code>@codebase</code>、即 IDE 內把整個 codebase 當 retrieval 來源的指令）多半按邏輯單位切 code（function、class、import block）、配合 AST 解析、比純文字 chunking 收益高很多。</p>
<h2 id="retrieval-失敗的根本原因">Retrieval 失敗的根本原因</h2>
<p>Retrieval 結果不理想時、根本原因通常落在這幾類：</p>
<h3 id="語意-gap">語意 gap</h3>
<p>Query 跟 document 描述的是同一個東西、但用詞、立場、抽象層級都差很多，這是 <a href="/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap</a>。例：query 是「怎麼讓 API 跑快」、document 是「latency optimization techniques」。Embedding 模型訓練得好的話可以對齊、訓練不好或 domain 不熟就 miss。緩解：query rewriting（讓 LLM 把 query 改成更接近 document 的 phrasing）、HyDE（hypothetical document embeddings、用 LLM 生成「假設的答案」、用這個假答案的 embedding 去 retrieval）。</p>
<h3 id="超出訓練分佈">超出訓練分佈</h3>
<p>Embedding 模型對某個 domain 表現崩（如金融術語、醫療 jargon、特殊 codebase 慣例）。判讀訊號：retrieval 結果看起來「隨機」、語意相關性低。緩解：換 domain-specific embedding 模型、或退回 BM25。</p>
<h3 id="chunk-邊界穿過語意單位">Chunk 邊界穿過語意單位</h3>
<p>正確答案被切到兩個 chunk、retrieval 拿到的只是其中半邊。判讀訊號：模型回答不完整或「我看到 X 但不知道 Y」、檢查發現 Y 在相鄰 chunk。緩解：加 overlap、改用語意邊界 chunking。</p>
<h3 id="query-過短缺乏-disambiguation-context">Query 過短缺乏 disambiguation context</h3>
<p>Query 太短、模型不知道使用者真正想要什麼（如 query 「python」可以指語言、shell binary、套件、文件章節）。Retrieval 拿到的可能語意完全錯。緩解：在 retrieval 前讓 LLM expand query、加上對話歷史當 context。</p>
<h3 id="embedding-跟下游-llm-訓練分佈不一致">Embedding 跟下游 LLM 訓練分佈不一致</h3>
<p>Embedding 模型擅長把「相關」拉近、但「相關」的定義可能跟下游 LLM 「能用」的定義不同。例：embedding 把同義詞拉近、但下游 LLM 需要的是「能完整回答 query 的 document」、不是「跟 query 同義」。判讀訊號：retrieval 看起來合理但回答品質差。緩解：retrieval + re-ranker（用較強模型對 retrieval candidates 再排序）。</p>
<p>這五類失敗各有自己的訊號、根本原因不同、緩解策略也不同。Retrieval 出問題時、先用症狀分類、再對應到根因、比「換更大 embedding 模型」這種反射式修法有效得多。</p>
<h2 id="production-retrieval-pipelinehybrid--reranker">Production retrieval pipeline：hybrid + reranker</h2>
<p>實務 production RAG 多不只用單一 embedding-based retrieval、而是「<a href="/blog/llm/knowledge-cards/hybrid-search/" data-link-title="Hybrid Search" data-link-desc="把字面 retrieval（BM25）跟語意 retrieval（embedding）的結果用 RRF 等方法合併、補單一路線的盲點">hybrid search</a> + <a href="/blog/llm/knowledge-cards/reranker/" data-link-title="Reranker" data-link-desc="對 retrieval top-K 結果用 cross-encoder 重新排序的 RAG 第二階段、品質提升顯著但 latency / cost 增加">reranker</a>」兩段式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">User query
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[Stage 1: Hybrid retrieve top-50]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ├── BM25（字面）retrieve top-25      ← 抓精確 keyword、識別碼、罕見 entity
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   └── Embedding（語意）retrieve top-25  ← 抓同義詞、jargon、語意相似
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ↓ Reciprocal Rank Fusion 合併
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   top-50 candidates
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">[Stage 2: Reranker rerank to top-5]
</span></span><span class="line"><span class="ln">10</span><span class="cl">   Cross-encoder 對每對 (query, doc) 算 fine-grained relevance
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">   top-5 給 LLM</span></span></code></pre></div><p>為什麼兩段式：</p>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>強項</th>
          <th>盲點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BM25-only</td>
          <td>精確 keyword、識別碼、術語</td>
          <td>語意相似抓不到（同義詞、不同表述）</td>
      </tr>
      <tr>
          <td>Embedding-only</td>
          <td>語意相似強</td>
          <td>罕見 entity、嚴格 keyword 容易漏</td>
      </tr>
      <tr>
          <td>Hybrid（BM25 + embedding）</td>
          <td>互補、覆蓋更廣</td>
          <td>但 top-50 仍有「相關但不精確」</td>
      </tr>
      <tr>
          <td>Hybrid + reranker</td>
          <td>兩段式、最終 top-5 精確度高</td>
          <td>每對 reranker call 慢、需要 cost / latency budget</td>
      </tr>
  </tbody>
</table>
<p>何時不需要 reranker：</p>
<ul>
<li>小語料（&lt; 1000 docs）、embedding 已準</li>
<li>純 keyword 任務、BM25 已準</li>
<li>極低 latency 要求（reranker 加幾百 ms）</li>
</ul>
<p>主流 reranker：Cohere Rerank 3（SaaS）、Jina Reranker v2（OSS）、BGE Reranker（OSS、中文友善）、Voyage rerank-2。詳細選型見 <a href="/blog/llm/knowledge-cards/reranker/" data-link-title="Reranker" data-link-desc="對 retrieval top-K 結果用 cross-encoder 重新排序的 RAG 第二階段、品質提升顯著但 latency / cost 增加">reranker 卡</a>。</p>
<h2 id="chunking-策略對比">Chunking 策略對比</h2>
<p><a href="/blog/llm/knowledge-cards/chunking/" data-link-title="Chunking" data-link-desc="把長文件切成可檢索片段的設計決策：resolution vs context loss 的本質取捨">chunking 卡</a> 講概念、實務有五種主流策略：</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>機制</th>
          <th>適合</th>
          <th>失敗模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Fixed-size</td>
          <td>按 token 數固定切（如每 512 token）</td>
          <td>通用 baseline、簡單</td>
          <td>切壞句子 / 段落邊界、語意斷裂</td>
      </tr>
      <tr>
          <td>Recursive</td>
          <td>按分隔符遞迴切（先段落、再句、再固定大小）</td>
          <td>通用文字、保留段落結構</td>
          <td>仍可能切壞表格 / 程式碼</td>
      </tr>
      <tr>
          <td>Markdown header</td>
          <td>按 markdown 標題切（H1/H2/H3）</td>
          <td>文檔、技術文章、有明確 structure</td>
          <td>標題層級不一致時破</td>
      </tr>
      <tr>
          <td>Code-aware（tree-sitter）</td>
          <td>按 AST 切（function / class 邊界）</td>
          <td>程式碼 retrieval</td>
          <td>跨檔案邏輯抓不到</td>
      </tr>
      <tr>
          <td>Semantic</td>
          <td>用 embedding 判段落語意邊界、切在語意斷點</td>
          <td>知識文章、長 narrative</td>
          <td>慢、需要 pre-process embedding</td>
      </tr>
  </tbody>
</table>
<p>判讀流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">內容類型？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── 純文字 / 文章       → Recursive 或 Semantic
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── Markdown 文檔       → Markdown header（fallback recursive）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 程式碼              → Code-aware（tree-sitter）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── 混合（文章 + code） → Markdown header 主、code block 用 tree-sitter
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">└── PDF                 → 先轉 Markdown 再用 Markdown header
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Chunk 大小？
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── 一般 RAG            → 512-1024 token、overlap 50-100 token
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 短回答 / 精確匹配  → 256-512 token、更精確
</span></span><span class="line"><span class="ln">11</span><span class="cl">└── 整段理解 / 長 narrative → 1024-2048 token、配合 long context model</span></span></code></pre></div><p>實務常見錯誤：</p>
<ol>
<li><strong>拿 raw PDF 直接 chunking</strong>：PDF 結構亂、應該先轉 markdown</li>
<li><strong>過大 chunk 套小 context embedding</strong>：bge-large context limit 512、塞 2048 chunk 直接截斷</li>
<li><strong>不加 overlap</strong>：句子被切斷、retrieval 漏前後文</li>
<li><strong>混合語料用同樣 chunking</strong>：technical doc + casual blog + code 一視同仁、品質都差</li>
</ol>
<h2 id="rag-vs-fine-tuning-vs-long-context">RAG vs Fine-tuning vs Long Context</h2>
<p>「讓模型知道新東西」有三條路、解的問題層級不同：</p>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>機制</th>
          <th>適合場景</th>
          <th>不適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RAG</td>
          <td>動態外掛知識、prompt 時 retrieval</td>
          <td>動態更新、知識量大、需要 traceable</td>
          <td>需要 holistic 理解、知識高度結構化</td>
      </tr>
      <tr>
          <td>Fine-tuning</td>
          <td>改變模型權重、教新行為 / 領域知識</td>
          <td>風格 / 領域特化、有專屬 training data</td>
          <td>知識常變、訓練資料少</td>
      </tr>
      <tr>
          <td>Long context</td>
          <td>整份知識直接塞 prompt</td>
          <td>知識量小（&lt; context 上限）、單次任務</td>
          <td>知識重複用（每次塞 cost 高）</td>
      </tr>
  </tbody>
</table>
<p>三者不互斥、實際應用常組合使用：fine-tune 模型懂 domain jargon、RAG 拉動態知識、long context 在單一任務塞完整脈絡。</p>
<p>判讀「該用哪一條」的核心問題：</p>
<ul>
<li>知識會不會變？常變 → RAG。穩定 → fine-tune 或 long context。</li>
<li>知識量多大？小（&lt; 100K tokens、塞得進 <a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window</a>）→ long context。大 → RAG。</li>
<li>需要 traceable（知道答案來源）？是 → RAG（每個 chunk 有 source）。否 → fine-tune 也可。</li>
<li>是行為 / 風格還是事實？行為 → fine-tune（教模型「該怎麼回應」）。事實 → RAG（教模型「該知道什麼」）。</li>
</ul>
<p>寫 code 場景：codebase 變得快、量大、需要 traceable（要知道參考的是哪個 file）——RAG 是預設選擇。Fine-tune 在「想讓模型懂特定 codebase 風格 / 慣例」時補上、但在 codebase 變動頻繁的多數場景成本壓過收益；少數穩定大型 codebase 且風格規範強的情境（如金融 / 醫療 SDK）才值得評估 fine-tune。</p>
<h2 id="何時不適合-rag">何時不適合 RAG</h2>
<p>RAG 適用面有邊界、下列情境改用其他方案更划算：</p>
<ul>
<li><strong>需要 holistic 理解整份文件</strong>：如改寫整篇文章的風格、跨段邏輯重組。Retrieval 拿到的是片段、看不到整體。改用 long context 把整份塞進 prompt、或先讓 LLM summarize 再對 summary 操作。</li>
<li><strong>知識是高度結構化資料</strong>：如使用者資料庫、產品目錄。直接用 SQL query 比 embedding retrieval 精確得多。RAG 變成繞遠路。</li>
<li><strong>知識量小、每次都會用到</strong>：如系統 prompt 的角色設定、不變的規則。直接寫進 system prompt 比每次 retrieval 簡單。</li>
<li><strong><a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">Retrieval cost</a> 高於 long context</strong>：知識量壓過 context 但壓力不大（如 50K tokens）、retrieval pipeline 維護成本可能高於直接塞長 context。值不值得做 RAG 看 query 頻率：偶爾用就 long context、高頻用才值得建 retrieval。</li>
<li><strong>Latency 敏感場景</strong>：RAG 加一輪 retrieval、<a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a> 變長。即時補完場景可能受不了。</li>
</ul>
<p>判讀「該不該做 RAG」的反射：先問「不做 RAG 會怎樣」、再評估 RAG 的維護成本。RAG 不是免費的——需要 ingestion pipeline、embedding 服務、vector database、retrieval logic、re-ranker、評估系統。判讀 overengineering 的訊號：查詢量 &lt; 100/day、文件 &lt; 1000 份、變動頻率 &lt; 月一次、這類規模通常 long context + 簡單檔案讀取已足夠；超過這個量級才值得建完整 RAG stack。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Retrieval + augmentation 的二段式結構：retrieve 找相關內容、augment 塞進 prompt。這個 framing 跟具體實作無關。</li>
<li>語意 vs 字面相似的差異跟互補性。</li>
<li>Chunking 的 resolution vs context loss 取捨。</li>
<li>五類 retrieval 失敗模式的分類。</li>
<li>RAG / fine-tuning / long context 三條路線的判讀框架。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 embedding 模型（nomic-embed、bge、mxbai 等會持續更新）。</li>
<li>Vector database 選型（Pinecone / Weaviate / Chroma / pgvector 等市場格局會變）。Storage layer 的工程判讀（規模驅動升級、dependency 約束、index 生命週期）見 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a>。</li>
<li>Framework API（LangChain / LlamaIndex 的具體呼叫方式半年一變）。</li>
<li>最佳 chunk size 數字（隨 embedding 模型跟 LLM context 能力演化）。</li>
<li>Hybrid retrieval / re-ranker 的具體實作（會持續優化）。</li>
</ul>
<p>當這篇文章「過時」的時候、過時的是參考數字跟工具選型；retrieval 本質、失敗模式、跟其他路線的取捨判讀仍會成立。看到新 RAG 工具時、回到本章的 framing：它解的是哪類問題、它的 chunking 策略是什麼、它如何處理五類失敗模式——能很快判斷它解的問題跟你的場景是否對齊。</p>
<h2 id="下一章">下一章</h2>
<p>本章預設「有 backend」、沒 backend 的場景（個人 blog、docs site 加 RAG）的 deployment 取捨見 <a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16 靜態 / serverless RAG deployment</a>。</p>
<p>下一章：<a href="/blog/llm/04-applications/rag-retrieval-enhancements/" data-link-title="4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing" data-link-desc="Query 端增強（rewriting / expansion / HyDE）、multi-step iterative retrieval、retrieve 後的 context packing（dedup / ordering / summarization）、adaptive retrieval：vanilla RAG 不夠時的下一層工具箱">4.2 RAG 檢索增強</a>、看 vanilla RAG 不夠用時的下一層工具箱（query rewriting / HyDE / multi-step / <a href="/blog/llm/knowledge-cards/context-packing/" data-link-title="Context Packing" data-link-desc="RAG retrieve 後把 chunks 去重、排序、壓縮、標來源，再塞進 prompt 的組裝決策">context packing</a>）。把 LLM 從讀資料延伸到對外部世界做事見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use 原理</a>。Retrieval 把外部內容引入 prompt 本身就是攻擊面（同個機制讓 codebase 內容、外部文件、剪貼簿都能間接影響模型輸出）、IDE 場景的 prompt injection 判讀見 <a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3 IDE 場景的 prompt injection</a>。</p>
]]></content:encoded></item><item><title>4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing</title><link>https://tarrragon.github.io/blog/llm/04-applications/rag-retrieval-enhancements/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/rag-retrieval-enhancements/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理&lt;/a> 建立了 vanilla RAG 的骨架——chunk、embed、retrieve、prompt——並列出 hybrid + reranker 的 production 兩段式。本章往上走一層、寫&lt;strong>當 vanilla 兩段式仍不夠時、有哪些增強技術可選&lt;/strong>。&lt;/p>
&lt;p>實務上 vanilla RAG 不夠用的場景比想像多：&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap&lt;/a> 大、單次 retrieve 拿到的片段不足以回答完整問題、retrieve 結果太多塞爆 context、不該 retrieve 的問題被強制 retrieve。每個場景對應不同的增強技術。本章把這些技術寫成可挑選的工具箱、不是「全部都套」的最佳實踐。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>區分 retrieval pipeline 的四個增強層（query 端 / retrieval 端 / context 組裝端 / 控制流端）。&lt;/li>
&lt;li>對 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap&lt;/a> 選對工具（query rewriting / expansion / HyDE）。&lt;/li>
&lt;li>判斷任務需要 multi-step retrieval 還是 single-step 夠用。&lt;/li>
&lt;li>設計 retrieve 後的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-packing/" data-link-title="Context Packing" data-link-desc="RAG retrieve 後把 chunks 去重、排序、壓縮、標來源，再塞進 prompt 的組裝決策">context packing&lt;/a>（dedup、ordering、summarization）。&lt;/li>
&lt;li>設計 adaptive retrieval：什麼時候該 retrieve、什麼時候直接答。&lt;/li>
&lt;/ol>
&lt;h2 id="retrieval-pipeline-的四個增強層">Retrieval Pipeline 的四個增強層&lt;/h2>
&lt;p>Vanilla RAG 是「query → retrieve → prompt」三步。增強分四層、每層解不同問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">┌─────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">│ User query │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">└─────────┬───────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> [1. Query 端增強]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> query rewriting / expansion / HyDE / query decomposition
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> [2. Retrieval 端增強]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> hybrid search + reranker（見 4.1）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> multi-step / iterative retrieval
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> [3. Context 組裝端]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> dedup / ordering / summarization / compression
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> [4. 控制流端]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> adaptive retrieval（要不要 retrieve）/ self-RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> LLM final answer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判讀 vanilla 不夠時、先定位失敗在哪一層、再選對應工具。盲目把四層全套上、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost&lt;/a> 跟 latency 翻倍、accuracy 不一定有對應收益。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> 建立了 vanilla RAG 的骨架——chunk、embed、retrieve、prompt——並列出 hybrid + reranker 的 production 兩段式。本章往上走一層、寫<strong>當 vanilla 兩段式仍不夠時、有哪些增強技術可選</strong>。</p>
<p>實務上 vanilla RAG 不夠用的場景比想像多：<a href="/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap</a> 大、單次 retrieve 拿到的片段不足以回答完整問題、retrieve 結果太多塞爆 context、不該 retrieve 的問題被強制 retrieve。每個場景對應不同的增強技術。本章把這些技術寫成可挑選的工具箱、不是「全部都套」的最佳實踐。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>區分 retrieval pipeline 的四個增強層（query 端 / retrieval 端 / context 組裝端 / 控制流端）。</li>
<li>對 <a href="/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap</a> 選對工具（query rewriting / expansion / HyDE）。</li>
<li>判斷任務需要 multi-step retrieval 還是 single-step 夠用。</li>
<li>設計 retrieve 後的 <a href="/blog/llm/knowledge-cards/context-packing/" data-link-title="Context Packing" data-link-desc="RAG retrieve 後把 chunks 去重、排序、壓縮、標來源，再塞進 prompt 的組裝決策">context packing</a>（dedup、ordering、summarization）。</li>
<li>設計 adaptive retrieval：什麼時候該 retrieve、什麼時候直接答。</li>
</ol>
<h2 id="retrieval-pipeline-的四個增強層">Retrieval Pipeline 的四個增強層</h2>
<p>Vanilla RAG 是「query → retrieve → prompt」三步。增強分四層、每層解不同問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">┌─────────────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│ User query                                      │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">└─────────┬───────────────────────────────────────┘
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">          ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   [1. Query 端增強]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   query rewriting / expansion / HyDE / query decomposition
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">          ↓
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   [2. Retrieval 端增強]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   hybrid search + reranker（見 4.1）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   multi-step / iterative retrieval
</span></span><span class="line"><span class="ln">11</span><span class="cl">          ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">   [3. Context 組裝端]
</span></span><span class="line"><span class="ln">13</span><span class="cl">   dedup / ordering / summarization / compression
</span></span><span class="line"><span class="ln">14</span><span class="cl">          ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">   [4. 控制流端]
</span></span><span class="line"><span class="ln">16</span><span class="cl">   adaptive retrieval（要不要 retrieve）/ self-RAG
</span></span><span class="line"><span class="ln">17</span><span class="cl">          ↓
</span></span><span class="line"><span class="ln">18</span><span class="cl">   LLM final answer</span></span></code></pre></div><p>判讀 vanilla 不夠時、先定位失敗在哪一層、再選對應工具。盲目把四層全套上、<a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a> 跟 latency 翻倍、accuracy 不一定有對應收益。</p>
<h2 id="query-端增強">Query 端增強</h2>
<p>Vanilla RAG 直接用 user query 做 embedding、但 user query 往往不是「最適合 retrieve 的形狀」。Query 端增強就是在 retrieve 前重塑 query。</p>
<h3 id="query-rewriting"><a href="/blog/llm/knowledge-cards/query-rewriting/" data-link-title="Query Rewriting" data-link-desc="在 RAG 檢索前改寫使用者查詢，讓 query 更接近文件語言與索引分佈">Query rewriting</a></h3>
<p>用 LLM 把 user query 改寫成「更接近 document phrasing」的形式。</p>
<ul>
<li><strong>適用</strong>：query 口語、document 正式（如 user：「怎麼讓 API 跑快」、document：「latency optimization techniques」）。</li>
<li><strong>實作</strong>：LLM call、prompt 是「把以下 query 改寫成適合 search 的查詢句、保留語意、改用技術詞彙」。</li>
<li><strong>失效</strong>：rewriting 把意圖改偏（user 問「為什麼慢」、改成「optimization」、答非所問）。緩解：rewriting 提示要求 preserve intent、retrieve 結果回來後讓 LLM 對照原 query 判斷。</li>
<li><strong>Cost</strong>：每 query 多一個 LLM call、latency 加 200–500ms，屬於 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a>。</li>
</ul>
<h3 id="query-expansion"><a href="/blog/llm/knowledge-cards/query-expansion/" data-link-title="Query Expansion" data-link-desc="RAG 檢索前把一個 query 擴成多個語意變體，增加 coverage，再合併 retrieval 結果">Query expansion</a></h3>
<p>不改 query、而是<strong>生成多個 query 變體</strong>、一起 retrieve、合併結果。</p>
<ul>
<li><strong>適用</strong>：query 短、有多種可能解讀（「python」可指語言 / shell / 套件）、單一 query 漏 coverage。</li>
<li><strong>實作</strong>：LLM 生成 3–5 個變體（同義改寫、不同角度、不同抽象層級）、每個變體獨立 retrieve、結果用 Reciprocal Rank Fusion 合併（RRF 是 RAG 文獻常見的多 <a href="/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">retrieval source</a> 合併演算法、不在本指南範圍展開）。</li>
<li><strong>失效</strong>：變體太發散、混入無關 doc、稀釋了 top-k 的精確度。緩解：限制變體數量（3–5）、合併時對重複出現的 doc 加權。</li>
<li><strong>Cost</strong>：N 倍 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a>、但每次 retrieve 是平行、latency 不是 N 倍。</li>
</ul>
<h3 id="hydehypothetical-document-embeddings">HyDE（Hypothetical Document Embeddings）</h3>
<p><a href="/blog/llm/knowledge-cards/hyde/" data-link-title="HyDE（Hypothetical Document Embeddings）" data-link-desc="用 LLM 生成假設文件、對假文件做 embedding 去 retrieve、繞過 query-document gap 的 RAG 增強技術">HyDE</a>（<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> 提過、這裡展開）。核心觀察：<strong>query 跟 document 在 embedding 空間的距離、往往比 document 跟 document 之間更遠</strong>——這是 <a href="/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap</a> 的典型表現。</p>
<p>機制：</p>
<ol>
<li>用 LLM 對 user query 生成「一份假設的答案文件」（hallucinated document）。</li>
<li>對這份假文件做 embedding、不是對原 query。</li>
<li>用假文件 embedding 去 retrieve 真實 document。</li>
</ol>
<p>為什麼比直接 embed query 好：假文件的 phrasing、長度、結構都更接近 document 分佈、embedding 距離更可靠。<strong>重點是 retrieval、不是回答</strong>——假文件的事實正確性不重要（hallucinate 出錯誤細節 OK）、但語意 / 領域要落在對的範圍、才能拉回對的 document。</p>
<ul>
<li><strong>適用</strong>：<a href="/blog/llm/knowledge-cards/query-document-gap/" data-link-title="Query-Document Gap" data-link-desc="使用者 query 與文件語言在詞彙、形態、抽象層級或領域分佈上的落差，是 RAG retrieval miss 的常見原因">query-document gap</a> 顯著的場景（問句 vs 陳述、口語 vs 正式、抽象 vs 技術詞彙）。HyDE 原論文跨多個領域 benchmark 都有提升、不限技術 / 學術。</li>
<li><strong>失效</strong>：假文件偏離主題（LLM hallucinate 到別的領域）、retrieve 拿到完全不相關的東西。緩解：生成多個假文件取平均 embedding、或用 query + 假文件兩個 embedding 合併 retrieve。</li>
<li><strong>Cost</strong>：每 query 多一個 LLM call（生假文件）、latency 加 500ms–1s。</li>
</ul>
<h3 id="query-decomposition"><a href="/blog/llm/knowledge-cards/query-decomposition/" data-link-title="Query Decomposition" data-link-desc="把複合 query 拆成可獨立檢索的子 query，平行取得證據後再合成答案">Query decomposition</a></h3>
<p>把複雜 query 拆成幾個子 query、各自 retrieve、再合併。</p>
<ul>
<li><strong>適用</strong>：複合問題（「比較 A 跟 B 在 X 跟 Y 的差異」）、單次 retrieve 拿到的 chunk 不完整。</li>
<li><strong>跟 multi-step retrieval 的差異</strong>：decomposition 是「一次拆成 N 個 query 平行 retrieve」、multi-step 是「retrieve → 看結果 → decide 下一個 query」。前者快、後者貼近資料。</li>
<li><strong>失效</strong>：子 query 之間有依賴（後面的 query 要看前面的結果）、平行做不出來、要走 multi-step。</li>
</ul>
<h3 id="何時用哪個">何時用哪個</h3>
<table>
  <thead>
      <tr>
          <th>Query 問題</th>
          <th>對應技術</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用詞跟 document 落差大</td>
          <td>Query rewriting</td>
      </tr>
      <tr>
          <td>Query 太短 / 有歧義</td>
          <td><a href="/blog/llm/knowledge-cards/query-expansion/" data-link-title="Query Expansion" data-link-desc="RAG 檢索前把一個 query 擴成多個語意變體，增加 coverage，再合併 retrieval 結果">Query expansion</a></td>
      </tr>
      <tr>
          <td>Query-document 形態落差（問句 vs 陳述）</td>
          <td>HyDE</td>
      </tr>
      <tr>
          <td>複合問題、子問題彼此獨立</td>
          <td><a href="/blog/llm/knowledge-cards/query-decomposition/" data-link-title="Query Decomposition" data-link-desc="把複合 query 拆成可獨立檢索的子 query，平行取得證據後再合成答案">Query decomposition</a></td>
      </tr>
      <tr>
          <td>子問題彼此依賴</td>
          <td>Multi-step（下一節）</td>
      </tr>
  </tbody>
</table>
<p>實務上 query rewriting 跟 HyDE 是首選——cost 低、改 prompt 即可、收益穩。Expansion 跟 decomposition 在特定 query 形態才有顯著收益、預設不開。</p>
<h2 id="multi-step--iterative-retrieval"><a href="/blog/llm/knowledge-cards/multi-step-retrieval/" data-link-title="Multi-Step Retrieval" data-link-desc="RAG 中多輪 retrieve → 判斷 → 再 retrieve 的控制流，用來處理 multi-hop 問題">Multi-step / Iterative Retrieval</a></h2>
<p>Single-step retrieve 假設「一次 retrieve 拿到所有需要的 chunk」、但多 hop 問題（要從 doc A 找到 entity X、再從 doc B 找 X 的屬性）這個假設不成立。Multi-step retrieval 是 retrieve → LLM 判斷夠不夠 → 不夠就再 retrieve、靠 LLM 的判斷決定 retrieve 路徑。</p>
<p>機制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Initial query
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Retrieve round 1 → top-k chunks
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">LLM：「這些 chunks 夠回答嗎？若不夠、下一個該 retrieve 什麼？」
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ↓ (不夠)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Generate sub-query 2
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Retrieve round 2 → top-k chunks
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">LLM 判斷
</span></span><span class="line"><span class="ln">12</span><span class="cl">   ↓ (夠)
</span></span><span class="line"><span class="ln">13</span><span class="cl">Final answer</span></span></code></pre></div><p>跟 vanilla single-step 的差異：</p>
<ul>
<li><strong>靈活</strong>：retrieve 路徑是 query-dependent、不是固定。</li>
<li><strong>昂貴</strong>：每 round 加一個 LLM call + retrieve、latency 跟 cost 線性疊加。</li>
<li><strong>失敗模式</strong>：LLM 判斷「不夠」的能力差、無限 retrieve；或判斷「夠了」太樂觀、缺資訊還是答。</li>
</ul>
<p>對應 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 的失敗模式分類：multi-step retrieval 是 agent loop 的特例、context drift / goal drift 一樣會發生。</p>
<h3 id="multi-hop-推理的核心模式">Multi-hop 推理的核心模式</h3>
<p>Multi-hop 問題的典型 pattern：「A 跟 B 有什麼共同點」、需要先 retrieve A 的屬性、再 retrieve B 的屬性、再 compare。Single-step retrieve 不會自動把這兩組 chunk 都抓回來。</p>
<p>Multi-step retrieval 在這類問題上的 accuracy 提升明顯、但 trade-off 是 latency 翻倍以上、cost 翻倍以上。</p>
<h3 id="multi-step-划算的三條件">Multi-step 划算的三條件</h3>
<p>三條件全滿足才走 multi-step、任一不滿足就停在 single-step：</p>
<ul>
<li><strong>問題確實 multi-hop</strong>：需要 retrieve A → 推 X → retrieve B 的形態。Single-hop 問題硬套 multi-step 純增加 cost。</li>
<li><strong>Latency budget 允許</strong>：每 round 加 1-2 秒、即時 chatbot 場景通常不容許、batch 場景才行。</li>
<li><strong>有客觀停止訊號</strong>：可用 deterministic check 判斷「夠了」、不是純靠 LLM 自評。沒有停止訊號容易無限 loop。</li>
</ul>
<h2 id="context-packingretrieve-拿到後怎麼塞進-prompt"><a href="/blog/llm/knowledge-cards/context-packing/" data-link-title="Context Packing" data-link-desc="RAG retrieve 後把 chunks 去重、排序、壓縮、標來源，再塞進 prompt 的組裝決策">Context packing</a>：retrieve 拿到後怎麼塞進 prompt</h2>
<p>Retrieve 拿到 top-k chunks 後、怎麼塞進 prompt 不是「直接 concat」這麼簡單。Context 組裝端的決策影響最終 accuracy 跟 cost。</p>
<h3 id="dedup">Dedup</h3>
<p>不同 chunk 可能涵蓋同樣內容（同段文字被多個版本切到、或不同 doc 引用同一個事實）。直接 concat 浪費 context budget。</p>
<ul>
<li><strong>實作</strong>：semantic dedup（embedding 距離小於 threshold 視為重複）、或字面 dedup（hash 比對）。</li>
<li><strong>失敗</strong>：dedup 太激進、誤殺有用 chunk；dedup 不夠、context 塞重複內容。</li>
</ul>
<h3 id="ordering">Ordering</h3>
<p>塞進 prompt 的 chunk 順序影響 LLM 注意力。LLM 對 context 開頭跟結尾的注意力比中間強（<a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle</a> 現象、深度討論見 <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 long context engineering</a>）。</p>
<ul>
<li><strong>策略一：relevance ordering</strong>：最相關的 chunk 放最前 / 最後、不重要的放中間。Trade-off：依賴 retrieval 的 ranking 準。</li>
<li><strong>策略二：document order</strong>：按原文順序排（同一 doc 的 chunk 連起來）。Trade-off：保留邏輯流、但相關性散落。</li>
<li><strong>策略三：mixed</strong>：top-3 放最前、top-4 到 top-K 按 document order 放後面。</li>
</ul>
<h3 id="summarization--compression">Summarization / compression</h3>
<p>Retrieve 拿到的 chunk 太多、塞不進 context。兩條路：</p>
<ul>
<li><strong>Summarization</strong>：用 LLM 把 chunks 摘要成更短的版本、再餵主 LLM。</li>
<li><strong>Compression</strong>：用較小模型抽出 chunks 中跟 query 相關的句子、丟掉無關部分。</li>
</ul>
<p>Trade-off：</p>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>收益</th>
          <th>代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Summarization</td>
          <td>Context 大幅縮、保留意義</td>
          <td>多一個 LLM call、可能漏細節</td>
      </tr>
      <tr>
          <td>Compression</td>
          <td>保留原文片段、可 traceable</td>
          <td>抽錯關鍵句、漏關鍵資訊</td>
      </tr>
      <tr>
          <td>Naïve concat（全塞）</td>
          <td>實作最簡、不漏資訊</td>
          <td>Token cost 高、lost-in-the-middle 風險高</td>
      </tr>
  </tbody>
</table>
<h3 id="source-attribution"><a href="/blog/llm/knowledge-cards/retrieval-source/" data-link-title="Retrieval Source" data-link-desc="RAG 從哪個 corpus、index、tool 或外部系統取回內容，決定來源可信度、freshness、權限與引用責任">Source attribution</a></h3>
<p>Retrieve 拿到的 chunk 進 prompt 時、要不要標來源，是 retrieval source 的追溯責任問題。</p>
<ul>
<li><strong>標</strong>：LLM 可以引用、提升可信度、user 可以 verify。Cost：每 chunk 加幾十 token。</li>
<li><strong>不標</strong>：context 短、但 LLM 沒法引用、user 沒法追溯。</li>
</ul>
<p>實務多半標、特別是法律 / 醫療 / 學術場景。</p>
<h2 id="控制流端要不要-retrieve">控制流端：要不要 retrieve</h2>
<p>Vanilla RAG 對每個 query 都 retrieve、不問該不該。實務上有些 query 不需要外部資料（「現在幾點」「2+2 等於多少」「翻譯這段文字」）、強制 retrieve 反而塞無關 chunk 干擾，也會浪費 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a>。</p>
<h3 id="adaptive-retrieval"><a href="/blog/llm/knowledge-cards/adaptive-retrieval/" data-link-title="Adaptive Retrieval" data-link-desc="RAG 控制流中先判斷是否需要檢索，只在外部知識有價值時才 retrieve">Adaptive retrieval</a></h3>
<p>讓 LLM 自己決定 retrieve 與否。</p>
<ul>
<li><strong>路線一：predict-then-retrieve</strong>：先用小模型 / 規則判斷 query 類型（factual / reasoning / chitchat）、factual 才 retrieve。</li>
<li><strong>路線二：self-RAG</strong>：LLM 在生成過程中、輸出特殊 token 「我需要 retrieve」、觸發 retrieve、整合結果繼續生成。需要訓練過或 prompt engineered 的模型支援。</li>
</ul>
<p>判讀 adaptive retrieval 是否有用：</p>
<ul>
<li>Query 分佈：若 80% query 都需要 retrieve、adaptive 收益小、固定 retrieve 就好。</li>
<li>Query 分佈：若 query 一半 chitchat 一半 factual、adaptive 減半 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a>、收益大。</li>
</ul>
<h3 id="confidence-based-retrieval">Confidence-based retrieval</h3>
<p>LLM 先嘗試直接答、若 confidence 低（self-report 或 logits 機率）、再 retrieve。</p>
<ul>
<li><strong>適用</strong>：模型對部分 query 有把握、部分沒、想省 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a>。</li>
<li><strong>失敗</strong>：模型過度自信、low-confidence 訊號不準、該 retrieve 沒 retrieve。</li>
</ul>
<h2 id="失敗模式增強堆疊出反效果">失敗模式：增強堆疊出反效果</h2>
<p>不同層的增強可以堆、但堆過頭會反效果：</p>
<ul>
<li><strong>Query rewriting + HyDE + expansion 全開</strong>：query 端 noise 過多、retrieve 結果稀釋、accuracy 反降。</li>
<li><strong>Multi-step + reranker + summarization 全開</strong>：每 round latency 累積到使用者不能忍受。</li>
<li><strong>Adaptive + multi-step 混亂</strong>：adaptive 說「不 retrieve」、但 multi-step 又觸發 retrieve、控制流互打。</li>
</ul>
<p>設計反射動作：先確認 vanilla RAG（hybrid + reranker）的失敗在哪一層、針對性加一個增強、看是否有收益、有再加下一個。<strong>不要四層全套</strong>。</p>
<h2 id="跟相鄰章節的邊界">跟相鄰章節的邊界</h2>
<ul>
<li><strong>vs <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a></strong>：4.1 寫 vanilla 骨架跟 production 兩段式（hybrid + reranker），這章寫進一步增強。</li>
<li><strong>vs <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 long context engineering</a></strong>：long context 是「context 大到能塞」、RAG 是「context 不夠要 retrieve」、兩者是不同 regime 的策略。本章 <a href="/blog/llm/knowledge-cards/context-packing/" data-link-title="Context Packing" data-link-desc="RAG retrieve 後把 chunks 去重、排序、壓縮、標來源，再塞進 prompt 的組裝決策">context packing</a> 段的 lost-in-the-middle 是兩個 regime 的共通議題。</li>
<li><strong>vs <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a></strong>：multi-step retrieval 是 workflow pattern 在 RAG 場景的特例。</li>
</ul>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>四層增強分類（query / retrieval / context 組裝 / 控制流）的座標。</li>
<li>各 query 端技術解的核心問題（用詞落差 / 歧義 / 形態落差 / 複合問題）。</li>
<li>Multi-step retrieval 跟 single-step 的 trade-off 結構。</li>
<li>Context 組裝的三個議題（dedup / ordering / compression）。</li>
<li>「先 vanilla、再針對失敗加增強」的設計反射。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>HyDE 等特定方法的最佳實作（隨 embedding 模型演化、效果會變）。</li>
<li>Self-RAG 等需要訓練的方法（隨 base model alignment 訓練成熟、可能變預設能力）。</li>
<li>各家 reranker 跟 embedding 模型的選型（半年一個世代）。</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use 原理</a>、從「LLM 讀外部資料」延伸到「LLM 對外部世界做事」。Vanilla RAG 的骨架見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1</a>、long context 跟 RAG 的取捨見 <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11</a>、multi-step 跟 reflection 的失敗模式比對見 <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a>。</p>
]]></content:encoded></item><item><title>4.3 Tool use 原理：LLM 跟外部世界互動</title><link>https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/tool-use/" data-link-title="Tool Use" data-link-desc="LLM 透過結構化呼叫外部工具（讀檔、查資料庫、發 API request）來擴展能力的設計、function calling 跟 MCP 是常見實作">Tool use&lt;/a> 把 LLM 從「會生成文字的模型」延伸到「能參與工程系統的元件」。它的核心機制是 structured output——把 LLM 的機率分佈約束到工程系統可解析的格式、讓下游程式能對 LLM 的輸出做確定性處理。&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function calling&lt;/a> 是 structured output 的工程化形態、由模型訓練端跟推論端共同支撐。協議層級的對應（structured output / function calling / MCP 三者怎麼疊）見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議&lt;/a>。&lt;/p>
&lt;p>本章寫的是「為什麼需要 tool use」「structured output 怎麼運作」「設計工具時該如何思考副作用」這類跟具體 framework 無關的原理。OpenAI function calling spec、Anthropic tools API、JSON Schema constrained sampling 等具體格式半年一變、不在本章焦點；本章寫的是「換 spec 之後仍然成立」的設計取捨。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>解釋為什麼 LLM 需要呼叫工具、純生成解不了什麼問題。&lt;/li>
&lt;li>看到 structured output / JSON mode 設定時、知道它在限制 sampling 的哪一層。&lt;/li>
&lt;li>判讀「這個模型 tool use 為什麼表現崩」的常見根因。&lt;/li>
&lt;li>設計工具時用「副作用範圍 + 信任邊界」思考、不只看「功能對不對」。&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼-llm-需要呼叫工具">為什麼 LLM 需要呼叫工具&lt;/h2>
&lt;p>LLM 的能力邊界決定了什麼任務「光靠生成解不了」：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>即時資料&lt;/strong>：模型訓練後不知道現在發生的事。「查今天天氣」「現在股價」必須拉外部資料。&lt;/li>
&lt;li>&lt;strong>精確計算&lt;/strong>：模型對大數運算、長乘法、開根號等表現不穩、calculator 一行解決。&lt;/li>
&lt;li>&lt;strong>副作用&lt;/strong>：把檔案寫到磁碟、發 email、call API——這些是「動作」、文字本身不會觸發磁碟 / 網路 / 外部系統的狀態變更（這也是為何要設計 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/sandbox/" data-link-title="Sandbox" data-link-desc="把程式跑在受限制環境的隔離技術、限制檔案 / 網路 / 系統呼叫權限、是 tool use 跟 MCP server 副作用控制的基礎">sandbox&lt;/a> 來限制副作用範圍）。&lt;/li>
&lt;li>&lt;strong>持久化狀態&lt;/strong>：模型本身無狀態、需要外部資料庫 / vector store / file system 儲存跨對話的資料。&lt;/li>
&lt;li>&lt;strong>規模化操作&lt;/strong>：搜尋一千個 file、處理 batch、跑 SQL——這些是 deterministic、用程式跑比讓模型「逐字模擬」快幾個量級。&lt;/li>
&lt;/ul>
&lt;p>Tool use 解的不只是「能力延伸」、更是「把 LLM 跟確定性系統接起來」。沒有 tool use、LLM 只能在自己的文字宇宙裡跑；有了 tool use、它變成可以呼叫資料庫、寫檔、發網路請求的「會說話的 agent」。&lt;/p>
&lt;p>這個跨界本身帶來新的問題：模型輸出必須能被工程系統消費。自然語言對人類友善、對程式不友善——下一節要解的就是這個橋。&lt;/p>
&lt;h2 id="structured-output-是-llm-跨入工程系統的橋">Structured Output 是 LLM 跨入工程系統的橋&lt;/h2>
&lt;p>自然語言對下游 parser 不友善：同一個意思有無限種表達、模型可能加 prefix、加 disclaimer、加 markdown 格式、漏關鍵欄位。如果直接 regex 解析、會 case by case 補例外、最終 parser 比 LLM 還複雜。&lt;/p>
&lt;p>Structured output 解這個問題：把 LLM 的輸出約束到預定義的結構（JSON、YAML、XML、特定 schema）。實作機制有幾種：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Prompt-level&lt;/strong>：在 prompt 裡明確要求「請輸出 JSON、schema 是 X」。靠模型 follow instruction 的能力、不保證 100% 合法。&lt;/li>
&lt;li>&lt;strong>JSON mode / response_format&lt;/strong>：推論伺服器在 &lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling&lt;/a> 階段（從機率分佈挑下一個 token 的步驟）對每個 token 都套合法 JSON 約束、把不合法的選項機率歸零。&lt;/li>
&lt;li>&lt;strong>Grammar-constrained sampling&lt;/strong>：用 grammar（描述合法語法的形式化規則、實作上常用 BNF 或類似格式）描述合法輸出形狀、推論時逐 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token&lt;/a> 過濾。可以約束到任意嚴格的結構。&lt;/li>
&lt;li>&lt;strong>Function calling 訓練&lt;/strong>：模型訓練階段就教「該怎麼呼叫工具」、輸出格式內建在模型行為裡。&lt;/li>
&lt;/ul>
&lt;p>四種機制的層級不同：prompt-level 是「請模型自律」、JSON mode 跟 grammar 是「sampling 階段強制」、function calling 是「訓練讓模型自然」。越靠近 sampling / 訓練端的機制越穩、但實作越複雜。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/tool-use/" data-link-title="Tool Use" data-link-desc="LLM 透過結構化呼叫外部工具（讀檔、查資料庫、發 API request）來擴展能力的設計、function calling 跟 MCP 是常見實作">Tool use</a> 把 LLM 從「會生成文字的模型」延伸到「能參與工程系統的元件」。它的核心機制是 structured output——把 LLM 的機率分佈約束到工程系統可解析的格式、讓下游程式能對 LLM 的輸出做確定性處理。<a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function calling</a> 是 structured output 的工程化形態、由模型訓練端跟推論端共同支撐。協議層級的對應（structured output / function calling / MCP 三者怎麼疊）見 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a>。</p>
<p>本章寫的是「為什麼需要 tool use」「structured output 怎麼運作」「設計工具時該如何思考副作用」這類跟具體 framework 無關的原理。OpenAI function calling spec、Anthropic tools API、JSON Schema constrained sampling 等具體格式半年一變、不在本章焦點；本章寫的是「換 spec 之後仍然成立」的設計取捨。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>解釋為什麼 LLM 需要呼叫工具、純生成解不了什麼問題。</li>
<li>看到 structured output / JSON mode 設定時、知道它在限制 sampling 的哪一層。</li>
<li>判讀「這個模型 tool use 為什麼表現崩」的常見根因。</li>
<li>設計工具時用「副作用範圍 + 信任邊界」思考、不只看「功能對不對」。</li>
</ol>
<h2 id="為什麼-llm-需要呼叫工具">為什麼 LLM 需要呼叫工具</h2>
<p>LLM 的能力邊界決定了什麼任務「光靠生成解不了」：</p>
<ul>
<li><strong>即時資料</strong>：模型訓練後不知道現在發生的事。「查今天天氣」「現在股價」必須拉外部資料。</li>
<li><strong>精確計算</strong>：模型對大數運算、長乘法、開根號等表現不穩、calculator 一行解決。</li>
<li><strong>副作用</strong>：把檔案寫到磁碟、發 email、call API——這些是「動作」、文字本身不會觸發磁碟 / 網路 / 外部系統的狀態變更（這也是為何要設計 <a href="/blog/llm/knowledge-cards/sandbox/" data-link-title="Sandbox" data-link-desc="把程式跑在受限制環境的隔離技術、限制檔案 / 網路 / 系統呼叫權限、是 tool use 跟 MCP server 副作用控制的基礎">sandbox</a> 來限制副作用範圍）。</li>
<li><strong>持久化狀態</strong>：模型本身無狀態、需要外部資料庫 / vector store / file system 儲存跨對話的資料。</li>
<li><strong>規模化操作</strong>：搜尋一千個 file、處理 batch、跑 SQL——這些是 deterministic、用程式跑比讓模型「逐字模擬」快幾個量級。</li>
</ul>
<p>Tool use 解的不只是「能力延伸」、更是「把 LLM 跟確定性系統接起來」。沒有 tool use、LLM 只能在自己的文字宇宙裡跑；有了 tool use、它變成可以呼叫資料庫、寫檔、發網路請求的「會說話的 agent」。</p>
<p>這個跨界本身帶來新的問題：模型輸出必須能被工程系統消費。自然語言對人類友善、對程式不友善——下一節要解的就是這個橋。</p>
<h2 id="structured-output-是-llm-跨入工程系統的橋">Structured Output 是 LLM 跨入工程系統的橋</h2>
<p>自然語言對下游 parser 不友善：同一個意思有無限種表達、模型可能加 prefix、加 disclaimer、加 markdown 格式、漏關鍵欄位。如果直接 regex 解析、會 case by case 補例外、最終 parser 比 LLM 還複雜。</p>
<p>Structured output 解這個問題：把 LLM 的輸出約束到預定義的結構（JSON、YAML、XML、特定 schema）。實作機制有幾種：</p>
<ul>
<li><strong>Prompt-level</strong>：在 prompt 裡明確要求「請輸出 JSON、schema 是 X」。靠模型 follow instruction 的能力、不保證 100% 合法。</li>
<li><strong>JSON mode / response_format</strong>：推論伺服器在 <a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling</a> 階段（從機率分佈挑下一個 token 的步驟）對每個 token 都套合法 JSON 約束、把不合法的選項機率歸零。</li>
<li><strong>Grammar-constrained sampling</strong>：用 grammar（描述合法語法的形式化規則、實作上常用 BNF 或類似格式）描述合法輸出形狀、推論時逐 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a> 過濾。可以約束到任意嚴格的結構。</li>
<li><strong>Function calling 訓練</strong>：模型訓練階段就教「該怎麼呼叫工具」、輸出格式內建在模型行為裡。</li>
</ul>
<p>四種機制的層級不同：prompt-level 是「請模型自律」、JSON mode 跟 grammar 是「sampling 階段強制」、function calling 是「訓練讓模型自然」。越靠近 sampling / 訓練端的機制越穩、但實作越複雜。</p>
<p>理解這個 stack 的價值是：看到「模型輸出 JSON 不穩」時、知道該往哪一層下手。Prompt 寫得清楚不夠的話、要動 sampling 約束；sampling 約束打開了還不穩、要看模型本身的 tool use 訓練覆蓋度。</p>
<h2 id="function-calling-跟-free-form-generation-的取捨">Function Calling 跟 Free-form Generation 的取捨</h2>
<p>「讓 LLM 呼叫工具」有兩條路：</p>
<p><strong>Function calling</strong>（模型訓練支撐）：</p>
<ul>
<li>模型訓練時看過大量「使用者問題 → 工具呼叫格式」的範例、知道該怎麼決定要不要呼叫、傳什麼參數。</li>
<li>優點：呼叫格式穩、模型「自然」知道何時該呼叫；不需要 prompt 工程寫很長。</li>
<li>缺點：受訓練資料分佈影響大、跨模型行為不一致；只支援模型訓練過的協議格式。</li>
<li>適合：主流 / 大型模型、想用最少 prompt 工程拿穩定行為。</li>
</ul>
<p><strong>Free-form + structured output</strong>（推論時約束）：</p>
<ul>
<li>寫 prompt 描述工具、用 grammar / JSON mode 約束輸出。</li>
<li>優點：跨模型可移植、不依賴模型 fine-tune；支援任意自訂協議格式。</li>
<li>缺點：模型可能不知道「何時該呼叫」、需要 prompt 工程描述觸發條件；嚴格約束下品質可能受影響。</li>
<li>適合：跨多家 LLM 都要用同一套程式、或用較弱的模型不能依賴 function calling 訓練。</li>
</ul>
<p>實際應用常混用：主流模型走 function calling、fallback 模型走 free-form。但混用增加維護成本、小型應用挑一條走通常更簡單。</p>
<p>判讀「該用哪一條」的訊號：</p>
<ul>
<li>目標模型主流 + 規模大（&gt;30B）→ function calling、函式呼叫格式通常穩、prompt 工程量最低（注意：Llama 3 70B 等大模型也有 function calling 訓練不均的 case、實際採用前最小驗證）。</li>
<li>目標模型小或非主流 → free-form + structured output、跨模型較穩。</li>
<li>想跨 LLM 供應商可移植 → free-form + 標準化 schema、不綁特定 provider 的 function spec。</li>
</ul>
<h2 id="為什麼本地小模型-tool-use-失敗率高">為什麼本地小模型 Tool use 失敗率高</h2>
<p>寫 code 場景的本地小模型（7B、14B 級）跑 <a href="/blog/llm/knowledge-cards/tool-use/" data-link-title="Tool Use" data-link-desc="LLM 透過結構化呼叫外部工具（讀檔、查資料庫、發 API request）來擴展能力的設計、function calling 跟 MCP 是常見實作">tool use</a> 經常失敗、表現訊號清楚：</p>
<ul>
<li>呼叫格式錯（JSON 不合法、欄位拼錯）。</li>
<li>參數胡亂填（type 不對、value 超出 schema 範圍）。</li>
<li>不該呼叫時呼叫（簡單問題硬要叫 calculator）。</li>
<li>該呼叫時不呼叫（複雜計算自己算錯）。</li>
<li>連續呼叫 loop（一直叫同一個工具不收斂）。</li>
</ul>
<p>根因有兩層、訓練端跟推論端各佔一半：</p>
<p>訓練端：</p>
<ul>
<li>Tool use 範例在預訓練資料中比例低（網路文字主要是「人類對話」、不是「人類 + 工具 trace」）。</li>
<li><a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">SFT 階段</a>才大量加 tool use 資料、但 SFT 規模相對小、小模型容量有限、學不全。</li>
<li>大模型（70B+）SFT 學得進、能 generalize；小模型 SFT 容量不夠、tool use 只在訓練過的 narrow 場景表現好。</li>
</ul>
<p>推論端（同一個模型在不同推論配置下失敗率不同）：</p>
<ul>
<li><strong>Temperature 過高</strong>：分佈被拉平、原本合法 JSON 的 token 機率被攤稀、不合法 token 反而被 sample 到。Tool use 場景建議 T ≤ 0.3。</li>
<li><strong>Context 接近上限</strong>：tool schema + 歷史對話 + retrieval result 把 context 用滿、模型在末段對 schema 的記憶衰減、輸出開始飄。</li>
<li><strong>多 tool / 巢狀 schema</strong>：可選工具超過 5 個、或單個 tool 參數有 3 層巢狀時、小模型 capacity 不足以同時 hold 所有結構約束。</li>
</ul>
<p>緩解策略：</p>
<ul>
<li><strong>限制 tool 數量</strong>：把可用 tool 控制在 3-5 個內、小模型較能 handle。</li>
<li><strong>詳細 prompt 描述每個 tool</strong>：補模型訓練的不足。</li>
<li><strong>強 structured output 約束</strong>：用 grammar 強制輸出合法、把不合法輸出的機率在 sampling 階段壓到零。</li>
<li><strong>重試 + fallback</strong>：第一次失敗的話、加 error feedback 重試；多次失敗 fallback 到「不用 tool」的 free-form。</li>
<li><strong>接受能力限制</strong>：複雜 multi-step tool use 本地小模型現階段做不好、切到雲端。</li>
</ul>
<p>判讀「該不該本地跑 tool use」的反射：先看任務的 tool 複雜度，單 tool / 簡單呼叫本地堪用，multi-step / 跨多 tool 通常需要 30B+ 模型，否則失敗率高到不實用。</p>
<h2 id="工具的副作用範圍設計">工具的「副作用範圍」設計</h2>
<p>設計給 LLM 用的工具時、除了「功能對不對」、把「副作用範圍 + 可逆性」一起納入設計。</p>
<p>可逆性 spectrum、由低風險到高風險：</p>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>副作用</th>
          <th>例子</th>
          <th>適合的審查模型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>純讀、無副作用</td>
          <td>search、read file、query DB</td>
          <td>完全自動</td>
      </tr>
      <tr>
          <td>2</td>
          <td>寫 sandbox / staging</td>
          <td>write to scratch file、test environment</td>
          <td>完全自動 + 事後審</td>
      </tr>
      <tr>
          <td>3</td>
          <td>寫本地持久化</td>
          <td>edit code file、modify config</td>
          <td>step-by-step 審查</td>
      </tr>
      <tr>
          <td>4</td>
          <td>寫共享 / production</td>
          <td>git push、deploy、modify DB production</td>
          <td>強制人類確認、也是 <a href="/blog/llm/knowledge-cards/prompt-injection/" data-link-title="Prompt Injection" data-link-desc="把惡意指令藏進 LLM 會讀到的內容、誘導 LLM 跑出非開發者預期行為的攻擊類別、OWASP LLM01 列入頭號威脅">prompt injection</a> 攻擊高風險區</td>
      </tr>
      <tr>
          <td>5</td>
          <td>操作真實世界</td>
          <td>發 email、買股票、控制硬體</td>
          <td>強制人類確認 + audit、prompt injection 影響不可逆</td>
      </tr>
  </tbody>
</table>
<p>每升一級、人類審查的需求越高、agent 的自主度越低。設計工具時、把同樣功能切到不同等級可以大幅降風險：</p>
<ul>
<li>「edit file」分成「propose diff」（等級 2）+「apply diff」（等級 3）、前者自動、後者要確認。</li>
<li>「query DB」分成「SELECT」（等級 1）+「INSERT / UPDATE」（等級 4）、前者自動、後者強制確認。</li>
<li>「run shell command」是 spectrum 上分佈最廣的工具——讓 LLM 自由跑 shell 等於開放等級 1-5 全部、是常見的 over-permissioned 設計。</li>
</ul>
<p>這個 framing 跟 OS 的權限模型同概念：least privilege 套用到 LLM tool use。每個工具設計時、先問「最差情況是什麼」、再決定該不該全自動。個人 dev 場景跑本地 LLM 的 tool use / MCP server 權限判讀（檔案系統 / shell / 網路存取邊界、第三方 MCP 信任）見 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2 tool use 與 MCP server 的權限模型</a>。</p>
<h2 id="結構化輸出的失敗模式">結構化輸出的失敗模式</h2>
<p>Structured output 用得好的時候、parser 不用寫 error handling；用得不好的時候、會撞到幾種典型失敗：</p>
<ul>
<li><strong>Schema 太嚴</strong>：模型「失敗」次數多、流程卡住。例如要求 enum 只能是 5 個值、但實際 query 有第 6 種情境、模型只能硬選一個錯的。</li>
<li><strong>Schema 太寬</strong>：模型輸出歧義、下游解析失敗。例如欄位定義成 <code>string</code>、模型可能輸出空字串、null、<code>&quot;N/A&quot;</code>、<code>&quot;none&quot;</code>、各種變體。</li>
<li><strong>Free-form 跟 structured 混合</strong>：要求 JSON 但同時要求「reasoning 寫在 markdown」、模型容易把 markdown 寫進 JSON string 亂掉 escape。</li>
<li><strong>巢狀太深</strong>：超過 3 層的 JSON 巢狀、模型容易在中間漏 <code>}</code> 或 <code>,</code>。Grammar-constrained sampling 可解、純 prompt 控制就脆弱。</li>
</ul>
<p>緩解模式：</p>
<ul>
<li><strong>Schema 寬度配合 retry</strong>：先用較寬 schema、解析失敗時 retry + 把錯誤訊息餵回模型修正。</li>
<li><strong>拆步驟</strong>：把複雜 structured output 拆成多個小步驟、每步驟一個簡單 schema、累積成完整結果。</li>
<li><strong>Few-shot 範例</strong>：在 prompt 裡放 3-5 個正確輸出範例、比文字描述 schema 更穩。</li>
</ul>
<h2 id="何時不需要-tool-use">何時不需要 Tool use</h2>
<p>Tool use 的適用面有邊界、下列情境純生成已足夠、加 tool use 反而增加成本與失敗點：</p>
<ul>
<li><strong>純文字產出任務</strong>：寫文章、改寫、翻譯、摘要——輸出本身是文字、不需要副作用、tool use 沒戲。</li>
<li><strong>單一回應對話</strong>：使用者問問題、模型答問題、不需要去 fetch 外部資料時。模型參數記憶覆蓋的範圍直接回答即可。</li>
<li><strong>靠 prompt + 模型內知識能解的任務</strong>：簡單 reasoning、code generation 不需要 file I/O、解釋程式碼——這些 tool use 加進去 overhead 大於收益。</li>
<li><strong>小型 in-process 應用、tool 數量極少（1-2 個）</strong>：可能直接 if-else 比 function calling 更簡單。</li>
</ul>
<p>判讀反射：先問「不用 tool use 能不能做到」、能做就保留純生成路徑。Tool use 是 LLM 能力延伸、把「加 tool use」當「應用變高級」的標誌會踩到過度設計、single-call 能解的問題包進 tool 是常見浪費。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>「LLM 輸出需要被工程系統消費」這個 framing。</li>
<li>Structured output 是 LLM 跟工程接軌的底層機制。</li>
<li>Function calling vs free-form 的取捨判讀。</li>
<li>訓練資料分佈如何影響 tool use 能力（小模型崩的根因）。</li>
<li>副作用範圍 / 可逆性 spectrum 的設計框架。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 schema spec（OpenAI function spec → Anthropic tools API → 未來的標準化）。</li>
<li>各 framework 的 tool 註冊 API。</li>
<li>哪些模型 function calling 訓練得好（會隨新模型更新）。</li>
<li>Grammar-constrained sampling 的具體實作（llama.cpp / vLLM / Outlines 等會持續演化）。</li>
</ul>
<p>看到新 tool use 介面或新 framework 時、回到本章的 framing 評估：它支援哪一層的 structured output、訓練過哪些 protocol、對副作用範圍有沒有設計——這些問題的答案決定它在你的場景能不能用。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構原理</a>、看 LLM 自主決策的設計取捨。副作用等級跟 HITL 時機怎麼配（pre-act / mid-stream / post-hoc）見 <a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5 人機協作拓樸</a>。本地 dev 場景把 tool use 落地到「實際給 wrapper 寫權限」的 hands-on、見 <a href="/blog/llm/01-local-llm-services/hands-on/permission-boundary/" data-link-title="Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪" data-link-desc="四組對照實驗：Ollama 自己沒 FS / shell 權限、wrapper 才有；--dry-run / --confirm / --auto 三檔審查粒度的取捨">Ollama 改檔案 / 寫程式碼的權限邊界</a>；個人 dev 視角的 tool use / MCP 權限判讀見 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a>。</p>
]]></content:encoded></item><item><title>模組四：LLM 應用層原理</title><link>https://tarrragon.github.io/blog/llm/04-applications/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>狀態&lt;/strong>：大綱階段、部分章節待完成內容。&lt;/p>&lt;/blockquote>
&lt;p>本模組整理 LLM 應用層的核心原理：模型裝起來、能對話之後、要怎麼跟外部世界互動、怎麼組成可用的工作流、怎麼測它跑得對不對。模組零到模組三建立的是「模型本身」的心智模型；本模組建立的是「模型作為系統元件」的心智模型。&lt;/p>
&lt;p>寫這個模組的核心約束是「&lt;strong>只寫不會過時的部分&lt;/strong>」。LangChain、LlamaIndex、aider、Cline 等工具半年一個世代、寫具體 API 半年後就過時；但「retrieval 在做什麼」「為什麼 LLM 需要 tool use」「agent loop 為什麼會失敗」「eval 軸怎麼選」這些原理跨工具世代都成立。本模組刻意避開具體實作教學、把焦點放在跨世代的設計取捨。&lt;/p>
&lt;h2 id="章節列表">章節列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>關鍵收穫&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0&lt;/a>&lt;/td>
 &lt;td>Prompt 技術光譜&lt;/td>
 &lt;td>三軸（context / 推理 / 格式）+ 四維 trade-off + stack 判讀 + 跟 fine-tune/RAG/chaining 的邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1&lt;/a>&lt;/td>
 &lt;td>RAG 原理：retrieval + augmentation 模式&lt;/td>
 &lt;td>為什麼要外掛知識、語意相似 vs 字面相似、chunking 取捨、失敗的根本原因&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-retrieval-enhancements/" data-link-title="4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing" data-link-desc="Query 端增強（rewriting / expansion / HyDE）、multi-step iterative retrieval、retrieve 後的 context packing（dedup / ordering / summarization）、adaptive retrieval：vanilla RAG 不夠時的下一層工具箱">4.2&lt;/a>&lt;/td>
 &lt;td>RAG 檢索增強：query rewriting / HyDE / multi-step / packing&lt;/td>
 &lt;td>四層增強分類、何時 stack 何時不要、adaptive retrieval&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3&lt;/a>&lt;/td>
 &lt;td>Tool use 原理：LLM 跟外部世界互動&lt;/td>
 &lt;td>structured output 是橋、function calling 取捨、為什麼小模型 tool use 崩&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4&lt;/a>&lt;/td>
 &lt;td>Agent 架構原理&lt;/td>
 &lt;td>Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、人類審查模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5&lt;/a>&lt;/td>
 &lt;td>人機協作拓樸：何時人介入、怎麼介入&lt;/td>
 &lt;td>Centaur vs Cyborg、jagged frontier、HITL 三時機（pre-act / mid-stream / post-hoc）、避免橡皮圖章化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6&lt;/a>&lt;/td>
 &lt;td>應用層協議：function calling / structured output / MCP&lt;/td>
 &lt;td>三者層級差異、為什麼出現 MCP、組合工作流&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7&lt;/a>&lt;/td>
 &lt;td>Workflow 編排模式&lt;/td>
 &lt;td>Pipeline / router / parallel / reflection 四種基本模式、退化條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8&lt;/a>&lt;/td>
 &lt;td>Multi-Agent 拓樸&lt;/td>
 &lt;td>Flat / hierarchical / agent-as-tool、specialization gain vs orchestration overhead、特有失敗模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9&lt;/a>&lt;/td>
 &lt;td>Production 部署的資源評估原理&lt;/td>
 &lt;td>6 個 dimension：concurrency / latency / cost / storage / observability / reliability&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">4.10&lt;/a>&lt;/td>
 &lt;td>衍生產物管理原理：什麼進 git、什麼不該&lt;/td>
 &lt;td>Source / derived / external 三分類、&lt;code>.gitignore&lt;/code> 設計模式、prompt + eval 版本管理、production deployment 對接&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11&lt;/a>&lt;/td>
 &lt;td>Long context engineering&lt;/td>
 &lt;td>claimed vs effective context、lost-in-the-middle、跟 RAG 的取捨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &amp;#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12&lt;/a>&lt;/td>
 &lt;td>Embedding model 內部&lt;/td>
 &lt;td>contrastive learning、選型、MTEB、in-domain fine-tune&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13&lt;/a>&lt;/td>
 &lt;td>Eval 設計座標系：三軸、八象限&lt;/td>
 &lt;td>Objective / component / quantitative 三軸 × 工具選擇、軸誤選的訊號、eval 演化路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14&lt;/a>&lt;/td>
 &lt;td>Benchmarking 與評估方法論&lt;/td>
 &lt;td>capability vs performance、in-house benchmark、&lt;code>llama-bench&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15&lt;/a>&lt;/td>
 &lt;td>Vision in coding workflow&lt;/td>
 &lt;td>VLM 在 coding 場景的 use cases、本地 VLM 選型、IDE 整合現狀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16&lt;/a>&lt;/td>
 &lt;td>靜態 / serverless RAG deployment&lt;/td>
 &lt;td>沒 backend 的 RAG 四方案、API key 暴露、CORS、abuse、SaaS 供應鏈、跟模組六 routing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/coding-agent-harness/" data-link-title="4.17 Coding agent harness：scaffold / context engineering / subagent" data-link-desc="Coding agent 的內部設計：scaffold vs harness 分層、context budget 25% 規則、subagent 拓樸、跟 Claude Code / Cursor / Aider 的 mapping">4.17&lt;/a>&lt;/td>
 &lt;td>Coding agent harness&lt;/td>
 &lt;td>Scaffold vs harness 分層、context budget 25% 規則、subagent 設計、跟 Claude Code / Cursor / Aider 的 mapping&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/prompt-caching-engineering/" data-link-title="4.18 Prompt caching 工程實務：cost / latency 最大槓桿" data-link-desc="Prompt cache 怎麼運作、cache_control 設計、coding agent 跟 long-context 的 cache pattern、anti-pattern 跟 cache miss 訊號">4.18&lt;/a>&lt;/td>
 &lt;td>Prompt caching 工程實務&lt;/td>
 &lt;td>Cache breakpoint 設計、coding agent / RAG 場景 pattern、anti-pattern、cost / latency 槓桿&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19&lt;/a>&lt;/td>
 &lt;td>Agent memory 分層架構&lt;/td>
 &lt;td>Working / session / episodic / semantic / procedural 四層、寫入時機、retrieval 設計、失敗模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20&lt;/a>&lt;/td>
 &lt;td>LLM tracing 與 observability&lt;/td>
 &lt;td>OTel GenAI semconv、cost / latency / failure debug、trace → eval 閉環&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21&lt;/a>&lt;/td>
 &lt;td>LLM-as-Judge 評估方法&lt;/td>
 &lt;td>Rubric 設計、pairwise vs direct、三大 bias 緩解、calibration、跟 production trace 的閉環&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22&lt;/a>&lt;/td>
 &lt;td>RAG storage 工程&lt;/td>
 &lt;td>四層可替換結構、storage 演化階梯、升級判讀訊號、index 生命週期、dependency 約束&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/hands-on/" data-link-title="4.x Hands-on：端到端案例" data-link-desc="把模組四的所有原理串成具體 case study：從 task decomposition、workflow 設計、eval 設計到 iteration loop">Hands-on&lt;/a>&lt;/td>
 &lt;td>端到端案例：把所有原理串成具體 case study&lt;/td>
 &lt;td>Customer support agent 從 task decomposition 到 eval 全流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="為什麼這個順序">為什麼這個順序&lt;/h2>
&lt;p>本模組章節順序的設計脈絡：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>狀態</strong>：大綱階段、部分章節待完成內容。</p></blockquote>
<p>本模組整理 LLM 應用層的核心原理：模型裝起來、能對話之後、要怎麼跟外部世界互動、怎麼組成可用的工作流、怎麼測它跑得對不對。模組零到模組三建立的是「模型本身」的心智模型；本模組建立的是「模型作為系統元件」的心智模型。</p>
<p>寫這個模組的核心約束是「<strong>只寫不會過時的部分</strong>」。LangChain、LlamaIndex、aider、Cline 等工具半年一個世代、寫具體 API 半年後就過時；但「retrieval 在做什麼」「為什麼 LLM 需要 tool use」「agent loop 為什麼會失敗」「eval 軸怎麼選」這些原理跨工具世代都成立。本模組刻意避開具體實作教學、把焦點放在跨世代的設計取捨。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0</a></td>
          <td>Prompt 技術光譜</td>
          <td>三軸（context / 推理 / 格式）+ 四維 trade-off + stack 判讀 + 跟 fine-tune/RAG/chaining 的邊界</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1</a></td>
          <td>RAG 原理：retrieval + augmentation 模式</td>
          <td>為什麼要外掛知識、語意相似 vs 字面相似、chunking 取捨、失敗的根本原因</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/rag-retrieval-enhancements/" data-link-title="4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing" data-link-desc="Query 端增強（rewriting / expansion / HyDE）、multi-step iterative retrieval、retrieve 後的 context packing（dedup / ordering / summarization）、adaptive retrieval：vanilla RAG 不夠時的下一層工具箱">4.2</a></td>
          <td>RAG 檢索增強：query rewriting / HyDE / multi-step / packing</td>
          <td>四層增強分類、何時 stack 何時不要、adaptive retrieval</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a></td>
          <td>Tool use 原理：LLM 跟外部世界互動</td>
          <td>structured output 是橋、function calling 取捨、為什麼小模型 tool use 崩</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a></td>
          <td>Agent 架構原理</td>
          <td>Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、人類審查模型</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5</a></td>
          <td>人機協作拓樸：何時人介入、怎麼介入</td>
          <td>Centaur vs Cyborg、jagged frontier、HITL 三時機（pre-act / mid-stream / post-hoc）、避免橡皮圖章化</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6</a></td>
          <td>應用層協議：function calling / structured output / MCP</td>
          <td>三者層級差異、為什麼出現 MCP、組合工作流</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a></td>
          <td>Workflow 編排模式</td>
          <td>Pipeline / router / parallel / reflection 四種基本模式、退化條件</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8</a></td>
          <td>Multi-Agent 拓樸</td>
          <td>Flat / hierarchical / agent-as-tool、specialization gain vs orchestration overhead、特有失敗模式</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9</a></td>
          <td>Production 部署的資源評估原理</td>
          <td>6 個 dimension：concurrency / latency / cost / storage / observability / reliability</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">4.10</a></td>
          <td>衍生產物管理原理：什麼進 git、什麼不該</td>
          <td>Source / derived / external 三分類、<code>.gitignore</code> 設計模式、prompt + eval 版本管理、production deployment 對接</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11</a></td>
          <td>Long context engineering</td>
          <td>claimed vs effective context、lost-in-the-middle、跟 RAG 的取捨</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12</a></td>
          <td>Embedding model 內部</td>
          <td>contrastive learning、選型、MTEB、in-domain fine-tune</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13</a></td>
          <td>Eval 設計座標系：三軸、八象限</td>
          <td>Objective / component / quantitative 三軸 × 工具選擇、軸誤選的訊號、eval 演化路徑</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14</a></td>
          <td>Benchmarking 與評估方法論</td>
          <td>capability vs performance、in-house benchmark、<code>llama-bench</code></td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15</a></td>
          <td>Vision in coding workflow</td>
          <td>VLM 在 coding 場景的 use cases、本地 VLM 選型、IDE 整合現狀</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16</a></td>
          <td>靜態 / serverless RAG deployment</td>
          <td>沒 backend 的 RAG 四方案、API key 暴露、CORS、abuse、SaaS 供應鏈、跟模組六 routing</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/coding-agent-harness/" data-link-title="4.17 Coding agent harness：scaffold / context engineering / subagent" data-link-desc="Coding agent 的內部設計：scaffold vs harness 分層、context budget 25% 規則、subagent 拓樸、跟 Claude Code / Cursor / Aider 的 mapping">4.17</a></td>
          <td>Coding agent harness</td>
          <td>Scaffold vs harness 分層、context budget 25% 規則、subagent 設計、跟 Claude Code / Cursor / Aider 的 mapping</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/prompt-caching-engineering/" data-link-title="4.18 Prompt caching 工程實務：cost / latency 最大槓桿" data-link-desc="Prompt cache 怎麼運作、cache_control 設計、coding agent 跟 long-context 的 cache pattern、anti-pattern 跟 cache miss 訊號">4.18</a></td>
          <td>Prompt caching 工程實務</td>
          <td>Cache breakpoint 設計、coding agent / RAG 場景 pattern、anti-pattern、cost / latency 槓桿</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19</a></td>
          <td>Agent memory 分層架構</td>
          <td>Working / session / episodic / semantic / procedural 四層、寫入時機、retrieval 設計、失敗模式</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20</a></td>
          <td>LLM tracing 與 observability</td>
          <td>OTel GenAI semconv、cost / latency / failure debug、trace → eval 閉環</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21</a></td>
          <td>LLM-as-Judge 評估方法</td>
          <td>Rubric 設計、pairwise vs direct、三大 bias 緩解、calibration、跟 production trace 的閉環</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22</a></td>
          <td>RAG storage 工程</td>
          <td>四層可替換結構、storage 演化階梯、升級判讀訊號、index 生命週期、dependency 約束</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/hands-on/" data-link-title="4.x Hands-on：端到端案例" data-link-desc="把模組四的所有原理串成具體 case study：從 task decomposition、workflow 設計、eval 設計到 iteration loop">Hands-on</a></td>
          <td>端到端案例：把所有原理串成具體 case study</td>
          <td>Customer support agent 從 task decomposition 到 eval 全流程</td>
      </tr>
  </tbody>
</table>
<h2 id="為什麼這個順序">為什麼這個順序</h2>
<p>本模組章節順序的設計脈絡：</p>
<ol>
<li><strong>先 4.0 Prompt 技術光譜</strong>：within-call 增強是後續所有設計的基底、先建立「prompt 層能做什麼、邊界在哪」的座標。</li>
<li><strong>接 4.1 RAG 原理 + 4.2 RAG 檢索增強</strong>：應用層最常見的模式、把「LLM + 外部知識」這個基本組合走過一遍、概念對映到每個讀者都用過的 <code>@codebase</code> 等實務經驗。</li>
<li><strong>再 4.3 Tool use</strong>：RAG 是「LLM 讀外部資料」、Tool use 是「LLM 對外部世界做事」、兩條延伸方向自然接續。</li>
<li><strong>再 4.4 Agent 架構 + 4.5 人機協作</strong>：把 Tool use 從「單次呼叫」延伸到「自主多步」、自然進入 agent；agent 自主後立刻面對人類介入時機問題。</li>
<li><strong>再 4.6 應用層協議</strong>：前面章節涉及 function calling、structured output、MCP 等術語、本章把這三個概念放回正確的層級、避免混為一談。</li>
<li><strong>再 4.7 Workflow + 4.8 Multi-agent</strong>：上層整合、把多 LLM call 跟多 agent 組合的設計模式整理成跨 framework 不變的概念地圖。</li>
<li><strong>4.9 起進入 production / 細節</strong>：部署資源、衍生產物管理、long context、embedding 內部、eval / benchmarking、tracing、judge——每個都是 production 場景遇到的具體議題。</li>
<li><strong>最後 hands-on</strong>：把上述所有原理串成具體案例、看「實際做的時候、原理怎麼落」。</li>
</ol>
<p>每章可以單獨讀、但若你是第一次接觸 LLM 應用層、照順序讀最不容易迷路。</p>
<h2 id="跟其他模組的分工">跟其他模組的分工</h2>
<table>
  <thead>
      <tr>
          <th>模組</th>
          <th>角度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模組零</td>
          <td>操作層心智模型：模型放哪、怎麼選工具</td>
      </tr>
      <tr>
          <td>模組一</td>
          <td>工具層：具體裝 Ollama / Continue.dev</td>
      </tr>
      <tr>
          <td>模組二</td>
          <td>數學工具：線性代數、機率、最佳化</td>
      </tr>
      <tr>
          <td>模組三</td>
          <td>理論機制：模型內部運作</td>
      </tr>
      <tr>
          <td>模組四</td>
          <td><strong>應用層原理</strong>：模型作為系統元件、跟外部世界互動的設計取捨</td>
      </tr>
  </tbody>
</table>
<h2 id="適合的讀者">適合的讀者</h2>
<table>
  <thead>
      <tr>
          <th>你的背景</th>
          <th>適合程度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫過 Ollama + Continue.dev、想懂「然後呢」</td>
          <td>直接適合、從 4.0 依序讀</td>
      </tr>
      <tr>
          <td>已經試過 LangChain / aider / Cline、想看原理</td>
          <td>直接適合、本模組補足「為什麼這樣設計」的視角</td>
      </tr>
      <tr>
          <td>想做 LLM 應用開發</td>
          <td>重點讀 4.0、4.1–4.3、4.4–4.5、4.7–4.8、4.13</td>
      </tr>
      <tr>
          <td>只想用本地 LLM 寫 code、不做應用</td>
          <td>跳過本模組無妨、模組零 + 模組一已足夠</td>
      </tr>
  </tbody>
</table>
<h2 id="不在本模組內的主題">不在本模組內的主題</h2>
<ol>
<li><strong>具體 framework 教學</strong>：LangChain、LlamaIndex 等的 API 用法、隨版本變、交給官方文件。</li>
<li><strong>具體 prompt 寫法</strong>：跨模型跨任務不可遷移、本模組 4.0 寫的是 prompt 技術 landscape 的結構、不是具體寫法。</li>
<li><strong>具體 agent 工具配置</strong>：aider、Cline 等的安裝設定、隨工具版本變、見 <a href="/blog/llm/01-local-llm-services/extension-paths/" data-link-title="1.6 延伸方向：Web UI、coding agent、產圖" data-link-desc="日常路徑跑穩後可以玩的延伸：Open WebUI、aider、ComfyUI；先把基底跑穩再進階">1.6 延伸方向</a> 的入口資訊。</li>
<li><strong>訓練 / fine-tuning</strong>：屬於改變模型本身、見 <a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4 訓練流程</a>。</li>
</ol>
]]></content:encoded></item><item><title>4.4 Agent 架構原理</title><link>https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">Agent&lt;/a> 跟「對話 LLM」的根本差異在於控制流的所有權。對話 LLM 是「人類問、模型答」、每輪都由人類決定下一步；agent 是「LLM 自己決定下一步、自己呼叫工具、自己評估結果」、控制流交給模型。&lt;/p>
&lt;p>這個轉變看似只是「加個 loop」、實際上帶來新的設計問題：失敗模式從「答錯」變成「跑偏」、終止條件變成設計重點、人類審查角色從「事後讀」變成「決定何時介入」。本章把 agent 的這些核心問題拆開、寫成跨 framework 都成立的原理。aider、Cline、LangGraph、各家 Agent SDK 等具體工具不在本章焦點——這些半年一個版本、原理層級更穩。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>區分「LLM agent」跟「對話 LLM」的本質差異。&lt;/li>
&lt;li>畫出 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent-loop/" data-link-title="Agent Loop" data-link-desc="LLM agent 自我循環的工作流：LLM 規劃下一步、執行 tool、看結果、再規劃下一步、直到任務完成或停止條件觸發">agent loop&lt;/a> 的核心結構、看到新 agent 工具能對應到這個骨架。&lt;/li>
&lt;li>看到 agent 失敗時、能診斷是哪一類失敗（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-drift/" data-link-title="Context Drift" data-link-desc="Agent 長任務中累積上下文逐步偏離原始目標，導致後續行動看似合理但整體跑偏">context drift&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/goal-drift/" data-link-title="Goal Drift" data-link-desc="Agent 把子目標誤當成整體目標，提早停止或朝錯誤完成條件前進的失敗模式">目標漂移&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/tool-result-misread/" data-link-title="Tool Result Misread" data-link-desc="Agent 誤讀工具輸出，把錯誤、空結果或部分成功當成成功，導致後續步驟建立在錯誤狀態上">tool 誤判&lt;/a>）。&lt;/li>
&lt;li>判斷一個任務該用 agent 還是 single-call。&lt;/li>
&lt;/ol>
&lt;h2 id="agent-跟對話-llm的差異">Agent 跟「對話 LLM」的差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>對話 LLM&lt;/th>
 &lt;th>Agent&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>控制流&lt;/td>
 &lt;td>人類驅動、每輪 turn 獨立&lt;/td>
 &lt;td>LLM 自己驅動、跨多步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>上下文&lt;/td>
 &lt;td>每次 prompt 由人類組裝&lt;/td>
 &lt;td>自己累積跨步驟 context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工具呼叫&lt;/td>
 &lt;td>單次 / 偶爾&lt;/td>
 &lt;td>多次連續、串接結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>終止&lt;/td>
 &lt;td>使用者結束對話&lt;/td>
 &lt;td>模型自己判斷「完成」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗模式&lt;/td>
 &lt;td>答錯（人類能立刻 catch）&lt;/td>
 &lt;td>跑偏、進入錯路、long horizon 累積誤差&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>人類角色&lt;/td>
 &lt;td>主導者&lt;/td>
 &lt;td>監督者 / 審查者&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個轉變對 LLM 提出新的能力要求：&lt;/p>
&lt;ul>
&lt;li>規劃能力（把目標拆成可執行的子步驟）。&lt;/li>
&lt;li>自我評估能力（判斷子步驟做對了沒）。&lt;/li>
&lt;li>工具選擇能力（多個工具中挑對的）。&lt;/li>
&lt;li>上下文管理能力（哪些 context 該帶下去、哪些可以丟）。&lt;/li>
&lt;/ul>
&lt;p>這幾項能力是雲端旗艦模型的明顯強項、也是本地小模型的明顯弱項。理解這個能力差距、能解釋為什麼「本地寫 code 用 Continue.dev 還行、本地跑 agent 經常失敗」、不是工具問題、是模型能力 baseline 問題——背後牽涉 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling&lt;/a> 訓練深度、long context &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill&lt;/a> 痛點、規劃能力差距。&lt;/p>
&lt;h2 id="agent-loop-的核心結構">Agent Loop 的核心結構&lt;/h2>
&lt;p>所有 agent framework 不管實作怎麼包裝、骨架都是同一個 loop：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">1. 感知（Perceive）：讀當前 context、環境狀態、上一步結果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. 推理（Reason）：思考下一步該做什麼、選工具、決定參數
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3. 行動（Act）：呼叫工具、修改環境
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">4. 觀察（Observe）：解讀工具回應、更新 context
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">5. 判斷終止：done 還是回 1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個 loop 跟控制系統的 sense-plan-act 同骨架、本質是「在環境中執行目標導向行為」。Agent framework 的差異主要在每一步的具體實作：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>感知&lt;/strong>怎麼編成 prompt？要保留多少歷史？怎麼壓縮 long context？&lt;/li>
&lt;li>&lt;strong>推理&lt;/strong>用什麼模型？用 chain-of-thought 還是直接決定？要不要再拆成 plan + act？&lt;/li>
&lt;li>&lt;strong>行動&lt;/strong>支援什麼 tool？怎麼防止破壞性操作？&lt;/li>
&lt;li>&lt;strong>觀察&lt;/strong>怎麼把工具回應翻成 context？大 output 怎麼摘要？&lt;/li>
&lt;li>&lt;strong>終止&lt;/strong>怎麼判斷？模型自己說、外部 critic 判斷、step 上限、cost 上限？&lt;/li>
&lt;/ul>
&lt;p>理解這個骨架的價值是：看到新 agent framework 時、按這 5 步問就能拆解它的設計取捨；agent 跑出問題時、定位是哪一步壞掉、不是「整個 agent 壞了」。&lt;/p>
&lt;h2 id="為什麼-agent-容易失敗">為什麼 Agent 容易失敗&lt;/h2>
&lt;p>Agent 跑長時間任務時、失敗率比 single-call 高很多、根因多半落在這三類：&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">Agent</a> 跟「對話 LLM」的根本差異在於控制流的所有權。對話 LLM 是「人類問、模型答」、每輪都由人類決定下一步；agent 是「LLM 自己決定下一步、自己呼叫工具、自己評估結果」、控制流交給模型。</p>
<p>這個轉變看似只是「加個 loop」、實際上帶來新的設計問題：失敗模式從「答錯」變成「跑偏」、終止條件變成設計重點、人類審查角色從「事後讀」變成「決定何時介入」。本章把 agent 的這些核心問題拆開、寫成跨 framework 都成立的原理。aider、Cline、LangGraph、各家 Agent SDK 等具體工具不在本章焦點——這些半年一個版本、原理層級更穩。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>區分「LLM agent」跟「對話 LLM」的本質差異。</li>
<li>畫出 <a href="/blog/llm/knowledge-cards/agent-loop/" data-link-title="Agent Loop" data-link-desc="LLM agent 自我循環的工作流：LLM 規劃下一步、執行 tool、看結果、再規劃下一步、直到任務完成或停止條件觸發">agent loop</a> 的核心結構、看到新 agent 工具能對應到這個骨架。</li>
<li>看到 agent 失敗時、能診斷是哪一類失敗（<a href="/blog/llm/knowledge-cards/context-drift/" data-link-title="Context Drift" data-link-desc="Agent 長任務中累積上下文逐步偏離原始目標，導致後續行動看似合理但整體跑偏">context drift</a> / <a href="/blog/llm/knowledge-cards/goal-drift/" data-link-title="Goal Drift" data-link-desc="Agent 把子目標誤當成整體目標，提早停止或朝錯誤完成條件前進的失敗模式">目標漂移</a> / <a href="/blog/llm/knowledge-cards/tool-result-misread/" data-link-title="Tool Result Misread" data-link-desc="Agent 誤讀工具輸出，把錯誤、空結果或部分成功當成成功，導致後續步驟建立在錯誤狀態上">tool 誤判</a>）。</li>
<li>判斷一個任務該用 agent 還是 single-call。</li>
</ol>
<h2 id="agent-跟對話-llm的差異">Agent 跟「對話 LLM」的差異</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>對話 LLM</th>
          <th>Agent</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>控制流</td>
          <td>人類驅動、每輪 turn 獨立</td>
          <td>LLM 自己驅動、跨多步</td>
      </tr>
      <tr>
          <td>上下文</td>
          <td>每次 prompt 由人類組裝</td>
          <td>自己累積跨步驟 context</td>
      </tr>
      <tr>
          <td>工具呼叫</td>
          <td>單次 / 偶爾</td>
          <td>多次連續、串接結果</td>
      </tr>
      <tr>
          <td>終止</td>
          <td>使用者結束對話</td>
          <td>模型自己判斷「完成」</td>
      </tr>
      <tr>
          <td>失敗模式</td>
          <td>答錯（人類能立刻 catch）</td>
          <td>跑偏、進入錯路、long horizon 累積誤差</td>
      </tr>
      <tr>
          <td>人類角色</td>
          <td>主導者</td>
          <td>監督者 / 審查者</td>
      </tr>
  </tbody>
</table>
<p>這個轉變對 LLM 提出新的能力要求：</p>
<ul>
<li>規劃能力（把目標拆成可執行的子步驟）。</li>
<li>自我評估能力（判斷子步驟做對了沒）。</li>
<li>工具選擇能力（多個工具中挑對的）。</li>
<li>上下文管理能力（哪些 context 該帶下去、哪些可以丟）。</li>
</ul>
<p>這幾項能力是雲端旗艦模型的明顯強項、也是本地小模型的明顯弱項。理解這個能力差距、能解釋為什麼「本地寫 code 用 Continue.dev 還行、本地跑 agent 經常失敗」、不是工具問題、是模型能力 baseline 問題——背後牽涉 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling</a> 訓練深度、long context <a href="/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill</a> 痛點、規劃能力差距。</p>
<h2 id="agent-loop-的核心結構">Agent Loop 的核心結構</h2>
<p>所有 agent framework 不管實作怎麼包裝、骨架都是同一個 loop：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 感知（Perceive）：讀當前 context、環境狀態、上一步結果
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. 推理（Reason）：思考下一步該做什麼、選工具、決定參數
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. 行動（Act）：呼叫工具、修改環境
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">7</span><span class="cl">4. 觀察（Observe）：解讀工具回應、更新 context
</span></span><span class="line"><span class="ln">8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">9</span><span class="cl">5. 判斷終止：done 還是回 1</span></span></code></pre></div><p>這個 loop 跟控制系統的 sense-plan-act 同骨架、本質是「在環境中執行目標導向行為」。Agent framework 的差異主要在每一步的具體實作：</p>
<ul>
<li><strong>感知</strong>怎麼編成 prompt？要保留多少歷史？怎麼壓縮 long context？</li>
<li><strong>推理</strong>用什麼模型？用 chain-of-thought 還是直接決定？要不要再拆成 plan + act？</li>
<li><strong>行動</strong>支援什麼 tool？怎麼防止破壞性操作？</li>
<li><strong>觀察</strong>怎麼把工具回應翻成 context？大 output 怎麼摘要？</li>
<li><strong>終止</strong>怎麼判斷？模型自己說、外部 critic 判斷、step 上限、cost 上限？</li>
</ul>
<p>理解這個骨架的價值是：看到新 agent framework 時、按這 5 步問就能拆解它的設計取捨；agent 跑出問題時、定位是哪一步壞掉、不是「整個 agent 壞了」。</p>
<h2 id="為什麼-agent-容易失敗">為什麼 Agent 容易失敗</h2>
<p>Agent 跑長時間任務時、失敗率比 single-call 高很多、根因多半落在這三類：</p>
<h3 id="context-drift上下文漂移"><a href="/blog/llm/knowledge-cards/context-drift/" data-link-title="Context Drift" data-link-desc="Agent 長任務中累積上下文逐步偏離原始目標，導致後續行動看似合理但整體跑偏">Context drift</a>（上下文漂移）</h3>
<p>每輪累積的 context 偏離原始目標、後期 LLM 「忘記」要做什麼。典型表現：開始任務是「修這個 bug」、跑了 10 步後變成「重構這個 module」、再 10 步後變成「rewrite 整個 file」。每一步看起來都合理、累積起來偏離原目標。</p>
<p>根因：</p>
<ul>
<li>模型對 long context 後段的 attention 偏弱（middle-loss 現象、attention 在序列中段表現最弱、見 <a href="/blog/llm/03-theoretical-foundations/attention-mechanism/" data-link-title="3.2 Attention 機制" data-link-desc="Query / Key / Value、scaled dot-product attention、multi-head attention：Transformer 的核心運算">3.2 attention 機制</a>）。</li>
<li>子步驟產出的中間結果會被當成「新目標」、模型沿著中間結果繼續推、原始目標被擠掉。</li>
<li>沒有定期重新引用原始目標的機制。</li>
</ul>
<p>緩解：每隔 N 步把原始目標重新塞回 context、或用外部 critic 比對「現在這步跟原目標的距離」。緩解失敗的下一步：N 步重塞仍漂移、改換較大 model（context 處理能力跟模型大小強相關）；換 model 仍漂移、escalate human 或退回 single-call 拆解任務。</p>
<h3 id="goal-drift目標漂移"><a href="/blog/llm/knowledge-cards/goal-drift/" data-link-title="Goal Drift" data-link-desc="Agent 把子目標誤當成整體目標，提早停止或朝錯誤完成條件前進的失敗模式">Goal drift</a>（目標漂移）</h3>
<p>模型把子目標當主目標、執行完子目標就停下來、原始任務沒完成。例：原任務「實作 + 測試 + commit」、模型實作完就回「我寫完了」、忘了還要測 + commit。</p>
<p>根因：</p>
<ul>
<li>訓練資料中「完成單一任務」的範例多、「完成複雜 multi-step 任務」的範例相對少。</li>
<li>子任務做完的「完成感」訊號比「整個任務還沒完」訊號強。</li>
</ul>
<p>緩解：終止條件用外部驗證（test 跑通、PR 開、commit 進）、不靠模型自己說「完成了」。緩解失敗的下一步：外部驗證仍漏步、加 explicit checklist 在 system prompt、每步要求模型回報 checklist 完成狀態。</p>
<h3 id="tool-result-misread工具結果誤判"><a href="/blog/llm/knowledge-cards/tool-result-misread/" data-link-title="Tool Result Misread" data-link-desc="Agent 誤讀工具輸出，把錯誤、空結果或部分成功當成成功，導致後續步驟建立在錯誤狀態上">Tool result misread</a>（工具結果誤判）</h3>
<p>Tool 回 error 或意外結果、模型 hallucinate「成功了」繼續推進、累積錯誤越來越深。例：<code>git push</code> 失敗、模型沒讀 error message、下一步開始寫 PR description、最終提交一個沒推上去的 branch。</p>
<p>根因：</p>
<ul>
<li>模型對「無聲失敗」（tool 回的格式正常但內容是 error）解讀差。</li>
<li>部分 framework 對 tool error 處理弱、模型看不到完整 error message。</li>
</ul>
<p>緩解：tool 設計時 error 用結構化、模型容易識別；agent loop 加 explicit error handling step、看到 error signal 強制 retry 或 escalate。緩解失敗的下一步：retry 仍失敗、強制呼叫 tool 重新讀狀態（如 <code>git status</code> / <code>git log</code>）確認、避免依賴模型對 tool 結果的記憶。</p>
<h2 id="什麼任務適合-agent-vs-single-call">什麼任務適合 Agent vs Single-call</h2>
<p>Agent 適用面有邊界、判讀 framework：</p>
<p><strong>適合 agent</strong>：</p>
<ul>
<li>目標可分解成明確子步驟。</li>
<li>子步驟有客觀驗證訊號（test 跑通、file 寫入、API 200）。</li>
<li>單一 call 上下文不足、需要跨多次 tool 互動。</li>
<li>失敗可以 recover（agent 跑錯一步可以糾正）。</li>
</ul>
<p><strong>不適合 agent、改用 single-call</strong>：</p>
<ul>
<li>目標模糊探索性（沒有客觀驗證）。</li>
<li>緊湊推理任務（拆步驟反而失去全局視角）。</li>
<li>簡單可預測的任務（agent overhead 大於收益）。</li>
<li>失敗代價極高（agent 跑錯一步很難 recover）。</li>
</ul>
<p>例子對照：</p>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>該用</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>修一個 bug、跑 test 確認</td>
          <td>Agent</td>
          <td>子步驟清楚、test 是客觀驗證</td>
      </tr>
      <tr>
          <td>寫一個 function 的 docstring</td>
          <td>Single-call</td>
          <td>簡單、不需 multi-step</td>
      </tr>
      <tr>
          <td>設計新 module 架構</td>
          <td>Single-call + 人類</td>
          <td>探索性、人類審查比 agent loop 有用</td>
      </tr>
      <tr>
          <td>重構整個 codebase</td>
          <td>Agent（謹慎）</td>
          <td>子步驟多但失敗代價高、需強人類監督</td>
      </tr>
      <tr>
          <td>寫詩 / brainstorming</td>
          <td>Single-call</td>
          <td>創意任務、沒有客觀驗證、agent loop 沒意義</td>
      </tr>
      <tr>
          <td>Migrate database schema</td>
          <td>Agent + 強審查</td>
          <td>子步驟清楚但失敗代價極高、每步要人類確認</td>
      </tr>
  </tbody>
</table>
<p>「先 single-call 試、不夠再 agent」是合理的預設姿勢。Agent 是「特定問題的解法」、客觀驗證訊號 + 可承擔失敗 + 多步必要、三者俱備時用；用錯地方反而增加 cost 跟失敗率。</p>
<h3 id="灰色帶反例判讀容易誤判的情境">灰色帶反例：判讀容易誤判的情境</h3>
<p>實務上常見的「該用但失敗了」「不該用但成功了」灰色帶、列幾個典型情境跟判讀路徑：</p>
<ul>
<li><strong>目標可分解但子步驟驗證不夠客觀</strong>：如「優化這段 code 的可讀性」、可以分成「重構函式 / 加註解 / rename 變數」、但「好不好」沒客觀驗證。Agent 跑完可能改成「自己覺得好」的版本、跟使用者期待差很多。判讀：改用 single-call + 人類審查、或加明確的 lint / formatter 當客觀驗證。</li>
<li><strong>失敗代價不對稱</strong>：如 production database migration、子步驟清楚（dump → migrate → verify）、但中間失敗可能毀資料。判讀：用 agent 但強制每步要 human-in-the-loop confirm、或拆成 agent 生 migration script + 人類執行兩階段。</li>
<li><strong>子步驟之間有強依賴</strong>：如「研究某 topic → 寫摘要 → 翻譯」、agent 容易在中間步驟漂掉、累積誤差傳到最後。判讀：強依賴 chain 走 single-call sequential pipeline、不走 agent loop。</li>
<li><strong>任務在訓練分佈邊緣</strong>：如 niche domain（特定 framework、罕見語言）的 multi-step 任務、模型對該 domain 沒看過 multi-step 範例、容易在 step transition 漏 context。判讀：先 small-scale 驗證 agent 在這個 domain 表現、再決定要不要 scale up。</li>
</ul>
<h2 id="termination-條件怎麼讓-agent-知道停下來">Termination 條件：怎麼讓 Agent 知道停下來</h2>
<p>Agent 的失敗模式很多落在 termination：該停沒停（無限 loop）、不該停就停（漏做子步驟）。Termination 策略選擇是 agent 設計的核心。</p>
<p>主流 termination 機制：</p>
<ul>
<li><strong>明確 done signal</strong>：tool 回 special token、模型輸出特定 phrase。最直接、但靠模型自律、不夠 robust。</li>
<li><strong>Step 上限</strong>：跑 N 步強制停。防止無限 loop、但 N 設不對會中途砍掉。</li>
<li><strong>Cost 上限</strong>：累計 token / dollar 超過 cap 強制停。實務防錢被燒掉。</li>
<li><strong>目標達成評估</strong>：另一個 LLM 或 deterministic check 判斷「任務完成了沒」。最 robust 但 cost 高。</li>
<li><strong>外部訊號</strong>：test 跑通、檔案被寫入、人類介入。客觀、用在有明確完成判準的任務。</li>
<li><strong>人類介入</strong>：把 termination 決定交給人類。最保守、適合不可逆任務。</li>
</ul>
<p>實務上多重 termination 並用：step 上限當 safety net、cost 上限當預算守門、外部訊號當主要判準、人類介入當最終 fallback。</p>
<p>判讀 termination 設計的訊號：</p>
<ul>
<li>沒有 step / cost cap → 失控風險高。</li>
<li>完全靠模型自己說「完成」→ 漂移風險高。</li>
<li>沒有客觀驗證 → 「成功」訊號可能是 <a href="/blog/llm/knowledge-cards/hallucination/" data-link-title="Hallucination" data-link-desc="LLM 生成內容看起來合理但事實錯誤、引用不存在的來源、虛構不存在的 entity 的現象">hallucination</a>。</li>
</ul>
<h2 id="agent-跟人類審查的協作模型">Agent 跟人類審查的協作模型</h2>
<p>Agent 的自主程度跟人類審查粒度是 spectrum、不是 binary：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>人類介入時機</th>
          <th>適合任務</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Full auto</td>
          <td>跑完之後審結果</td>
          <td>可逆任務、低風險（read-only、本地實驗）</td>
      </tr>
      <tr>
          <td>Checkpoint</td>
          <td>每隔 N 步審一次</td>
          <td>中等風險、長時間任務</td>
      </tr>
      <tr>
          <td>Step-by-step approval</td>
          <td>每個 tool call 前審</td>
          <td>不可逆任務、高風險（production change）</td>
      </tr>
      <tr>
          <td>Plan first, then auto</td>
          <td>審 plan、approve 後自動跑</td>
          <td>可預測子步驟、人類確認方向後可放手</td>
      </tr>
      <tr>
          <td>Human-in-the-loop（HITL、agent 過程中插入人類審查節點）</td>
          <td>Agent 不確定時主動問人類</td>
          <td>模糊邊界、需要 domain 判斷</td>
      </tr>
  </tbody>
</table>
<p>選擇依據主要是「副作用範圍」（見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 工具的副作用範圍設計</a>）：等級 1-2 工具可以 full auto、等級 3 適合 checkpoint、等級 4-5 強制 step-by-step。不同自主度對應的 HITL 時機選擇（pre-act / mid-stream / post-hoc）跟確認流程設計（避免橡皮圖章化）見 <a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5 人機協作拓樸</a>。</p>
<p>設計 agent 時、先設想最差情況：「agent 跑偏到底會發生什麼」、再決定該用哪一級協作模型。完全自動跑 production migration 通常是 over-trust、step-by-step 跑 search 通常是 under-trust。個人 dev 把這個協作模型從本機 wrapper 演化到團隊 / production 服務時的 routing 判讀見 <a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">6.5 跨進 production 的 routing 中樞</a>。</p>
<h2 id="本地-llm-跑-agent-的特殊挑戰">本地 LLM 跑 Agent 的特殊挑戰</h2>
<p>本地 LLM 跑 agent 現階段（2026/5）失敗率明顯高於雲端、根因不只一條：</p>
<ul>
<li><strong>Tool use 訓練不足</strong>（見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a>）：小模型 tool use 本來就崩、agent 需要多次穩定 tool use、失敗率複合放大。</li>
<li><strong>Long context prefill 痛點</strong>（見 <a href="/blog/llm/00-foundations/why-llm-feels-slow/" data-link-title="0.1 為什麼 LLM 生字慢" data-link-desc="自回歸架構與記憶體頻寬瓶頸：為何即使 Mac 算力很強，本地 LLM 仍一個字一個字吐">0.1 為什麼 LLM 生字慢</a>）：Agent 每步都重新 prefill 累積 context、TTFT 越跑越長。</li>
<li><strong>規劃能力弱</strong>：雲端旗艦在 multi-step planning 上的優勢是公認的；本地 model SFT 規模有限、規劃能力跟雲端有明顯差距。</li>
<li><strong>失敗 recovery 弱</strong>：模型發現走錯路時、本地模型較容易繼續錯下去、雲端模型較會自我修正。</li>
</ul>
<p>實務啟示：本地 agent 在 2026/5 屬於「值得試、但不一定留下」的階段。對寫 code 場景的多數使用者、agent loop 的複雜任務交給雲端旗艦更划算；本地保留給 single-call 跟簡單 tool use 場景。在以下條件成立前、雲端仍占優、可作為 tripwire 重新評估：</p>
<ul>
<li>30B+ 本地模型 SWE-bench tool-use 子集達雲端旗艦的 80% 以上、且推論成本可接受</li>
<li>本地推論伺服器（Ollama / LM Studio / oMLX）穩定支援 function calling spec、跨框架行為一致</li>
<li>Apple Silicon Mac 記憶體預算夠跑「主 model + drafter + KV cache」整套 agent loop 不 swap</li>
</ul>
<p>任一條件達標時、本地 agent 的成本效益就可能翻轉、值得重新評估。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Agent vs 對話 LLM 的控制流差異 framing。</li>
<li>Agent loop 五步骨架（感知 / 推理 / 行動 / 觀察 / 終止）。</li>
<li>三類失敗模式（context drift / 目標漂移 / tool 誤判）的分類。</li>
<li>「適合 agent vs single-call」的判讀框架。</li>
<li>Termination 策略的 trade-off。</li>
<li>人類審查協作 spectrum。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 agent framework（aider / Cline / LangGraph / OpenAI Assistants 等會持續演化）。</li>
<li>模型 agent 能力（本地模型會逐步追上雲端、平衡點會移動）。</li>
<li>Tool ecosystem 跟 MCP server 普及度（見 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a>）。</li>
<li>各家 agent 的最佳 prompt / system prompt（屬於 prompt engineering、本指南不展開）。</li>
</ul>
<p>看到新 agent framework 時、回到本章的 5 步骨架、3 類失敗模式、5 種人類審查協作模型——這些 dimension 不變、看新工具能很快理解它的定位跟限制。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5 人機協作拓樸</a>、把上文的人類審查 spectrum 落到「人類什麼時候介入、怎麼介入」的三時機設計。應用層協議（function calling / structured output / MCP）的層級差異見 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6</a>。Agent 對本機資源副作用的個人 dev 權限判讀見 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a>、個人工作流跨進 production 服務時的 routing 中樞見 <a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">6.5</a>。</p>
]]></content:encoded></item><item><title>4.5 人機協作拓樸：何時人介入、怎麼介入</title><link>https://tarrragon.github.io/blog/llm/04-applications/human-ai-collaboration/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/human-ai-collaboration/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/human-in-the-loop/" data-link-title="Human-in-the-loop（HITL）" data-link-desc="人類介入 LLM 工作流的設計：三種觸發時機（pre-act / mid-stream / post-hoc）、避免橡皮圖章化的四條件">HITL（human-in-the-loop）&lt;/a> 設計的本質是&lt;strong>在「人類介入頻率」spectrum 上選位置&lt;/strong>——位置由 risk（副作用範圍 + 失敗代價）跟自動 validator 能力決定。risk 高 + validator 弱、人類介入頻率高；risk 低 + validator 強、人類介入頻率低。落點選錯就會出兩種事故：自動化過度跑 production migration 是 over-trust、每個 tool call 都要 approval 是 under-trust。&lt;/p>
&lt;p>本章寫人機協作的拓樸設計：兩種工作模式（centaur / cyborg）、能力邊界的不規則性（jagged frontier）、三種 HITL 觸發時機、跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 自主度分層&lt;/a> 的對應。這層問題是跨產品 / 跨領域通用、跟具體 framework 無關。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>區分 centaur 跟 cyborg 兩種工作模式、判斷哪種適合哪種任務。&lt;/li>
&lt;li>描述 jagged frontier、解釋為什麼「全自動」是錯題。&lt;/li>
&lt;li>在 pre-act / mid-stream / post-hoc 三個時機點選對 HITL 設計。&lt;/li>
&lt;li>設計確認流程、避免人類變橡皮圖章。&lt;/li>
&lt;li>把這層設計對應回 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構&lt;/a> 的自主度分層。&lt;/li>
&lt;/ol>
&lt;h2 id="兩種工作模式centaur-跟-cyborg">兩種工作模式：Centaur 跟 Cyborg&lt;/h2>
&lt;p>Centaur 跟 cyborg 是兩種人類跟 LLM 共事的姿態。概念起源於 Kasparov 2010 提的 advanced chess（人類 + AI 配合下棋）、HBS / UPenn / Wharton 對 BCG 顧問使用 AI 的研究把這對 framing 套到 knowledge work、觀察到兩種使用模式都存在且各有適用。&lt;/p>
&lt;h3 id="centaur-模式">Centaur 模式&lt;/h3>
&lt;p>人類把整段任務委派給 LLM、等結果回來再審。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>比喻&lt;/strong>：人馬獸——上半身人、下半身馬、清楚的職責分工。&lt;/li>
&lt;li>&lt;strong>典型場景&lt;/strong>：「寫一份這個主題的 PPT 大綱、含三個案例、按以下風格、做完給我」、LLM 跑幾分鐘、人類審結果。&lt;/li>
&lt;li>&lt;strong>適合&lt;/strong>：任務邊界清楚、人類能事先描述完整需求、結果可離線審。&lt;/li>
&lt;li>&lt;strong>失敗模式&lt;/strong>：任務描述漏細節、LLM 跑偏到沒注意、結果不能用。緩解：先給小範圍試跑、確認方向再放手。&lt;/li>
&lt;/ul>
&lt;h3 id="cyborg-模式">Cyborg 模式&lt;/h3>
&lt;p>人類跟 LLM 緊密協作、快速來回、人類隨時調整方向。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>比喻&lt;/strong>：半機械人——人類跟 LLM 融合、邊做邊改。&lt;/li>
&lt;li>&lt;strong>典型場景&lt;/strong>：寫 code 時 IDE 內 inline completion、寫文章時邊輸入邊看 LLM 建議、debug 時來回問。&lt;/li>
&lt;li>&lt;strong>適合&lt;/strong>：任務探索性、需求邊做邊浮現、無法事先完整描述。&lt;/li>
&lt;li>&lt;strong>失敗模式&lt;/strong>：頻繁打斷思路、context switch 成本高、最後產出反而比 centaur 慢。緩解：對熟悉的任務 cyborg、不熟的任務 centaur。&lt;/li>
&lt;/ul>
&lt;h3 id="該用哪種">該用哪種&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>任務性質&lt;/th>
 &lt;th>預設模式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>邊界清楚、需求可事先描述完整&lt;/td>
 &lt;td>Centaur&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>探索性、邊做邊定義&lt;/td>
 &lt;td>Cyborg&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>大量重複（如 100 篇文章）&lt;/td>
 &lt;td>Centaur&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>創意 / 設計、要看回饋微調&lt;/td>
 &lt;td>Cyborg&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高代價、要 rollback 控制&lt;/td>
 &lt;td>Centaur + 強 review&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>學生 / 個人開發更常 cyborg 工作、企業自動化更常 centaur 工作。看到一個產品設計時、問「它鼓勵 user 走 centaur 還是 cyborg」、就能判讀它的設計取向。&lt;/p>
&lt;h2 id="jagged-frontierai-能力的不規則邊界">Jagged Frontier：AI 能力的不規則邊界&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/jagged-frontier/" data-link-title="Jagged frontier" data-link-desc="AI 能力分佈不規則的 framing：某些看似簡單的任務 AI 容易壞、某些看似複雜的任務 AI 反而做得好">Jagged frontier&lt;/a> 是觀察 AI 能力分佈的 framing。直覺上「AI 能做的任務」應該是一個 smooth 的連續區、簡單的能做、難的不能。實際上不是——AI 能做的任務分佈是&lt;strong>鋸齒狀（jagged）&lt;/strong>：某些看起來難的任務 AI 做得很好、某些看起來簡單的任務 AI 反而做不好。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/human-in-the-loop/" data-link-title="Human-in-the-loop（HITL）" data-link-desc="人類介入 LLM 工作流的設計：三種觸發時機（pre-act / mid-stream / post-hoc）、避免橡皮圖章化的四條件">HITL（human-in-the-loop）</a> 設計的本質是<strong>在「人類介入頻率」spectrum 上選位置</strong>——位置由 risk（副作用範圍 + 失敗代價）跟自動 validator 能力決定。risk 高 + validator 弱、人類介入頻率高；risk 低 + validator 強、人類介入頻率低。落點選錯就會出兩種事故：自動化過度跑 production migration 是 over-trust、每個 tool call 都要 approval 是 under-trust。</p>
<p>本章寫人機協作的拓樸設計：兩種工作模式（centaur / cyborg）、能力邊界的不規則性（jagged frontier）、三種 HITL 觸發時機、跟 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 自主度分層</a> 的對應。這層問題是跨產品 / 跨領域通用、跟具體 framework 無關。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>區分 centaur 跟 cyborg 兩種工作模式、判斷哪種適合哪種任務。</li>
<li>描述 jagged frontier、解釋為什麼「全自動」是錯題。</li>
<li>在 pre-act / mid-stream / post-hoc 三個時機點選對 HITL 設計。</li>
<li>設計確認流程、避免人類變橡皮圖章。</li>
<li>把這層設計對應回 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 的自主度分層。</li>
</ol>
<h2 id="兩種工作模式centaur-跟-cyborg">兩種工作模式：Centaur 跟 Cyborg</h2>
<p>Centaur 跟 cyborg 是兩種人類跟 LLM 共事的姿態。概念起源於 Kasparov 2010 提的 advanced chess（人類 + AI 配合下棋）、HBS / UPenn / Wharton 對 BCG 顧問使用 AI 的研究把這對 framing 套到 knowledge work、觀察到兩種使用模式都存在且各有適用。</p>
<h3 id="centaur-模式">Centaur 模式</h3>
<p>人類把整段任務委派給 LLM、等結果回來再審。</p>
<ul>
<li><strong>比喻</strong>：人馬獸——上半身人、下半身馬、清楚的職責分工。</li>
<li><strong>典型場景</strong>：「寫一份這個主題的 PPT 大綱、含三個案例、按以下風格、做完給我」、LLM 跑幾分鐘、人類審結果。</li>
<li><strong>適合</strong>：任務邊界清楚、人類能事先描述完整需求、結果可離線審。</li>
<li><strong>失敗模式</strong>：任務描述漏細節、LLM 跑偏到沒注意、結果不能用。緩解：先給小範圍試跑、確認方向再放手。</li>
</ul>
<h3 id="cyborg-模式">Cyborg 模式</h3>
<p>人類跟 LLM 緊密協作、快速來回、人類隨時調整方向。</p>
<ul>
<li><strong>比喻</strong>：半機械人——人類跟 LLM 融合、邊做邊改。</li>
<li><strong>典型場景</strong>：寫 code 時 IDE 內 inline completion、寫文章時邊輸入邊看 LLM 建議、debug 時來回問。</li>
<li><strong>適合</strong>：任務探索性、需求邊做邊浮現、無法事先完整描述。</li>
<li><strong>失敗模式</strong>：頻繁打斷思路、context switch 成本高、最後產出反而比 centaur 慢。緩解：對熟悉的任務 cyborg、不熟的任務 centaur。</li>
</ul>
<h3 id="該用哪種">該用哪種</h3>
<table>
  <thead>
      <tr>
          <th>任務性質</th>
          <th>預設模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>邊界清楚、需求可事先描述完整</td>
          <td>Centaur</td>
      </tr>
      <tr>
          <td>探索性、邊做邊定義</td>
          <td>Cyborg</td>
      </tr>
      <tr>
          <td>大量重複（如 100 篇文章）</td>
          <td>Centaur</td>
      </tr>
      <tr>
          <td>創意 / 設計、要看回饋微調</td>
          <td>Cyborg</td>
      </tr>
      <tr>
          <td>高代價、要 rollback 控制</td>
          <td>Centaur + 強 review</td>
      </tr>
  </tbody>
</table>
<p>學生 / 個人開發更常 cyborg 工作、企業自動化更常 centaur 工作。看到一個產品設計時、問「它鼓勵 user 走 centaur 還是 cyborg」、就能判讀它的設計取向。</p>
<h2 id="jagged-frontierai-能力的不規則邊界">Jagged Frontier：AI 能力的不規則邊界</h2>
<p><a href="/blog/llm/knowledge-cards/jagged-frontier/" data-link-title="Jagged frontier" data-link-desc="AI 能力分佈不規則的 framing：某些看似簡單的任務 AI 容易壞、某些看似複雜的任務 AI 反而做得好">Jagged frontier</a> 是觀察 AI 能力分佈的 framing。直覺上「AI 能做的任務」應該是一個 smooth 的連續區、簡單的能做、難的不能。實際上不是——AI 能做的任務分佈是<strong>鋸齒狀（jagged）</strong>：某些看起來難的任務 AI 做得很好、某些看起來簡單的任務 AI 反而做不好。</p>
<table>
  <thead>
      <tr>
          <th>看起來簡單但 AI 容易壞</th>
          <th>看起來複雜但 AI 做得好</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>精確算術</td>
          <td>寫一段風格指定的程式碼</td>
      </tr>
      <tr>
          <td>計數（這段有幾個字）</td>
          <td>翻譯複雜技術文章</td>
      </tr>
      <tr>
          <td>嚴格遵守冷僻格式</td>
          <td>從一段文字抽取關鍵 entity</td>
      </tr>
      <tr>
          <td>引用真實的 URL</td>
          <td>解釋複雜概念</td>
      </tr>
  </tbody>
</table>
<p>這張表是 2024-2025 的觀察、<strong>frontier 會隨模型升級漂移</strong>——reasoning model + tool use 普及後、算術跟計數已經部分往「能做」那邊移、URL 也可以靠 web search tool 補救。表的價值在於 framing「能力分佈不規則」、不是把具體 4 個 case 當定論。</p>
<p>每個例子背後的失敗機制各不相同：</p>
<ul>
<li><strong>精確算術</strong>：靠符號操作、訓練資料中算術佔比小、tokenizer 把數字切成多 token 也加難度。Tool use（呼叫 calculator）能補救。</li>
<li><strong>計數</strong>：要對 input 做精確 traversal、跟 LLM 的並行 <a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">attention</a> 機制不對盤、容易少算多算。對 needle in long context 的失敗模式類比見 <a href="/blog/llm/knowledge-cards/needle-in-haystack/" data-link-title="Needle in a Haystack" data-link-desc="把一個事實藏在 long context 不同位置、測試 LLM 能否抓出來的 benchmark 方法">needle in haystack</a> 卡。</li>
<li><strong>嚴格遵守冷僻格式</strong>：format 沒在訓練分佈中見過、模型回退到「我熟悉的格式」。Constrained decoding（見 <a href="/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10</a>）能補救。</li>
<li><strong>引用真實 URL</strong>：模型沒辦法區分「真實存在」跟「看起來合理」、<a href="/blog/llm/knowledge-cards/hallucination/" data-link-title="Hallucination" data-link-desc="LLM 生成內容看起來合理但事實錯誤、引用不存在的來源、虛構不存在的 entity 的現象">hallucinate</a> 出格式對但內容假的 URL。靠 tool（web search、URL validator）才能驗證。</li>
</ul>
<p>整體看：能力分佈跟訓練資料分佈、tokenizer 行為、推論機制相關、跟人類直覺的「難易」沒對齊。這給三個實務啟示：</p>
<ul>
<li><strong>不要用「人類直覺難易」推測 AI 能力</strong>。試跑、看結果、不要預判。</li>
<li><strong>「全自動」是 over-trust 假設</strong>：frontier 鋸齒、總有些子任務落在 frontier 外、需要人介入或 tool 補。設計時要假設「有部分子任務 AI 會失敗」、而不是「都會成功」。</li>
<li><strong>失敗在 frontier 外的任務、再加 prompt iteration 通常無效</strong>：那是模型能力邊界問題、不是 prompt 問題。對應 <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> 的 systematic vs random error 診斷。</li>
</ul>
<h3 id="falling-asleep-at-the-wheelfrontier-外的隱性風險">Falling asleep at the wheel：frontier 外的隱性風險</h3>
<p>研究發現一個跟 jagged frontier 互動的人類行為模式：<strong>人類傾向不分辨任務是否在 frontier 內、對 AI 結果一律低度審查</strong>。結果 frontier 內的任務 AI 做得好、人類審不審差別不大；frontier 外的任務 AI 做得差、但人類也沒審出來、產出帶錯送出。</p>
<p>緩解：</p>
<ul>
<li><strong>明確標 frontier</strong>：對團隊 / 產品 user 標出「AI 在這類任務可靠 / 不可靠」、不要假設 user 會自己分辨。</li>
<li><strong>frontier 外的任務強制人類審查</strong>：把「該審 vs 不該審」做成 deterministic 規則、不交給 user 自由心證。</li>
<li><strong>抽樣審查</strong>：即使 frontier 內任務、隨機抽樣審查、偵測 frontier 漂移（模型升級或 prompt 變動後 frontier 可能移動）。</li>
</ul>
<h2 id="hitl-三種觸發時機">HITL 三種觸發時機</h2>
<p>人類介入的時機決定 HITL 的型態。三個時機點各有適用場景：</p>
<h3 id="pre-act動作執行前確認">Pre-act：動作執行前確認</h3>
<p>LLM 決定要做某個 action、但 action 真的執行前停下來、給人類審 + approve。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">LLM decides: 「我要刪除 user_id=123 的 record」
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">[HUMAN APPROVE?]
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ (approved)
</span></span><span class="line"><span class="ln">5</span><span class="cl">Execute deletion</span></span></code></pre></div><ul>
<li><strong>適用</strong>：不可逆 / 高代價的 action。對應 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent</a> 的「step-by-step approval」協作模型。</li>
<li><strong>失敗模式</strong>：approval 流程太頻繁、人類疲勞、最後變橡皮圖章。緩解見後面「避免橡皮圖章化」段。</li>
</ul>
<h3 id="mid-stream執行過程中介入">Mid-stream：執行過程中介入</h3>
<p>Agent loop 跑到一半、發現自己不確定、主動停下來問人類。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Agent: 「我有兩個方案、不確定哪個、請選 A 還是 B？」
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">[HUMAN PICKS]
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">Agent continues with chosen path</span></span></code></pre></div><ul>
<li><strong>適用</strong>：任務有多個合理路徑、選擇影響後續策略、不該由 agent 自決。</li>
<li><strong>跟 pre-act 的差異</strong>：pre-act 是「我準備做 X、你 approve 嗎」（agent 已決定方向）、mid-stream 是「我不確定該做什麼、你決定」（決策權交給人類）。</li>
<li><strong>失敗模式</strong>：agent 不知道自己該不知道（unknown unknowns）、該問沒問、自己亂走。緩解：在 prompt 內 enumerate 常見的「該問人類」情境、降低 agent 自決的範圍。</li>
</ul>
<h3 id="post-hoc事後申訴--校正">Post-hoc：事後申訴 / 校正</h3>
<p>Agent 已執行、結果交付、user 看完後可以申訴 / 校正。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Agent produces result → User sees result
</span></span><span class="line"><span class="ln">2</span><span class="cl">                              ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">                       [USER APPEALS?]
</span></span><span class="line"><span class="ln">4</span><span class="cl">                              ↓ (yes)
</span></span><span class="line"><span class="ln">5</span><span class="cl">                       Human reviews + adjusts
</span></span><span class="line"><span class="ln">6</span><span class="cl">                              ↓
</span></span><span class="line"><span class="ln">7</span><span class="cl">                       Feedback loop → 改 prompt / fine-tune</span></span></code></pre></div><ul>
<li><strong>適用</strong>：行為層次的細節調整、評分類任務（如自動打分後 user 申訴）、預先審不可行的場景。</li>
<li><strong>跟 pre/mid 的差異</strong>：post-hoc 不擋執行流、執行完才介入；前兩者擋在執行前 / 執行中。</li>
<li><strong>典型例子</strong>：自動評分系統的 appeal 流程——LLM 打分完、user 對分數有異議時、走人類審查、結果不只改這次分數、還回饋進系統改善後續評分。</li>
<li><strong>失敗模式</strong>：appeal rate 過高（系統信任度差）、或 appeal rate 過低（user 不知道可以申訴 / 申訴成本高）、回饋訊號失真。</li>
</ul>
<h3 id="三個時機的選擇">三個時機的選擇</h3>
<table>
  <thead>
      <tr>
          <th>時機</th>
          <th>適合任務</th>
          <th>不適合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pre-act</td>
          <td>高代價、不可逆、副作用範圍大</td>
          <td>高頻率動作（會把人類淹死）</td>
      </tr>
      <tr>
          <td>Mid-stream</td>
          <td>路徑分歧、需要 domain judgment</td>
          <td>路徑可由 agent 自決的低代價任務</td>
      </tr>
      <tr>
          <td>Post-hoc</td>
          <td>評分 / 評估、低代價、user 數量大</td>
          <td>不可逆動作（事後 appeal 來不及）</td>
      </tr>
  </tbody>
</table>
<p>實務多重組合：pre-act 擋高代價、mid-stream 處理 agent 的不確定性、post-hoc 收 user 回饋改善系統。<strong>三者各自處理不同 risk class、不互斥</strong>。</p>
<h2 id="有效-hitl-的四個設計條件">有效 HITL 的四個設計條件</h2>
<p>HITL 要真的擋住失敗、不退化成 rubber-stamp approval、設計上要滿足四個條件。每個條件對應一個常見退化模式、可以同時當 checklist 用。</p>
<h3 id="條件一分級不同-risk-走不同-gate">條件一：分級、不同 risk 走不同 gate</h3>
<p>高 risk 動作（push、deploy、production change）強制 step-by-step approval；中等 risk（檔案寫入、本機 commit）每 N 步 checkpoint；低 risk（read-only、本機 sandbox）full auto。對應 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 tool use 副作用範圍</a> 的等級分類。</p>
<p>對應反例：每個 tool call 都要 approve、不分高低代價、user 每天按 100 次 approve、按到下意識、根本沒看內容。</p>
<h3 id="條件二approval-ui-強制-show-diff">條件二：approval UI 強制 show diff</h3>
<p>審查的具體內容（準備寫的檔案內容、準備執行的 SQL、準備發的 email 草稿）必須在 approval UI 上呈現、user 看得到才能做出有意義的判斷。</p>
<p>對應反例：「approve this action?」按鈕、但 user 看不到 action 的具體內容、只能盲簽。沒有 diff 就沒有審查、不要假裝有審查。</p>
<h3 id="條件三reject-有明確-fallback-路徑">條件三：reject 有明確 fallback 路徑</h3>
<p>User reject 後 agent 該怎麼處理（換方案、停下來、escalate）要在設計時確定、不能讓「reject 等同流程斷」。</p>
<p>對應反例：只能 approve、reject 的話 agent 不知道怎麼辦、user 怕 reject 後續流程斷、就一律按 approve、HITL 失去意義。</p>
<h3 id="條件四approval-訊號要回饋進系統">條件四：approval 訊號要回饋進系統</h3>
<p>User 的 approve / reject pattern 進 trace、定期 analyze、把「總是 approve 的動作」自動降級、「總是 reject 的動作」進 prompt 改變 agent 預設行為。</p>
<p>對應反例：User 一直 approve / reject、但訊號沒回饋、agent 下次還是問一樣的問題、user 疲勞累積。</p>
<h2 id="跟-agent-自主度分層的對應">跟 Agent 自主度分層的對應</h2>
<p><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 列了五種人類審查協作模型：full auto、checkpoint、step-by-step approval、plan first then auto、human-in-the-loop。本章三種 HITL 時機跟這五種協作模型的對應：</p>
<table>
  <thead>
      <tr>
          <th>Agent 自主度分層</th>
          <th>主要 HITL 時機</th>
          <th>設計重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Full auto</td>
          <td>Post-hoc</td>
          <td>Appeal 流程、抽樣審查、distribution monitoring</td>
      </tr>
      <tr>
          <td>Checkpoint</td>
          <td>Pre-act（每 N 步）</td>
          <td>分級 approval、diff 必須 show</td>
      </tr>
      <tr>
          <td>Step-by-step approval</td>
          <td>Pre-act（每步）</td>
          <td>UI 簡潔、reject 路徑清楚、避免疲勞</td>
      </tr>
      <tr>
          <td>Plan first, then auto</td>
          <td>Pre-act（plan 階段）+ Post-hoc</td>
          <td>Plan diff + 執行後審查</td>
      </tr>
      <tr>
          <td>Human-in-the-loop（mid-stream）</td>
          <td>Mid-stream</td>
          <td>Agent 知道自己該問人類、不該問的事不問</td>
      </tr>
  </tbody>
</table>
<p>選哪一層、看 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 工具副作用範圍</a> 等級：等級 1-2 用 full auto + post-hoc、等級 3 用 checkpoint、等級 4-5 強制 step-by-step。</p>
<h2 id="跟-fuzzy-engineering-典範的關係">跟 Fuzzy Engineering 典範的關係</h2>
<p><a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 Deterministic vs Fuzzy Engineering</a> 講 fuzzy 邊界要包 deterministic guardrail。HITL 是 guardrail 的一個 case——把人類判斷當成 deterministic check 來包 fuzzy LLM 行為。</p>
<p>判讀 HITL 該存在的訊號：</p>
<ul>
<li>任務的 fuzzy 行為輸出進入不可逆 deterministic 系統（DB write、API call、實體 action）。</li>
<li>LLM 在這類 boundary 上的失敗代價遠高於 HITL 的人類 cost。</li>
<li>沒有可靠的自動 validator（用 LLM judge 風險也太高）。</li>
</ul>
<p>三者俱備時、HITL 是必要的 guardrail。任一不滿足、可能用 schema validation / output validator / distribution monitoring 替代、不需要人類在 loop 內。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Centaur vs cyborg 兩種工作模式的分類。</li>
<li>Jagged frontier 概念、「全自動」是錯題的論證。</li>
<li>三種 HITL 觸發時機（pre-act / mid-stream / post-hoc）的分類。</li>
<li>橡皮圖章化的四個反模式跟緩解。</li>
<li>跟 agent 自主度分層、fuzzy engineering 典範的對應結構。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>Jagged frontier 的具體位置（哪些任務在 frontier 內、隨模型能力進步會移動）。</li>
<li>HITL 的 UI / UX 工具（隨產品 framework 演化）。</li>
<li>Approval 自動化的程度（更強的 distribution monitoring 可能讓部分 HITL 變得不必要）。</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a>、把 function calling / structured output / MCP 三個概念放回正確層級、銜接 agent 跟外部系統的協議設計。Agent 自主度分層完整討論見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>、工具副作用範圍見 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a>、HITL 在 fuzzy engineering 中的定位見 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a>。</p>
]]></content:encoded></item><item><title>4.6 應用層協議：function calling / structured output / MCP</title><link>https://tarrragon.github.io/blog/llm/04-applications/application-protocols/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/application-protocols/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function calling&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">structured output&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP&lt;/a> 是 LLM 應用落地時最常被混為一談的三個術語。三者解的問題層級完全不同：function calling 是&lt;strong>模型能力&lt;/strong>（訓練階段建立）、structured output 是**&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">sampling 約束&lt;/a>&lt;strong>（推論階段控制）、MCP 是&lt;/strong>server 協議**（架構層標準化）。把三者放回正確層級、應用設計就會變清楚；混為一談會看到「我啟用了 function calling 為什麼還需要 structured output」「MCP 跟 function calling 衝突嗎」這類根本誤解。&lt;/p>
&lt;p>本章把三者的層級差異拆開、解釋為什麼會出現 MCP、跟它們在實際應用中怎麼組合。具體 spec 細節（OpenAI function calling JSON 格式、Anthropic tools API、MCP server 實作）不在本章——這些半年一變、本章寫的是「換 spec 之後仍成立」的概念結構。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>用一句話分別說清楚三者解什麼問題。&lt;/li>
&lt;li>看到「啟用 function calling」「設定 structured output」「裝 MCP server」這些句子時、知道在說哪一層。&lt;/li>
&lt;li>判斷一個 LLM 應用該用哪幾個組合、什麼情境只需要一部分。&lt;/li>
&lt;li>解釋為什麼 MCP 會出現、它複用了哪個成功模式。&lt;/li>
&lt;/ol>
&lt;h2 id="三個概念的層級差異">三個概念的層級差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>概念&lt;/th>
 &lt;th>解的問題&lt;/th>
 &lt;th>在哪一層&lt;/th>
 &lt;th>跟模型訓練的關係&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Function calling&lt;/td>
 &lt;td>模型怎麼「知道」要呼叫工具&lt;/td>
 &lt;td>模型能力&lt;/td>
 &lt;td>訓練時建立、寫進權重&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Structured output&lt;/td>
 &lt;td>模型輸出怎麼被 parser 確定性消費&lt;/td>
 &lt;td>Sampling 約束&lt;/td>
 &lt;td>推論時控制、跟訓練無關&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MCP&lt;/td>
 &lt;td>LLM application 怎麼接外部 tool&lt;/td>
 &lt;td>Server 協議&lt;/td>
 &lt;td>不涉模型、純架構標準&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三者正交、可獨立或組合：&lt;/p>
&lt;ul>
&lt;li>用 function calling 但不用 structured output：訓練過 tool use 的模型直接呼叫工具、靠模型自律輸出合法 JSON。&lt;/li>
&lt;li>用 structured output 但不用 function calling：模型沒訓練過 tool use、用 prompt + grammar 強制輸出合法格式。&lt;/li>
&lt;li>用 MCP 但不用 function calling：MCP 標準化 tool 的暴露方式、模型用什麼機制呼叫不重要。&lt;/li>
&lt;li>三者都用：function calling 讓模型穩、structured output 約束格式、MCP 提供 tool ecosystem。&lt;/li>
&lt;/ul>
&lt;p>把這張表記熟、再看 LLM 應用相關討論、會發現「這個工具支援 function calling」「我的應用要 MCP」這類句子實際在說不同層級。&lt;/p>
&lt;h2 id="function-calling-是模型能力">Function Calling 是模型能力&lt;/h2>
&lt;p>Function calling 是模型在訓練階段建立的能力：&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">SFT 階段&lt;/a>大量「使用者 query + 該呼叫什麼工具 + 傳什麼參數」的範例、讓模型學會「看到 query 知道何時呼叫、怎麼呼叫」。&lt;/p>
&lt;p>判讀模型 function calling 強弱的訊號：&lt;/p>
&lt;ul>
&lt;li>該呼叫時呼叫、不該呼叫時不呼叫的準確度。&lt;/li>
&lt;li>呼叫格式合法率（不亂寫 JSON）。&lt;/li>
&lt;li>參數準確度（type 正確、value 合理）。&lt;/li>
&lt;li>多工具情況下選對工具的準確度。&lt;/li>
&lt;/ul>
&lt;p>這四個訊號跨模型差異大、根因是訓練資料分佈：&lt;/p>
&lt;ul>
&lt;li>OpenAI / Anthropic 旗艦模型 SFT 階段 function calling 範例大量、表現穩定。&lt;/li>
&lt;li>Llama 3 / Gemma 4 / Qwen3 開源旗艦模型 SFT 階段也加 function calling、但範例量不一、表現有落差。&lt;/li>
&lt;li>小型開源模型（&amp;lt; 14B）function calling 訓練嚴重不足；tool schema 複雜、多工具選擇、巢狀參數時失敗率高、單一工具 + 平坦 schema 仍可用。&lt;/li>
&lt;/ul>
&lt;p>理解這點的價值：看到「這個模型支援 function calling」的宣稱、要追問「&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/training-example-coverage/" data-link-title="Training Example Coverage" data-link-desc="訓練資料中的任務範例是否覆蓋足夠情境，決定模型在 function calling、格式輸出與邊界案例上的穩定性">訓練範例 coverage&lt;/a> 多廣」、不是 binary 的支援 / 不支援、是 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/capability-spectrum/" data-link-title="Capability Spectrum" data-link-desc="把模型能力視為連續光譜而非支援 / 不支援二分，用覆蓋度、穩定性與失敗模式判讀真實可用性">spectrum&lt;/a> 的訓練深度。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function calling</a>、<a href="/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">structured output</a>、<a href="/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP</a> 是 LLM 應用落地時最常被混為一談的三個術語。三者解的問題層級完全不同：function calling 是<strong>模型能力</strong>（訓練階段建立）、structured output 是**<a href="/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">sampling 約束</a><strong>（推論階段控制）、MCP 是</strong>server 協議**（架構層標準化）。把三者放回正確層級、應用設計就會變清楚；混為一談會看到「我啟用了 function calling 為什麼還需要 structured output」「MCP 跟 function calling 衝突嗎」這類根本誤解。</p>
<p>本章把三者的層級差異拆開、解釋為什麼會出現 MCP、跟它們在實際應用中怎麼組合。具體 spec 細節（OpenAI function calling JSON 格式、Anthropic tools API、MCP server 實作）不在本章——這些半年一變、本章寫的是「換 spec 之後仍成立」的概念結構。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>用一句話分別說清楚三者解什麼問題。</li>
<li>看到「啟用 function calling」「設定 structured output」「裝 MCP server」這些句子時、知道在說哪一層。</li>
<li>判斷一個 LLM 應用該用哪幾個組合、什麼情境只需要一部分。</li>
<li>解釋為什麼 MCP 會出現、它複用了哪個成功模式。</li>
</ol>
<h2 id="三個概念的層級差異">三個概念的層級差異</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>解的問題</th>
          <th>在哪一層</th>
          <th>跟模型訓練的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Function calling</td>
          <td>模型怎麼「知道」要呼叫工具</td>
          <td>模型能力</td>
          <td>訓練時建立、寫進權重</td>
      </tr>
      <tr>
          <td>Structured output</td>
          <td>模型輸出怎麼被 parser 確定性消費</td>
          <td>Sampling 約束</td>
          <td>推論時控制、跟訓練無關</td>
      </tr>
      <tr>
          <td>MCP</td>
          <td>LLM application 怎麼接外部 tool</td>
          <td>Server 協議</td>
          <td>不涉模型、純架構標準</td>
      </tr>
  </tbody>
</table>
<p>三者正交、可獨立或組合：</p>
<ul>
<li>用 function calling 但不用 structured output：訓練過 tool use 的模型直接呼叫工具、靠模型自律輸出合法 JSON。</li>
<li>用 structured output 但不用 function calling：模型沒訓練過 tool use、用 prompt + grammar 強制輸出合法格式。</li>
<li>用 MCP 但不用 function calling：MCP 標準化 tool 的暴露方式、模型用什麼機制呼叫不重要。</li>
<li>三者都用：function calling 讓模型穩、structured output 約束格式、MCP 提供 tool ecosystem。</li>
</ul>
<p>把這張表記熟、再看 LLM 應用相關討論、會發現「這個工具支援 function calling」「我的應用要 MCP」這類句子實際在說不同層級。</p>
<h2 id="function-calling-是模型能力">Function Calling 是模型能力</h2>
<p>Function calling 是模型在訓練階段建立的能力：<a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">SFT 階段</a>大量「使用者 query + 該呼叫什麼工具 + 傳什麼參數」的範例、讓模型學會「看到 query 知道何時呼叫、怎麼呼叫」。</p>
<p>判讀模型 function calling 強弱的訊號：</p>
<ul>
<li>該呼叫時呼叫、不該呼叫時不呼叫的準確度。</li>
<li>呼叫格式合法率（不亂寫 JSON）。</li>
<li>參數準確度（type 正確、value 合理）。</li>
<li>多工具情況下選對工具的準確度。</li>
</ul>
<p>這四個訊號跨模型差異大、根因是訓練資料分佈：</p>
<ul>
<li>OpenAI / Anthropic 旗艦模型 SFT 階段 function calling 範例大量、表現穩定。</li>
<li>Llama 3 / Gemma 4 / Qwen3 開源旗艦模型 SFT 階段也加 function calling、但範例量不一、表現有落差。</li>
<li>小型開源模型（&lt; 14B）function calling 訓練嚴重不足；tool schema 複雜、多工具選擇、巢狀參數時失敗率高、單一工具 + 平坦 schema 仍可用。</li>
</ul>
<p>理解這點的價值：看到「這個模型支援 function calling」的宣稱、要追問「<a href="/blog/llm/knowledge-cards/training-example-coverage/" data-link-title="Training Example Coverage" data-link-desc="訓練資料中的任務範例是否覆蓋足夠情境，決定模型在 function calling、格式輸出與邊界案例上的穩定性">訓練範例 coverage</a> 多廣」、不是 binary 的支援 / 不支援、是 <a href="/blog/llm/knowledge-cards/capability-spectrum/" data-link-title="Capability Spectrum" data-link-desc="把模型能力視為連續光譜而非支援 / 不支援二分，用覆蓋度、穩定性與失敗模式判讀真實可用性">spectrum</a> 的訓練深度。</p>
<h2 id="structured-output-是-sampling-約束">Structured Output 是 Sampling 約束</h2>
<p><a href="/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">Structured output</a> 是推論階段的技巧、跟模型訓練無關：在 <a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling</a>（從機率分佈挑下一個 token 的步驟）時對每個 token 做 <a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar</a> / schema 約束、不合法 token 的機率（logit、token 機率的對數）被歸零、把不合法輸出的可能性壓到不會被 sample。</p>
<p>主要實作機制（適用 / 限制條件附在每項下）：</p>
<ul>
<li><strong>JSON mode</strong>：每步 sampling 過濾、只允許「保持 JSON 仍合法」的 token。適用：絕大多數 OpenAI 相容 API 都有支援；限制：只保 JSON 合法、不保 schema 對位。</li>
<li><strong>Grammar-constrained sampling</strong>：用 <a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar</a>（描述合法語法的形式化規則、實作上常用 <a href="/blog/llm/knowledge-cards/bnf/" data-link-title="BNF（Backus-Naur Form）" data-link-desc="用遞迴產生式描述語法的經典記法，是 CFG、parser 與 grammar-constrained sampling 常見的基礎表示">BNF</a> 或 <a href="/blog/llm/knowledge-cards/lark-grammar/" data-link-title="Lark Grammar" data-link-desc="Lark parser 使用的 EBNF-like grammar 格式，常被 structured output 工具拿來描述自訂輸出語法">Lark grammar</a>）描述完整輸出形狀、推論時逐 token 過濾。適用：需要嚴格自訂格式（<a href="/blog/llm/knowledge-cards/dsl/" data-link-title="DSL（Domain-Specific Language）" data-link-desc="為特定業務或技術領域設計的小語言，在 LLM 應用中常作為可解析、可驗證、可執行的中介輸出">DSL</a>、特定 query language）；限制：要伺服器層支援（llama.cpp、vLLM 有、有些雲端 API 沒）。</li>
<li><strong>Schema-guided</strong>：依 JSON Schema 動態決定每步允許哪些 token、強制 enum / type / required 等約束。適用：複雜結構化資料；限制：實作複雜度高、跨伺服器一致性差。</li>
<li><strong>Logit bias</strong>：對特定 token 加 bias、間接引導 sampling、最弱但最靈活的方式。適用：簡單的 token 黑名單 / 白名單；限制：無法保證結構合法。</li>
</ul>
<p>優勢相對 function calling：</p>
<ul>
<li><strong>跨模型可移植</strong>：不依賴模型訓練、任何能跑 sampling 的模型都能上。</li>
<li><strong>可任意自訂格式</strong>：不限於 OpenAI 或某 provider 的 function spec、想定義什麼 schema 都行。</li>
<li><strong>保證 100% 合法輸出</strong>：grammar 約束下不可能輸出 invalid JSON。</li>
</ul>
<p>代價：</p>
<ul>
<li><strong>約束太嚴可能跟模型「自然」輸出衝突</strong>：模型本來想說 A、grammar 強制只能說 B、品質會降。</li>
<li><strong>實作成本</strong>：grammar 解析跟動態 logit mask 在推論伺服器要支援、不是所有 server 都成熟。</li>
<li><strong>跟模型訓練脫鉤</strong>：模型「不知道」自己被約束、可能還是用沒用 function calling 訓練的「猜測」方式生成。</li>
</ul>
<p>實務上 structured output 跟 function calling 經常組合：function calling 訓練讓模型「自然」傾向合法輸出、structured output 約束兜底保證「真的合法」。</p>
<h2 id="mcp-是-server-協議">MCP 是 Server 協議</h2>
<p>MCP（Model Context Protocol、2024 年由 Anthropic 提出）是「LLM application ↔ 外部 tool server 之間的標準化協議」。它不在模型能力層、不在 sampling 層、是更高層的架構規範。</p>
<p>要理解 MCP 的定位、回顧 LLM 生態的歷史問題：</p>
<p>每個 LLM application（Cursor、Continue.dev、Claude Desktop、aider 等）要接每個 tool（檔案系統、資料庫、search、自訂 API），都得寫 adapter。N 個 application × M 個 tool 的整合成本是 N×M、生態擴張時成本爆炸。</p>
<p>MCP 把這個成本拆成兩段：</p>
<ul>
<li><strong>LLM application 端</strong>：實作 MCP client（一次）、之後支援任意 MCP server。</li>
<li><strong>Tool 端</strong>：實作 MCP server（一次）、之後被任意 MCP client 接到。</li>
</ul>
<p>整合成本從 N×M 降到 N+M。同樣的 ecosystem effect 跟模組零的 <a href="/blog/llm/00-foundations/openai-compatible-api/" data-link-title="0.3 OpenAI 相容 API" data-link-desc="為什麼幾乎所有本地 LLM 工具不用改就能切到本地：背後是同一套 API 形狀">OpenAI 相容 API</a> 一樣——標準化中介把生態整合複雜度從乘法降到加法。</p>
<p>MCP 涵蓋的「server 該提供什麼」包括：</p>
<ul>
<li>Tool 註冊（這個 server 提供哪些 tool）。</li>
<li>Tool schema（每個 tool 的參數定義）。</li>
<li>Tool 呼叫協議（呼叫方式 + 回應格式）。</li>
<li>Resource 暴露（檔案、文件等讀取資源）。</li>
<li>Prompt template 共享（reusable system prompt）。</li>
</ul>
<p>這些都在 protocol 層、模型怎麼用 tool（function calling 還是 structured output）不在 MCP 規範範圍——MCP 不管你模型強不強、它只管「tool 怎麼被暴露」。</p>
<h2 id="為什麼會出現-mcp">為什麼會出現 MCP</h2>
<p>MCP 是 LLM application 生態擴張到一定程度後的必然產物。觀察生態演化：</p>
<ul>
<li><strong>2023 早期</strong>：每個 LLM app 各自寫工具整合、Cursor 接 file system、Continue.dev 接 codebase、aider 接 git——各自的 adapter 邏輯互不通用。</li>
<li><strong>2024 中期</strong>：function calling spec 標準化（OpenAI 跟 Anthropic 各自定義）、解決「模型怎麼呼叫工具」、但「工具怎麼暴露給 application」還是各家自己處理。</li>
<li><strong>2024 底</strong>：Anthropic 提 MCP、把「工具暴露」也標準化、補完 ecosystem 拼圖。</li>
</ul>
<p>複用 OpenAI 相容 API 的成功模式：</p>
<ul>
<li><a href="/blog/llm/knowledge-cards/openai-compatible-api/" data-link-title="OpenAI 相容 API" data-link-desc="本地推論伺服器跟雲端 OpenAI 共用的 API 形狀標準">OpenAI 相容 API</a>：標準化「介面層 ↔ <a href="/blog/llm/knowledge-cards/inference-server/" data-link-title="Inference Server" data-link-desc="載入模型權重、處理 prompt、產生 token 的常駐 process">推論伺服器</a>」、所有 IDE plugin 都接這個。</li>
<li>MCP：標準化「LLM application ↔ tool server」、所有 application 都接這個。</li>
</ul>
<p>兩者都採用同個策略：定義最小可用標準、讓生態繞著標準長、所有 player 受益。</p>
<p>MCP 成熟度判讀訊號（不固化在某一個時間點、用這幾個 signal 重新評估）：</p>
<ul>
<li><strong>Application 採納範圍</strong>：主要 LLM application（Claude Desktop、Cursor、Continue.dev、其他主流 IDE / chat 介面）是否原生支援。</li>
<li><strong>Tool server catalog 規模</strong>：社群維護的 MCP server 數量跟覆蓋範圍（檔案系統、git、Slack、雲端 API 等是否都有現成 server）。</li>
<li><strong>本地推論生態接入度</strong>：Ollama、LM Studio 等本地伺服器是否原生支援 MCP（或仍以 OpenAI 相容 API 為主）。</li>
<li><strong>跨平台一致性</strong>：Windows / macOS / Linux 上的 MCP server 行為是否一致、SDK 是否穩定。</li>
</ul>
<p>四個訊號全部成熟前、MCP 仍處於「主要 application 支援、本地生態剛開始接」的擴張期；訊號逐步達標後、預期會像 OpenAI 相容 API 一樣成為應用層的默認標準。</p>
<p>它跟 function calling 的關係：MCP 提供 tool 的暴露機制、模型怎麼呼叫這些 tool 仍走 function calling（如果模型支援）或 structured output（如果用約束）。三者疊加而非互斥。</p>
<h2 id="三者組合的實際工作流">三者組合的實際工作流</h2>
<p>一個完整 LLM application 的典型 stack：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">使用者 prompt
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">LLM application（Claude Desktop / Cursor / 自家應用）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓ (MCP client、列出所有可用 tool)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">MCP server pool（檔案系統 server、git server、自家 API server...）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ↑
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">LLM application 把 tool 描述塞進 prompt
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">推論伺服器（OpenAI API / Ollama / Anthropic API）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ↓ (function calling 訓練 + structured output 約束)
</span></span><span class="line"><span class="ln">11</span><span class="cl">模型輸出：「我要呼叫 tool X、參數是 Y」
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">13</span><span class="cl">LLM application 用 MCP 把呼叫送到對應 server
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">Server 執行、回應
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">LLM application 把結果塞進 context、回到推論伺服器繼續</span></span></code></pre></div><p>三者各司其職：</p>
<ul>
<li><strong>Function calling</strong> 讓模型穩定輸出工具呼叫（訓練支撐）。</li>
<li><strong>Structured output</strong> 兜底保證呼叫格式合法（sampling 約束）。</li>
<li><strong>MCP</strong> 提供 tool ecosystem、application 不用為每個 tool 寫專屬 adapter（架構標準）。</li>
</ul>
<p>少了任一個都還能跑、但效率跟生態擴展性降一級：</p>
<ul>
<li>沒 function calling、靠 prompt + structured output、跨模型品質不穩。判讀訊號：同 prompt 在不同模型上 tool 呼叫格式錯誤率差 30% 以上。</li>
<li>沒 structured output、靠模型自律、偶有失敗。判讀訊號：&lt; 30B 模型在複雜 schema 下 JSON 合法率 &lt; 90%。</li>
<li>沒 MCP、每個 application 自己寫所有 tool 整合、ecosystem 不可規模化。判讀訊號：團隊維護 &gt; 5 個 tool adapter、每換 LLM provider 重寫一輪。</li>
</ul>
<h2 id="常見的組合誤用">常見的組合誤用</h2>
<p>三者組合在以下情境會失敗、是判讀「我的應用為何不穩」的常見候選：</p>
<ul>
<li><strong>Structured output 蓋過 function calling 訓練</strong>：模型訓練時用 Anthropic tools 格式、應用強制套 OpenAI function spec 的 grammar、模型輸出「合法但語意空洞」的 JSON（schema 對、欄位填湊數）。修法：用模型訓練過的 spec、避免在 grammar 層強制改寫。</li>
<li><strong>MCP server 在 prompt context 撐爆 tool 描述</strong>：MCP server 暴露幾十個 tool、每個都有 schema 跟 description、全塞進 system prompt 把 context budget 耗光。修法：dynamic tool selection（先讓 LLM 看「tool 摘要」選相關的、再把選中 tool 的詳細 schema 塞進 context）。</li>
<li><strong>Function calling + structured output 兩邊 schema 不一致</strong>：模型訓練的 function spec 跟 application 套的 JSON schema 欄位不對、模型輸出符合訓練 spec 但不符合 application schema、parser 失敗。修法：grammar 直接從 function spec 生、避免人工維護兩份。</li>
<li><strong>MCP server 沒做 input validation、prompt injection 通過 tool 結果污染 context</strong>：tool 回的內容沒檢查、惡意內容（如 PR 留言中的「請執行 rm -rf」）被模型當指令執行。修法：tool 輸出做 sanitization、可疑內容用 sandbox 標籤包起來、模型 prompt 明確區分「使用者指令」vs「tool 結果」。個人 dev 在自己機器上跑 MCP server 的權限模型（檔案系統 / shell / 網路存取邊界、第三方 MCP 信任）見 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a>；IDE 場景中 codebase / 外部文件 / 剪貼簿等 prompt injection 攻擊面見 <a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3</a>。</li>
</ul>
<h2 id="何時可以只用一部分">何時可以只用一部分</h2>
<p>三者組合的需求視場景而定：</p>
<ul>
<li><strong>單純 structured 輸出</strong>（不呼叫工具）：只需 structured output、不需 function calling / MCP。例：把使用者輸入分類成 enum、輸出固定 schema 的 JSON。</li>
<li><strong>In-process tool</strong>（直接 Python function）：function calling + 簡單 dispatcher、不需 MCP。應用規模小時最直接。</li>
<li><strong>跨 application 共用 tool</strong>：才需要 MCP。如果你只寫自己用的 app、in-process 比 MCP 簡單。</li>
<li><strong>用較弱模型</strong>：可能只用 structured output、跳過 function calling。</li>
</ul>
<p>三者的「最小可用組合」視應用複雜度而定。早期應用通常從 function calling 開始、規模化後加 MCP、品質要求高時加 structured output 兜底——演化路徑不必一步到位。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>三個層級的分界（模型能力 / sampling 約束 / server 協議）。</li>
<li>N×M → N+M 的標準化收益、跟 OpenAI 相容 API 的對應。</li>
<li>三者疊加而非互斥的設計取捨。</li>
<li>「最小可用組合」的判讀框架。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>MCP 是 2024-2025 才標準化的協議、未來 5 年可能演化或被新協議補充（協議層更新慢、但會更新）。</li>
<li>各家 function calling spec 的具體格式（OpenAI / Anthropic / 開放標準會持續細化）。</li>
<li>Structured output 的具體實作（grammar engines / JSON mode 會持續優化）。</li>
<li>哪些工具有 MCP server 可用（生態 catalog 會擴展）。</li>
</ul>
<p>看到新協議或新 spec 時、回到本章三層 framing 問：它解的是哪一層？能不能跟既有的另兩層組合？這個問題的答案能很快定位新東西在 stack 中的位置。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 Workflow 編排模式</a>、把多 LLM call 組合的設計模式整理出來。</p>
]]></content:encoded></item><item><title>4.7 Workflow 編排模式</title><link>https://tarrragon.github.io/blog/llm/04-applications/workflow-patterns/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/workflow-patterns/</guid><description>&lt;p>LLM 應用很少是單一 call、多半是多次 LLM call 的組合。Multi-call 組合的模式雖然各 framework（LangGraph、LlamaIndex Workflow、各家 DAG runner）包裝不同、本質上可歸納成幾種基本模式：pipeline、router、parallel、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/reflection/" data-link-title="Reflection / Self-critique" data-link-desc="要求模型先輸出一版、再 critique 自己、再修改的 prompting / workflow 模式、有自身失敗模式">reflection&lt;/a>。理解這幾個模式、看到任何 LLM application 都能拆解成基本元件、判斷複雜度合不合理、識別常見反模式。&lt;/p>
&lt;p>本章寫的是這四種模式的本質、它們的失敗模式、彼此組合的方式、何時退化成 single call 更好。具體 framework 的 DAG syntax / workflow API 不在本章——這些跨 framework 差異大、半年一變、原理層級更穩。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>區分四種基本 workflow 模式。&lt;/li>
&lt;li>看到一個 LLM application 時、能畫出它的 workflow 結構圖。&lt;/li>
&lt;li>判斷一個 workflow 是否該退化成 single call。&lt;/li>
&lt;li>識別 workflow 設計常見的反模式。&lt;/li>
&lt;/ol>
&lt;h2 id="llm-應用的本質是多-call-組合">LLM 應用的本質是多 Call 組合&lt;/h2>
&lt;p>單一 LLM call 解的問題有上限：&lt;/p>
&lt;ul>
&lt;li>Context 限制：再大的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window&lt;/a> 也有上限、長文件得切。&lt;/li>
&lt;li>推理深度：複雜推理拆步驟通常比一次推完更穩。&lt;/li>
&lt;li>Tool 範圍：multi-step tool use 需要多次 call 串起來。&lt;/li>
&lt;li>多面向評估：同時要管邏輯、風格、合規時、單次 call 容易偏其中一面。&lt;/li>
&lt;/ul>
&lt;p>Multi-call 組合擴展能力範圍、代價是每多一個 call 多一份成本：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Latency&lt;/strong>：N 個 call sequential 跑是 N 倍 latency；parallel 跑也至少要 max(call latency)。&lt;/li>
&lt;li>&lt;strong>Cost&lt;/strong>：每個 call 的 token 成本累加、N 個 call 是 N 倍 cost。&lt;/li>
&lt;li>&lt;strong>失敗點&lt;/strong>：每個 call 都可能失敗、N 個 call 串起來成功率是個別成功率連乘。&lt;/li>
&lt;li>&lt;strong>複雜度&lt;/strong>：error handling、retry、partial success 處理複雜度爆炸。&lt;/li>
&lt;/ul>
&lt;p>「設計 workflow」的核心問題不是「能不能拆成多 call」、是「拆成多 call 的收益值不值得這份成本」。Workflow 設計常見的失敗是過早優化（software engineering idiom：「premature optimization is the root of all evil」、在沒有 profiling 證據前就拆結構、複雜度爆炸但無實質改進）、把簡單問題切成複雜 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">DAG（directed acyclic graph、有向無環圖、描述步驟依賴關係的資料結構）&lt;/a>、最終比 single call 慢、貴、難維護、品質卻沒提升。Single call 在「context 塞得下 + 任務不需要外部 tool + 失敗代價低」的情境下仍是最高 ROI 選項。&lt;/p>
&lt;p>四種基本模式各自解不同的「為什麼需要多 call」、下面逐個展開。&lt;/p>
&lt;h2 id="pipeline線性串接">Pipeline：線性串接&lt;/h2>
&lt;p>&lt;strong>結構&lt;/strong>：&lt;code>call_1 → call_2 → call_3 → ...&lt;/code>、後一個 call 用前一個的 output 當 input。&lt;/p>
&lt;p>&lt;strong>適合場景&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>任務有清楚的線性子步驟（萃取 → 摘要 → 翻譯、或 plan → execute → review）。&lt;/li>
&lt;li>每個子步驟用同個模型最划算（一個 call 撐不下、拆成幾個 call 接力）。&lt;/li>
&lt;li>子步驟輸出需要中間驗證 / 處理（前一步先過 schema 解析、再餵下一步）。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>典型例子&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Code review pipeline：先 LLM 找問題列表 → 再 LLM 對每個問題寫修改建議 → 最後 LLM 合成 summary。&lt;/li>
&lt;li>文件處理：原文 → 萃取結構化資訊 → 套用 template → 輸出最終格式。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>失敗模式&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>LLM 應用很少是單一 call、多半是多次 LLM call 的組合。Multi-call 組合的模式雖然各 framework（LangGraph、LlamaIndex Workflow、各家 DAG runner）包裝不同、本質上可歸納成幾種基本模式：pipeline、router、parallel、<a href="/blog/llm/knowledge-cards/reflection/" data-link-title="Reflection / Self-critique" data-link-desc="要求模型先輸出一版、再 critique 自己、再修改的 prompting / workflow 模式、有自身失敗模式">reflection</a>。理解這幾個模式、看到任何 LLM application 都能拆解成基本元件、判斷複雜度合不合理、識別常見反模式。</p>
<p>本章寫的是這四種模式的本質、它們的失敗模式、彼此組合的方式、何時退化成 single call 更好。具體 framework 的 DAG syntax / workflow API 不在本章——這些跨 framework 差異大、半年一變、原理層級更穩。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>區分四種基本 workflow 模式。</li>
<li>看到一個 LLM application 時、能畫出它的 workflow 結構圖。</li>
<li>判斷一個 workflow 是否該退化成 single call。</li>
<li>識別 workflow 設計常見的反模式。</li>
</ol>
<h2 id="llm-應用的本質是多-call-組合">LLM 應用的本質是多 Call 組合</h2>
<p>單一 LLM call 解的問題有上限：</p>
<ul>
<li>Context 限制：再大的 <a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window</a> 也有上限、長文件得切。</li>
<li>推理深度：複雜推理拆步驟通常比一次推完更穩。</li>
<li>Tool 範圍：multi-step tool use 需要多次 call 串起來。</li>
<li>多面向評估：同時要管邏輯、風格、合規時、單次 call 容易偏其中一面。</li>
</ul>
<p>Multi-call 組合擴展能力範圍、代價是每多一個 call 多一份成本：</p>
<ul>
<li><strong>Latency</strong>：N 個 call sequential 跑是 N 倍 latency；parallel 跑也至少要 max(call latency)。</li>
<li><strong>Cost</strong>：每個 call 的 token 成本累加、N 個 call 是 N 倍 cost。</li>
<li><strong>失敗點</strong>：每個 call 都可能失敗、N 個 call 串起來成功率是個別成功率連乘。</li>
<li><strong>複雜度</strong>：error handling、retry、partial success 處理複雜度爆炸。</li>
</ul>
<p>「設計 workflow」的核心問題不是「能不能拆成多 call」、是「拆成多 call 的收益值不值得這份成本」。Workflow 設計常見的失敗是過早優化（software engineering idiom：「premature optimization is the root of all evil」、在沒有 profiling 證據前就拆結構、複雜度爆炸但無實質改進）、把簡單問題切成複雜 <a href="/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">DAG（directed acyclic graph、有向無環圖、描述步驟依賴關係的資料結構）</a>、最終比 single call 慢、貴、難維護、品質卻沒提升。Single call 在「context 塞得下 + 任務不需要外部 tool + 失敗代價低」的情境下仍是最高 ROI 選項。</p>
<p>四種基本模式各自解不同的「為什麼需要多 call」、下面逐個展開。</p>
<h2 id="pipeline線性串接">Pipeline：線性串接</h2>
<p><strong>結構</strong>：<code>call_1 → call_2 → call_3 → ...</code>、後一個 call 用前一個的 output 當 input。</p>
<p><strong>適合場景</strong>：</p>
<ul>
<li>任務有清楚的線性子步驟（萃取 → 摘要 → 翻譯、或 plan → execute → review）。</li>
<li>每個子步驟用同個模型最划算（一個 call 撐不下、拆成幾個 call 接力）。</li>
<li>子步驟輸出需要中間驗證 / 處理（前一步先過 schema 解析、再餵下一步）。</li>
</ul>
<p><strong>典型例子</strong>：</p>
<ul>
<li>Code review pipeline：先 LLM 找問題列表 → 再 LLM 對每個問題寫修改建議 → 最後 LLM 合成 summary。</li>
<li>文件處理：原文 → 萃取結構化資訊 → 套用 template → 輸出最終格式。</li>
</ul>
<p><strong>失敗模式</strong>：</p>
<ul>
<li><strong>中間步驟誤差累積</strong>：第一步小錯、第二步基於錯誤輸出、第三步累積到完全跑偏。整體錯誤率是個別錯誤率連乘的補集（任一步錯整個 pipeline 錯）。</li>
<li><strong>無法檢測前段錯誤</strong>：後段沒辦法回頭修正前段、即使發現結果不對。</li>
<li><strong>過度拆解</strong>：本來 single call 能處理的事拆成 3 步、latency 跟 cost 都暴增。</li>
</ul>
<p><strong>緩解策略</strong>：</p>
<ul>
<li>中間步驟加 validation（schema 解析、簡單 sanity check）、catch 早期錯誤。</li>
<li>關鍵 pipeline 加 logging、出問題時能定位是哪一步壞。</li>
<li>定期重新評估「這個 pipeline 真的需要拆嗎」、不需要就合併回 single call。</li>
</ul>
<h2 id="router依輸入分流">Router：依輸入分流</h2>
<p><strong>結構</strong>：<code>input → classifier → path A / B / C → output</code>、依分類結果走不同處理路徑。</p>
<p><strong>適合場景</strong>：</p>
<ul>
<li>輸入類型多樣、不同類型需要不同處理（簡單 query 用小模型、複雜 query 用大模型）。</li>
<li>Cost 優化（多數簡單請求走便宜 path、少數複雜請求走貴 path）。</li>
<li>功能分流（query 是 search、summarization、還是 code edit）。</li>
</ul>
<p><strong>典型例子</strong>：</p>
<ul>
<li>客服分流：先判斷使用者意圖（查訂單 / 退貨 / 一般諮詢）、再分到對應 specialized agent。</li>
<li>模型分流：先 1B classifier 判斷複雜度、簡單問題給本地 14B、複雜問題給雲端旗艦。</li>
</ul>
<p><strong>失敗模式</strong>：</p>
<ul>
<li><strong>Classifier 錯判</strong>：分流錯了、整個 query 跑進最差 path、結果完全不對。</li>
<li><strong>Classifier 比下游還複雜</strong>：本來 router 是 cost saver、結果 classifier 本身就要強模型、變成多花錢的繞路。</li>
<li><strong>Path 設計不完整</strong>：有些 query 不符合任何 path、router 強塞到某個 path、結果差。</li>
</ul>
<p>Classifier 設計常用 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">structured output</a> 強制輸出 enum、避免 free-form 解析；複雜應用可能把 classifier 本身做成 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">tool use</a> 的特例。</p>
<p><strong>緩解策略</strong>：</p>
<ul>
<li>Classifier 用較弱模型但加 confidence 判讀、低 confidence 走 fallback path。</li>
<li>簡化 path 數量（3-5 個內、保留必要切分即可）。</li>
<li>設計「unknown / catch-all」path、不假設所有 input 都能歸類。</li>
</ul>
<h2 id="parallel多-call-並行結果合併">Parallel：多 Call 並行、結果合併</h2>
<p><strong>結構</strong>：<code>input → [call A, call B, call C 並行跑] → 合併 → output</code>、多次 LLM call 同時跑、結果合起來。</p>
<p><strong>適合場景</strong>：</p>
<ul>
<li>任務有獨立面向（評估一份 PR 的程式碼品質 / 安全性 / 可讀性、各自一個 call）。</li>
<li>Ensemble（同個任務跑多次、用 majority vote 提高可靠度）。</li>
<li>Multi-source retrieval（從不同來源平行拉資料、合起來餵下游）。</li>
</ul>
<p><strong>典型例子</strong>：</p>
<ul>
<li>多面向審查：同份 code 同時跑「邏輯」「風格」「安全」三個 review、合併成總評。</li>
<li>Ensemble decoding：同個 prompt 用不同 seed / temperature 跑 5 次、majority vote 拿最穩答案。</li>
</ul>
<p><strong>失敗模式</strong>：</p>
<ul>
<li><strong>合併邏輯難設計</strong>：parallel 容易、合併難。三個 reviewer 意見不一致時怎麼裁判？多數決還是加權？</li>
<li><strong>Cost 是 sequential 數倍</strong>：parallel 跑 N call 的 cost 是 sequential single 的 N 倍、latency 才有節省。</li>
<li><strong>不需要並行</strong>：本來 sequential single call 能解的事、parallel 變浪費。</li>
<li><strong>不獨立的「平行」</strong>：兩個 call 其實依賴彼此、強制 parallel 反而漏訊號。</li>
</ul>
<p><strong>緩解策略</strong>：</p>
<ul>
<li>Parallel 前先問：這些 call 真的獨立嗎？依賴的應該 sequential。</li>
<li>合併邏輯先設計、再開始 parallel；想清楚怎麼合再進 parallel。</li>
<li>Cost 監控：parallel 是 cost amplifier、生產環境注意。</li>
</ul>
<h2 id="reflection產出--評估--修正">Reflection：產出 → 評估 → 修正</h2>
<p><strong>結構</strong>：<code>產出 → critic 評估 → 依評估修正 → 再評估 → ...</code>、self-improvement loop。</p>
<p><strong>適合場景</strong>：</p>
<ul>
<li>任務有客觀評估訊號（test 跑通、規範符合、structured output 合法）。</li>
<li>一次產出品質不夠、可以迭代改善。</li>
<li>創意任務的「初稿 → 修稿」流程。</li>
</ul>
<p><strong>典型例子</strong>：</p>
<ul>
<li>Code 生成 + test 驅動：產出 code → 跑 test → 失敗的話讀 error 修正 → 再跑 test → 直到通過。</li>
<li>文章寫作：生成草稿 → critic 模型評論 → 依評論修改 → 再評論 → 直到滿意。</li>
</ul>
<p><strong>失敗模式</strong>：</p>
<ul>
<li><strong>Critic 跟 generator 共用 blind spot</strong>：兩個都同個模型、有同樣的盲點、reflection 收斂到同樣錯誤位置（如兩個都不認識某個 framework 的 API、再 reflect 也不對）。</li>
<li><strong>無限循環</strong>：沒有客觀停止訊號、reflection 一直跑、cost 爆掉。</li>
<li><strong>過度修正</strong>：每次 reflection 都改一點、累積結果變糟（過度 fitting critic 意見）。</li>
<li><strong>Critic 失職</strong>：critic 太寬鬆、什麼都說 OK；或太嚴格、什麼都挑、永遠停不下來。</li>
</ul>
<p><strong>Systematic vs random error 的關鍵分層</strong>：reflection 對 random error 有效（同 prompt 重跑因 sampling 抖動產生的錯誤、多輪重 sample 會收斂）、但對 systematic error 無效（generator 跟 critic 共用 base model、訓練分佈中的盲點不會因重跑消失、loop 收斂到同樣錯誤）。判讀訊號：若 critic 每次給的修正建議都很像、或修完還是同一類錯、就是 systematic error、加 reflection steps 無收益。修法：換不同 base model 當 critic、或加外部驗證（test、lint、schema check）取代 LLM critic。Agent loop 是 reflection 的特例、進階失敗模式分析見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構</a> 的三類失敗段。</p>
<p><strong>緩解策略</strong>：</p>
<ul>
<li>Critic 用不同模型、或不同 prompt 角度、減少 blind spot。</li>
<li>Reflection 設 step 上限、即使沒達標也強制停。</li>
<li>客觀驗證訊號（test pass、schema 合法、外部 metric）優先於 LLM critic 主觀評估。</li>
<li>Reflection 對有客觀訊號的任務 ROI 高、對純主觀偏好任務收益有限（critic 的「主觀好壞」跟 generator 同 base 時是同樣 distribution、無法 calibrate）。</li>
</ul>
<h2 id="四種模式的組合">四種模式的組合</h2>
<p>實際應用通常混用、不是純單一模式：</p>
<ul>
<li><strong>Pipeline of routers</strong>：第一步先 router 分類、每個 path 內部又是 pipeline。</li>
<li><strong>Parallel of pipelines</strong>：多個 pipeline 平行跑、最後合併。</li>
<li><strong>Reflection inside pipeline</strong>：pipeline 中某幾步用 reflection loop 改進、其他步驟 single call。</li>
<li><strong>Router into reflection</strong>：依輸入複雜度分流、簡單走 single call、複雜走 reflection loop。</li>
</ul>
<p>複雜應用通常是這四種模式的多層組合。看 LLM application 結構時、能識別出基本模式組合、就能判斷它的複雜度合不合理。</p>
<p>組合的判讀訊號：</p>
<ul>
<li>三層以上嵌套通常 over-engineered、考慮簡化。</li>
<li>同個模式重複用很多次（如 5 個 pipeline 串）可能拆得太細。</li>
<li>看不出基本模式的 ad-hoc 流程、通常維護困難。</li>
</ul>
<h2 id="何時退化成-single-call-更好">何時退化成 Single Call 更好</h2>
<p>判讀「該不該設計 workflow」的反射：先問「single call 能不能解」、不行再考慮拆。</p>
<p>Single call 更適合的訊號：</p>
<ul>
<li>上下文短（&lt; 8K token、塞得進現代 LLM）。</li>
<li>推理單純、不需要 multi-step。</li>
<li>不需要 tool 或只需一兩個簡單 tool。</li>
<li>沒有客觀驗證訊號可用、reflection 沒意義。</li>
<li>Latency 敏感、不能接受多次 round trip。</li>
</ul>
<p>「我先寫個 pipeline」常是過早優化：</p>
<ul>
<li>簡單問題切成 5 步、累積誤差大過拆分收益。</li>
<li>為了「靈活性」抽象太多、最終比 single prompt 還難改。</li>
<li>寫 workflow framework 的成本超過用 raw API 的成本。</li>
</ul>
<p>實務做法：先 single call baseline、跑半週看品質、不夠用再分解；workflow 設計留到 baseline 不足之後。</p>
<h2 id="workflow-設計常見的反模式">Workflow 設計常見的反模式</h2>
<p>幾種特別容易踩的反模式：</p>
<h3 id="過度切碎-pipeline">過度切碎 pipeline</h3>
<p>把任務切成 10 步、每步一個 LLM call、累積誤差大、latency 拖長、cost 爆。問題通常是「我以為拆細了品質會好」、實際相反。</p>
<p>訊號：pipeline 步驟超過 5 個、每步輸入輸出量級接近、看不出為什麼需要分。</p>
<p>緩解：能合併的合併、保留必要切點（中間有外部 tool 介入、或需要驗證的步驟）。</p>
<h3 id="parallel-跑根本不需要並行的事">Parallel 跑根本不需要並行的事</h3>
<p>兩個 call 其實依賴彼此、或本質是同個任務、硬要 parallel。Cost 是 sequential 的 N 倍、品質沒提升。</p>
<p>訊號：parallel 出來的結果合併邏輯複雜、或合併結果跟「直接 sequential 跑」差不多。</p>
<p>緩解：parallel 前問「這幾個 call 真的獨立、結果真的可合併嗎」、不獨立就 sequential。</p>
<h3 id="reflection-沒有客觀停止條件">Reflection 沒有客觀停止條件</h3>
<p>Reflection loop 純靠模型自己判斷「夠好了沒」、容易過度修正或無限循環。</p>
<p>訊號：reflection loop 沒有 step cap、沒有外部 metric、純依模型自評。</p>
<p>緩解：每個 reflection loop 都設 step 上限 + 客觀停止訊號（test pass、external check）。</p>
<h3 id="router-classifier-過於複雜">Router classifier 過於複雜</h3>
<p>Classifier 本身就需要強模型、變成 router「省 cost」反而花更多。</p>
<p>訊號：classifier 用的模型跟下游 path 同等級或更強。</p>
<p>緩解：classifier 用最便宜的小模型；最便宜小模型若 confidence 不夠、改成「沒有 router、全部走主 path」。</p>
<h3 id="看不出基本模式的-ad-hoc-流程">看不出基本模式的 ad-hoc 流程</h3>
<p>完全自訂的 control flow、不能對應到任何標準模式、維護困難。</p>
<p>訊號：流程圖畫不出來、新人接手要花一週搞懂、改一個 bug 影響不知道擴散到哪。</p>
<p>緩解：重新設計、強制套用基本模式組合。不能套用通常代表設計過度複雜。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>四種基本模式（pipeline / router / parallel / reflection）的結構跟失敗模式分類。</li>
<li>Multi-call 成本（latency / cost / 失敗點累乘）的本質。</li>
<li>「先 single call baseline、不夠再分解」的設計順序。</li>
<li>五個常見反模式的識別。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 workflow framework（LangGraph、LlamaIndex Workflow、各家 DAG runner）的 API。</li>
<li>「最佳化」的具體技巧（caching、batching、streaming 整合）。</li>
<li>哪些 framework 對哪種模式支援好（會持續演化）。</li>
</ul>
<p>看到新 workflow framework 時、回到本章四模式 framing、看它支援哪些模式、有沒有解決常見反模式、能不能跟你的應用場景對齊。Framework 換代不影響這四個模式的本質結構。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8 Multi-Agent 拓樸</a>、當 single-thread 多 call 不夠用、需要平行專業化角色 / 跨產品 agent 重用時、進入 multi-agent 系統的拓樸設計。</p>
<p>設計完 workflow 後、進 production 還要評估資源、latency / throughput 取捨、observability 三層、降級設計、見 <a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production 資源規劃</a>。</p>
]]></content:encoded></item><item><title>4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool</title><link>https://tarrragon.github.io/blog/llm/04-applications/multi-agent-topology/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/multi-agent-topology/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns&lt;/a> 寫的是「多次 LLM call 怎麼組合」、四個基本模式（pipeline / router / parallel / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/reflection/" data-link-title="Reflection / Self-critique" data-link-desc="要求模型先輸出一版、再 critique 自己、再修改的 prompting / workflow 模式、有自身失敗模式">reflection&lt;/a>）解的是 single-thread 多 call 問題。當問題進一步複雜——需要平行的多個專業化角色、需要跨產品的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">agent&lt;/a> 重用、需要 agent 之間互相呼叫——就進入 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/multi-agent-system/" data-link-title="Multi-agent system" data-link-desc="多個 LLM agent 協作的系統、跟 multi-call workflow 的差異在控制流跟責任邊界、三種拓樸 flat / hierarchical / agent-as-tool">multi-agent system&lt;/a> 的領域。&lt;/p>
&lt;p>本章寫的是 multi-agent 系統的&lt;strong>拓樸結構&lt;/strong>：何時值得從多 call 走到多 agent、flat 跟 hierarchical 兩種拓樸的差異、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent-as-tool/" data-link-title="Agent-as-Tool" data-link-desc="把一個專責 agent 包成可被另一個 agent 呼叫的 tool，形成跨 agent 的責任邊界">agent-as-tool&lt;/a> 的 MCP 視角、specialization 跟 orchestration overhead 的核心 trade-off。具體 framework（CrewAI、AutoGen、LangGraph 多 agent 等）半年一個世代、本章不寫具體 API。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>判斷一個系統該停在 multi-call workflow 還是進入 multi-agent。&lt;/li>
&lt;li>區分 flat / hierarchical / agent-as-tool 三種拓樸、各自的適用場景。&lt;/li>
&lt;li>估算 specialization gain vs orchestration overhead 的 trade-off。&lt;/li>
&lt;li>識別 multi-agent 特有的失敗模式（循環依賴、責任歸屬模糊、context 重複傳遞）。&lt;/li>
&lt;li>把 agent-as-tool 對應回 MCP / function calling 的協議設計。&lt;/li>
&lt;/ol>
&lt;h2 id="從-multi-call-走到-multi-agent-的判讀">從 Multi-Call 走到 Multi-Agent 的判讀&lt;/h2>
&lt;p>Multi-agent 跟 multi-call 不是「agent 數量多寡」的差別、是控制流跟責任邊界的差別。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Multi-call workflow&lt;/th>
 &lt;th>Multi-agent system&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>控制流&lt;/td>
 &lt;td>主程式編排、每 call 是 step&lt;/td>
 &lt;td>Agent 自己決定下一步、可能呼叫其他 agent&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>角色&lt;/td>
 &lt;td>Step 跟 step 之間沒有「身份」、就是函數&lt;/td>
 &lt;td>每個 agent 有 role / 專業 / 工具集&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Context&lt;/td>
 &lt;td>主程式傳 context、step 不擁有 context&lt;/td>
 &lt;td>Agent 自帶 memory、有「自己知道的事」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重用&lt;/td>
 &lt;td>Step 是函數、容易 import 重用&lt;/td>
 &lt;td>Agent 是黑盒、跨系統重用要透過協議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗歸屬&lt;/td>
 &lt;td>Step 失敗、主程式接&lt;/td>
 &lt;td>Agent 失敗、可能 cascading 影響別的 agent&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀「該走 multi-agent」的四條件（&lt;strong>任一不滿足、就留在 multi-call&lt;/strong>）：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>角色差異顯著&lt;/strong>：不同 step 要不同 prompt / model / tool / memory。任一條件同質就退回 multi-call、硬拆成多 agent 只是換個名字、orchestration overhead 純增。&lt;/li>
&lt;li>&lt;strong>跨產品重用&lt;/strong>：同一個 agent 要被多團隊 / 多場景使用。單一 user / 單一場景的話、寫成函數比 agent 簡單。&lt;/li>
&lt;li>&lt;strong>真正平行 / 動態協作&lt;/strong>：多個 agent 各做自己的事最後合併、或哪些 agent 參與是 query-dependent。控制流可寫死、step 順序固定時、multi-call pipeline 已足夠。&lt;/li>
&lt;li>&lt;strong>團隊熟悉度足&lt;/strong>：multi-agent 失敗模式比 multi-call 多、debug 比較難。團隊還在學階段、debug 容易性 &amp;gt; 靈活性、先 stick to multi-call。&lt;/li>
&lt;/ul>
&lt;p>「先 multi-call、不夠再 multi-agent」是合理預設姿勢。Multi-agent 是「特定問題的解法」、不是「更高級的設計」。對應 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構&lt;/a> 的「先 single-call、不夠再 agent」反射、層級往上類似。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> 寫的是「多次 LLM call 怎麼組合」、四個基本模式（pipeline / router / parallel / <a href="/blog/llm/knowledge-cards/reflection/" data-link-title="Reflection / Self-critique" data-link-desc="要求模型先輸出一版、再 critique 自己、再修改的 prompting / workflow 模式、有自身失敗模式">reflection</a>）解的是 single-thread 多 call 問題。當問題進一步複雜——需要平行的多個專業化角色、需要跨產品的 <a href="/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">agent</a> 重用、需要 agent 之間互相呼叫——就進入 <a href="/blog/llm/knowledge-cards/multi-agent-system/" data-link-title="Multi-agent system" data-link-desc="多個 LLM agent 協作的系統、跟 multi-call workflow 的差異在控制流跟責任邊界、三種拓樸 flat / hierarchical / agent-as-tool">multi-agent system</a> 的領域。</p>
<p>本章寫的是 multi-agent 系統的<strong>拓樸結構</strong>：何時值得從多 call 走到多 agent、flat 跟 hierarchical 兩種拓樸的差異、<a href="/blog/llm/knowledge-cards/agent-as-tool/" data-link-title="Agent-as-Tool" data-link-desc="把一個專責 agent 包成可被另一個 agent 呼叫的 tool，形成跨 agent 的責任邊界">agent-as-tool</a> 的 MCP 視角、specialization 跟 orchestration overhead 的核心 trade-off。具體 framework（CrewAI、AutoGen、LangGraph 多 agent 等）半年一個世代、本章不寫具體 API。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>判斷一個系統該停在 multi-call workflow 還是進入 multi-agent。</li>
<li>區分 flat / hierarchical / agent-as-tool 三種拓樸、各自的適用場景。</li>
<li>估算 specialization gain vs orchestration overhead 的 trade-off。</li>
<li>識別 multi-agent 特有的失敗模式（循環依賴、責任歸屬模糊、context 重複傳遞）。</li>
<li>把 agent-as-tool 對應回 MCP / function calling 的協議設計。</li>
</ol>
<h2 id="從-multi-call-走到-multi-agent-的判讀">從 Multi-Call 走到 Multi-Agent 的判讀</h2>
<p>Multi-agent 跟 multi-call 不是「agent 數量多寡」的差別、是控制流跟責任邊界的差別。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Multi-call workflow</th>
          <th>Multi-agent system</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>控制流</td>
          <td>主程式編排、每 call 是 step</td>
          <td>Agent 自己決定下一步、可能呼叫其他 agent</td>
      </tr>
      <tr>
          <td>角色</td>
          <td>Step 跟 step 之間沒有「身份」、就是函數</td>
          <td>每個 agent 有 role / 專業 / 工具集</td>
      </tr>
      <tr>
          <td>Context</td>
          <td>主程式傳 context、step 不擁有 context</td>
          <td>Agent 自帶 memory、有「自己知道的事」</td>
      </tr>
      <tr>
          <td>重用</td>
          <td>Step 是函數、容易 import 重用</td>
          <td>Agent 是黑盒、跨系統重用要透過協議</td>
      </tr>
      <tr>
          <td>失敗歸屬</td>
          <td>Step 失敗、主程式接</td>
          <td>Agent 失敗、可能 cascading 影響別的 agent</td>
      </tr>
  </tbody>
</table>
<p>判讀「該走 multi-agent」的四條件（<strong>任一不滿足、就留在 multi-call</strong>）：</p>
<ul>
<li><strong>角色差異顯著</strong>：不同 step 要不同 prompt / model / tool / memory。任一條件同質就退回 multi-call、硬拆成多 agent 只是換個名字、orchestration overhead 純增。</li>
<li><strong>跨產品重用</strong>：同一個 agent 要被多團隊 / 多場景使用。單一 user / 單一場景的話、寫成函數比 agent 簡單。</li>
<li><strong>真正平行 / 動態協作</strong>：多個 agent 各做自己的事最後合併、或哪些 agent 參與是 query-dependent。控制流可寫死、step 順序固定時、multi-call pipeline 已足夠。</li>
<li><strong>團隊熟悉度足</strong>：multi-agent 失敗模式比 multi-call 多、debug 比較難。團隊還在學階段、debug 容易性 &gt; 靈活性、先 stick to multi-call。</li>
</ul>
<p>「先 multi-call、不夠再 multi-agent」是合理預設姿勢。Multi-agent 是「特定問題的解法」、不是「更高級的設計」。對應 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 的「先 single-call、不夠再 agent」反射、層級往上類似。</p>
<h2 id="三種拓樸">三種拓樸</h2>
<p>Multi-agent 的拓樸結構決定 agent 之間怎麼通訊、誰決定誰做什麼。三種主流拓樸各有適用場景。</p>
<h3 id="flat-拓樸all-to-all">Flat 拓樸：all-to-all</h3>
<p>所有 agent 同層級、可以互相呼叫、沒有固定 orchestrator。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">       Agent A ─────── Agent B
</span></span><span class="line"><span class="ln">2</span><span class="cl">         │  ╲          ╱  │
</span></span><span class="line"><span class="ln">3</span><span class="cl">         │   ╲        ╱   │
</span></span><span class="line"><span class="ln">4</span><span class="cl">         │    ╲      ╱    │
</span></span><span class="line"><span class="ln">5</span><span class="cl">       Agent C ─────── Agent D</span></span></code></pre></div><ul>
<li><strong>適用</strong>：agent 之間平等、任務需要動態協商（agent A 想知道 X、問 B 跟 D、再決定）。</li>
<li><strong>典型場景</strong>：研究型多 agent debate、模擬多個利害關係人協商。</li>
<li><strong>失敗模式</strong>：
<ul>
<li><strong>N² 通訊複雜度</strong>：agent 多了之後、通訊路徑潛在 N²、實務常較稀疏但難預測、cost / latency 上限不可控。</li>
<li><strong>無權威仲裁</strong>：兩個 agent 意見衝突、沒有第三方決定、容易死鎖。</li>
<li><strong>責任歸屬模糊</strong>：最終結果是誰決定的不清楚、debug 困難。</li>
</ul>
</li>
<li><strong>規模限制</strong>：實務上 flat 拓樸超過 5–6 個 agent 就難維護、不推薦大規模。</li>
</ul>
<h3 id="hierarchical-拓樸orchestrator--specialists">Hierarchical 拓樸：orchestrator + specialists</h3>
<p>一個 orchestrator agent 對外、底下若干 specialist agent、orchestrator 決定 dispatch 給誰、合併結果回 user。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">              User
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">                │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">          ┌─────────────┐
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">          │ Orchestrator │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">          └──┬──┬──┬──┬─┘
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">             │  │  │  │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        ┌────┘  │  │  └────┐
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   Specialist  │  │   Specialist
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">       A    Spec  Spec      D
</span></span><span class="line"><span class="ln">10</span><span class="cl">             B    C</span></span></code></pre></div><ul>
<li><strong>適用</strong>：對 user 要單一介面、底下 agent 專業化、orchestrator 知道每個 specialist 的 capability。</li>
<li><strong>典型場景</strong>：智慧家庭中央控制（user 對 orchestrator 說話、orchestrator 派給 climate / security / energy agent）、複雜客服系統（orchestrator 派給 product / refund / billing 不同 specialist）。</li>
<li><strong>失敗模式</strong>：
<ul>
<li><strong>Orchestrator 變單點瓶頸</strong>：所有請求過 orchestrator、它的 prompt / model 限制整個系統能力。</li>
<li><strong>Specialist 之間訊息傳遞要過 orchestrator</strong>：增加 latency、容易丟細節。</li>
<li><strong>Orchestrator 不知道何時該派誰</strong>：需要動態描述 specialist capability、複雜 query 容易 dispatch 錯。</li>
</ul>
</li>
<li><strong>變體</strong>：multi-level hierarchy（orchestrator 下面還有 sub-orchestrator），實務上 2 層夠用、3 層以上 overhead 大於 specialization gain。</li>
</ul>
<h3 id="agent-as-toolagent-互通就是-tool-call">Agent-as-Tool：agent 互通就是 tool call</h3>
<p>把每個 agent 包成「另一個 agent 的 tool」、agent A 呼叫 agent B 跟呼叫 weather API 在介面上一樣——都是 tool call。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Agent A
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├── tool: weather_api
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ├── tool: database_query
</span></span><span class="line"><span class="ln">4</span><span class="cl">  └── tool: agent_B  ←── 內部其實是另一個 agent loop
</span></span><span class="line"><span class="ln">5</span><span class="cl">                            └── 它也有自己的 tools
</span></span><span class="line"><span class="ln">6</span><span class="cl">                                ├── tool: code_executor
</span></span><span class="line"><span class="ln">7</span><span class="cl">                                └── tool: agent_C</span></span></code></pre></div><ul>
<li><strong>適用</strong>：agent 之間有清楚的「誰呼叫誰」、不是平等協商；想透過標準協議（function calling / MCP）讓 agent 跨系統重用。</li>
<li><strong>典型場景</strong>：<a href="/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP</a> 的 tool primitive 視角下、<a href="/blog/llm/knowledge-cards/agent-as-tool/" data-link-title="Agent-as-Tool" data-link-desc="把一個專責 agent 包成可被另一個 agent 呼叫的 tool，形成跨 agent 的責任邊界">agent-as-tool</a> 可以包成 MCP server 暴露、client agent 把它當 tool 用。跨組織 agent 互通常走這個模式。注意 MCP 還有 resources / prompts 另外兩類 primitive、不是所有 MCP server 都是 agent-as-tool。</li>
<li><strong>跟 hierarchical 的關係</strong>：agent-as-tool 是 hierarchical 的一個實作策略——orchestrator 把 specialist agent 當 tool。差異在於：hierarchical 可能是同進程內的緊耦合、agent-as-tool 走標準協議、跨進程 / 跨組織 / 可替換。</li>
<li><strong>失敗模式</strong>：
<ul>
<li><strong>協議的 schema 太薄</strong>：agent 跟 agent 之間的 input/output 用 string 傳、丟結構資訊、下游難解析。</li>
<li><strong>Cascading failure</strong>：下游 agent 失敗、上游 agent 不知道為什麼失敗、誤判繼續。</li>
<li><strong>重複 context 傳遞</strong>：每次呼叫都要重新 brief 一次下游 agent、token cost 爆。緩解：下游 agent 自帶 session memory（見 <a href="/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19 agent memory architecture</a>）。</li>
</ul>
</li>
</ul>
<h3 id="三種拓樸的選擇">三種拓樸的選擇</h3>
<table>
  <thead>
      <tr>
          <th>場景特性</th>
          <th>推薦拓樸</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2–4 個 agent、需要動態協商</td>
          <td>Flat</td>
      </tr>
      <tr>
          <td>多個專業 agent、單一對外介面</td>
          <td>Hierarchical</td>
      </tr>
      <tr>
          <td>跨組織 / 跨進程 / 標準化重用</td>
          <td>Agent-as-tool</td>
      </tr>
      <tr>
          <td>大規模（10+ agents）、固定協作模式</td>
          <td>Hierarchical 多層</td>
      </tr>
      <tr>
          <td>想簡單開始</td>
          <td>Hierarchical 兩層</td>
      </tr>
  </tbody>
</table>
<p>教材建議的組合：對外是 hierarchical（單一 orchestrator）、orchestrator 內部跟 specialist 通訊走 agent-as-tool 協議（如 MCP tool primitive）、specialist 之間用 flat 模式平等溝通。實務上組合方式因團隊跟產品差異很大、這只是一個合理起點。</p>
<h2 id="specialization-gain-vs-orchestration-overhead">Specialization Gain vs Orchestration Overhead</h2>
<p>Multi-agent 的核心 trade-off 是<strong>專業化收益跟協調成本的拉鋸</strong>。</p>
<h3 id="specialization-gain把-agent-拆細的好處">Specialization gain：把 agent 拆細的好處</h3>
<ul>
<li><strong>單一責任</strong>：每個 agent prompt 短、focus 清楚、debugging 容易。</li>
<li><strong>獨立優化</strong>：每個 agent 可以用不同 model（具體 routing 思路屬於 <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> router 模式）、不同 prompt、獨立 eval。</li>
<li><strong>重用</strong>：同一個 specialist 跨多個系統用、攤平訓練 / 設計成本。</li>
<li><strong>平行</strong>：獨立 agent 可平行跑、latency 降。</li>
</ul>
<h3 id="orchestration-overhead拆細的成本">Orchestration overhead：拆細的成本</h3>
<ul>
<li><strong>Context 傳遞成本</strong>：每個 agent 要被 brief、context 重複傳、token 累積。</li>
<li><strong>Latency 累積</strong>：每跳一個 agent 加一個 LLM call 的 latency、跨 agent chain 跟 reflection / multi-step retrieval 一樣會累積。</li>
<li><strong>失敗模式多</strong>：每個 agent 自己會 drift、agent 之間也會誤判、debug 比 single agent 難。</li>
<li><strong>責任歸屬</strong>：bug 出現時、定位是哪個 agent 跑偏要看完整 trace。</li>
</ul>
<h3 id="何時-specialization-划算">何時 specialization 划算</h3>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>Specialization 划算？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Agent 之間 role 差異顯著</td>
          <td>划算</td>
      </tr>
      <tr>
          <td>Agent 之間 role 同質</td>
          <td>不划算</td>
      </tr>
      <tr>
          <td>重用機會多（多產品 / 多場景）</td>
          <td>划算</td>
      </tr>
      <tr>
          <td>單一場景 / 單一團隊</td>
          <td>不划算</td>
      </tr>
      <tr>
          <td>每個 sub-task 各自有客觀 eval</td>
          <td>划算</td>
      </tr>
      <tr>
          <td>Sub-task 無法獨立評估</td>
          <td>不划算（debugging 困難）</td>
      </tr>
      <tr>
          <td>Latency 容忍度高（後台 batch）</td>
          <td>划算</td>
      </tr>
      <tr>
          <td>即時 chatbot</td>
          <td>不划算（orchestration latency 殺死 UX）</td>
      </tr>
  </tbody>
</table>
<p>兩個容易低估的條件展開：</p>
<ul>
<li><strong>「sub-task 無法獨立評估」為何讓 debugging 困難</strong>：當 specialist agent 出問題、若沒有 component-level eval、要從 final output 倒推到「哪個 agent 跑偏」要看完整 trace + 人工讀。Single agent 失敗只需查一個 agent 的 trace、multi-agent 失敗要查 N 個、且 cascading failure 讓 root cause 模糊。要配 sub-task 客觀 eval（如 <a href="/blog/llm/knowledge-cards/retrieval-recall/" data-link-title="Retrieval Recall" data-link-desc="衡量 RAG 檢索是否把應該命中的文件或 chunk 放進 top-k 結果，是 component-level eval 的核心指標">retrieval recall</a>、抽取 accuracy）才能秒抓問題層、不然 specialization 換來的是更貴的 debug。</li>
<li><strong>「orchestration latency 殺死 UX」的量級</strong>：每跳一個 agent 加一個 LLM call（雲端旗艦 ~1-3s）。Hierarchical 三層、user query 到回應走 3+ 次 LLM、累積 3-10s。即時 chatbot 的 latency budget 通常 &lt; 3s、multi-agent 容易超標。Workaround：specialist 換小 model、或某些 step 改 deterministic、或退回 single agent + multi-step prompt。</li>
</ul>
<h3 id="先粗再細的演化路徑">「先粗、再細」的演化路徑</h3>
<p>實務多採演化路徑、不是一開始就設計多 agent：</p>
<ol>
<li><strong>Single agent 開始</strong>：把整個任務塞一個 agent、看跑得起來嗎。</li>
<li><strong>發現某子任務 systematic 失敗</strong>：那個子任務拆出來、變成 specialist agent。</li>
<li><strong>更多子任務需要拆</strong>：演化成 hierarchical。</li>
<li><strong>要跨產品重用</strong>：把某個 specialist 包成 agent-as-tool（透過 MCP）。</li>
</ol>
<p>這條路徑的好處是<strong>每一步都有具體痛點驅動拆分</strong>、不是「為了 multi-agent 而 multi-agent」。</p>
<h2 id="multi-agent-特有的失敗模式">Multi-Agent 特有的失敗模式</h2>
<p>除了單 agent 共通的失敗（context drift / goal drift / tool misread、見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>）、multi-agent 系統有自己特有的失敗模式：</p>
<h3 id="循環依賴">循環依賴</h3>
<p>循環依賴是 agent 呼叫圖在執行期才形成 cycle、靜態 declaration 抓不出來、結果無限執行。例：Agent A 呼叫 B、B 呼叫 C、C 又呼叫 A、形成 cycle。</p>
<p>緩解：</p>
<ul>
<li>Call stack 監測、深度超過 N 強制中止。</li>
<li>Agent 設計時明確 declare 它會呼叫哪些下游 agent、靜態 check 不出 cycle。</li>
<li>Cycle 的合法用例（如 negotiation）要明確設停止條件。</li>
</ul>
<h3 id="責任歸屬模糊">責任歸屬模糊</h3>
<p>責任歸屬模糊是 multi-agent 的 cascading 結構讓 final output 的「哪個 agent 出錯」可能跨多個 agent 累積、debug 時不知道從哪查。</p>
<p>緩解：</p>
<ul>
<li>強制 trace 全部 agent call（見 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a>）。</li>
<li>每個 agent 明確 declare 它對 final output 的貢獻範圍。</li>
<li>Error 用結構化、明確標出 raised by 哪個 agent。</li>
</ul>
<h3 id="context-重複傳遞">Context 重複傳遞</h3>
<p>Context 重複傳遞是 agent-as-tool 介面下、上游每次呼叫下游都要重新 brief 一遍、缺乏跨 call 的狀態保留、累積成 token cost 跟 latency 雙重浪費。</p>
<p>緩解：</p>
<ul>
<li>Specialist agent 自帶 session memory、不用每次 brief（見 <a href="/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19 agent memory architecture</a>）。</li>
<li>共享 context（global state、reference passing）取代複製。</li>
<li>Agent-as-tool 協議設計時、輸入 schema 包含「已 brief 過、跳過 intro」flag。</li>
</ul>
<h3 id="orchestrator-成為單點認知瓶頸">Orchestrator 成為單點認知瓶頸</h3>
<p>Orchestrator 是 hierarchical 拓樸的核心、要理解所有 specialist 跟分派邏輯、它的 prompt / capability 限制整個系統上限。換 specialist 容易（介面標準）、換 orchestrator 牽動所有 routing 邏輯（耦合深）。</p>
<p>緩解：</p>
<ul>
<li>Orchestrator 的 dispatch 邏輯外部化（不寫在 prompt 內、寫在 deterministic routing rule）。</li>
<li>Specialist 自己 declare capability（用 OpenAPI / MCP schema）、orchestrator 動態讀、不寫死。</li>
</ul>
<h3 id="agent-之間互相-hallucinate">Agent 之間互相 hallucinate</h3>
<p>Agent 之間互相 hallucinate 是 agent 介面信任假設失效——上游 agent 給的 input 被視為「可信」、下游沒驗證就執行、hallucinated 內容沿著 agent chain 層層放大。</p>
<p>緩解：</p>
<ul>
<li>Agent 之間互通也要走 schema validation（見 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a> guardrail 段）。</li>
<li>Critical path 加 deterministic check、不只靠 LLM 自評。</li>
</ul>
<h2 id="跟-mcp--function-calling-的協議對應">跟 MCP / Function Calling 的協議對應</h2>
<p><a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a> 寫 function calling / structured output / MCP 的層級差異。Multi-agent 拓樸的 agent-as-tool 模式直接對應 MCP：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Agent-as-tool 在 MCP 視角下的展開：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">Client Agent
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ├── MCP client
</span></span><span class="line"><span class="ln">5</span><span class="cl">  │     ↓ stdio / SSE / HTTP
</span></span><span class="line"><span class="ln">6</span><span class="cl">  │   MCP server #1 ← 包了一個 specialist agent
</span></span><span class="line"><span class="ln">7</span><span class="cl">  │   MCP server #2 ← 包了另一個 specialist agent
</span></span><span class="line"><span class="ln">8</span><span class="cl">  │   MCP server #3 ← 包了一個外部 service
</span></span><span class="line"><span class="ln">9</span><span class="cl">  └── 對 client agent 來說、三者介面一致、都是 tool</span></span></code></pre></div><p>這個 framing 的價值：<strong>目前 agent 跨組織重用的主要工程問題是 agent-as-tool 協議普及度</strong>——MCP 是當前的主流選項。當業界對協議 schema 達成共識（無論是 MCP 還是後續演化的標準）、agent-as-tool 拓樸的工程成本會大幅下降。</p>
<p>判讀訊號：自家 agent 想暴露給其他團隊用、預設選 MCP server 包裝、不要設計 proprietary protocol。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Multi-call vs multi-agent 的判讀框架（控制流 / 角色 / context / 重用 / 失敗歸屬五維度）。</li>
<li>Flat / hierarchical / agent-as-tool 三種拓樸的結構分類。</li>
<li>Specialization gain vs orchestration overhead 的 trade-off。</li>
<li>「先粗、再細」的演化路徑反射。</li>
<li>Multi-agent 特有的五類失敗模式跟緩解。</li>
<li>Agent-as-tool 對應 MCP 的 framing。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 multi-agent framework（CrewAI / AutoGen / LangGraph multi-agent 等會持續演化）。</li>
<li>MCP server 生態的成熟度（普及度會大幅影響 agent-as-tool 的工程成本）。</li>
<li>各家 framework 對 multi-agent 失敗模式的 handling 工具（debugging / tracing tooling）。</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production 部署資源評估</a>、把多 LLM call / 多 agent 系統的 cost / latency / capacity 落到具體 production 評估。Multi-agent 跟 multi-call 的對比基礎見 <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a>、agent 自身的失敗模式見 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a>、MCP 協議層討論見 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a>。</p>
]]></content:encoded></item><item><title>4.9 Production 部署的資源評估原理</title><link>https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/</guid><description>&lt;p>LLM 應用從本地實驗跨到 production 是個 phase transition、不是線性放大。本地 single-user 場景的「跑得起來」變 production 場景就要回答全新一組問題：100 個 user 同時打進來怎麼辦、每個 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token&lt;/a> 要多少錢、p99 latency 怎麼控、model service down 了怎麼處理。&lt;/p>
&lt;p>本章寫的是「&lt;strong>從本地實驗 → production 該想清楚的維度&lt;/strong>」、focus 在跨工具世代不變的原理。具體 framework（vLLM、TGI、Triton、SGLang）跟雲端服務（OpenAI / Anthropic / Bedrock）的選型不展開——這些半年一個世代、寫了會過時。本章建立的是「無論用哪套工具、都該回答」的設計取捨清單。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent&lt;/a> 對應「應用怎麼設計」、本章對應「應用怎麼跑」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>列出 production LLM 部署該評估的 6 個 dimension。&lt;/li>
&lt;li>解釋 single-user benchmark 為什麼不能直接 extrapolate 到 multi-user 場景。&lt;/li>
&lt;li>區分 latency-sensitive 跟 throughput-sensitive 應用的設計差別。&lt;/li>
&lt;li>對成本模型（$/request、$/token、$/month）做合理估算。&lt;/li>
&lt;/ol>
&lt;h2 id="從本地到-production-的-phase-transition">從本地到 production 的 phase transition&lt;/h2>
&lt;p>本地 LLM 跑 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP&lt;/a> 的 baseline（&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/rag-mcp-resources/" data-link-title="Hands-on：RAG / MCP 的資源 footprint" data-link-desc="RAG ingest / query / MCP server 三階段的 RAM / 磁碟 / process 實測、多模型並存的 RAM 衝突、本地 LLM 跑 RAG 跟單純 chat 的差異">hands-on 章節&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>本地（single-user）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>並發 user&lt;/td>
 &lt;td>1&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Latency 要求&lt;/td>
 &lt;td>秒級 OK&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Index 大小&lt;/td>
 &lt;td>&amp;lt; 100 MB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost&lt;/td>
 &lt;td>一次性硬體&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Uptime&lt;/td>
 &lt;td>自己重啟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>觀測&lt;/td>
 &lt;td>&lt;code>tail log&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Production 場景每個維度都跳一個量級：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Production（multi-tenant）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>並發 user&lt;/td>
 &lt;td>10 - 10000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Latency 要求&lt;/td>
 &lt;td>p50 &amp;lt; 500 ms、p99 &amp;lt; 2 s&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Index 大小&lt;/td>
 &lt;td>GB - TB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost&lt;/td>
 &lt;td>$ / request、$ / token、$ / month&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Uptime&lt;/td>
 &lt;td>99.9% SLA&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>觀測&lt;/td>
 &lt;td>metrics、traces、dashboards&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個維度跳一個量級的 implication 不是「資源 × 10」、是「全新的失敗模式 + 新的設計取捨」。&lt;/p></description><content:encoded><![CDATA[<p>LLM 應用從本地實驗跨到 production 是個 phase transition、不是線性放大。本地 single-user 場景的「跑得起來」變 production 場景就要回答全新一組問題：100 個 user 同時打進來怎麼辦、每個 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a> 要多少錢、p99 latency 怎麼控、model service down 了怎麼處理。</p>
<p>本章寫的是「<strong>從本地實驗 → production 該想清楚的維度</strong>」、focus 在跨工具世代不變的原理。具體 framework（vLLM、TGI、Triton、SGLang）跟雲端服務（OpenAI / Anthropic / Bedrock）的選型不展開——這些半年一個世代、寫了會過時。本章建立的是「無論用哪套工具、都該回答」的設計取捨清單。</p>
<p>跟 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> / <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use</a> / <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent</a> 對應「應用怎麼設計」、本章對應「應用怎麼跑」。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>列出 production LLM 部署該評估的 6 個 dimension。</li>
<li>解釋 single-user benchmark 為什麼不能直接 extrapolate 到 multi-user 場景。</li>
<li>區分 latency-sensitive 跟 throughput-sensitive 應用的設計差別。</li>
<li>對成本模型（$/request、$/token、$/month）做合理估算。</li>
</ol>
<h2 id="從本地到-production-的-phase-transition">從本地到 production 的 phase transition</h2>
<p>本地 LLM 跑 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> / <a href="/blog/llm/knowledge-cards/mcp/" data-link-title="MCP（Model Context Protocol）" data-link-desc="LLM application ↔ 外部 tool server 之間的標準化協議、複用 OpenAI 相容 API 的成功模式">MCP</a> 的 baseline（<a href="/blog/llm/01-local-llm-services/hands-on/rag-mcp-resources/" data-link-title="Hands-on：RAG / MCP 的資源 footprint" data-link-desc="RAG ingest / query / MCP server 三階段的 RAM / 磁碟 / process 實測、多模型並存的 RAM 衝突、本地 LLM 跑 RAG 跟單純 chat 的差異">hands-on 章節</a>）：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>本地（single-user）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>並發 user</td>
          <td>1</td>
      </tr>
      <tr>
          <td>Latency 要求</td>
          <td>秒級 OK</td>
      </tr>
      <tr>
          <td>Index 大小</td>
          <td>&lt; 100 MB</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>一次性硬體</td>
      </tr>
      <tr>
          <td>Uptime</td>
          <td>自己重啟</td>
      </tr>
      <tr>
          <td>觀測</td>
          <td><code>tail log</code></td>
      </tr>
  </tbody>
</table>
<p>Production 場景每個維度都跳一個量級：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Production（multi-tenant）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>並發 user</td>
          <td>10 - 10000</td>
      </tr>
      <tr>
          <td>Latency 要求</td>
          <td>p50 &lt; 500 ms、p99 &lt; 2 s</td>
      </tr>
      <tr>
          <td>Index 大小</td>
          <td>GB - TB</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>$ / request、$ / token、$ / month</td>
      </tr>
      <tr>
          <td>Uptime</td>
          <td>99.9% SLA</td>
      </tr>
      <tr>
          <td>觀測</td>
          <td>metrics、traces、dashboards</td>
      </tr>
  </tbody>
</table>
<p>每個維度跳一個量級的 implication 不是「資源 × 10」、是「全新的失敗模式 + 新的設計取捨」。</p>
<h2 id="維度-1concurrent-users--throughput">維度 1：Concurrent users / Throughput</h2>
<h3 id="為什麼這個維度最關鍵">為什麼這個維度最關鍵</h3>
<p>本地 single-user 的 baseline 數字（<a href="/blog/llm/01-local-llm-services/hands-on/rag-mcp-resources/" data-link-title="Hands-on：RAG / MCP 的資源 footprint" data-link-desc="RAG ingest / query / MCP server 三階段的 RAM / 磁碟 / process 實測、多模型並存的 RAM 衝突、本地 LLM 跑 RAG 跟單純 chat 的差異">hands-on</a> 紀錄的 RAM / latency）<strong>在 multi-user 場景下幾乎無法 extrapolate</strong>、根因是資源爭用會放大原本看不到的成本：</p>
<ul>
<li>100 個 user 同時送 request → 不是「同樣 latency × 100」、是「queueing + memory contention + GPU 排隊」、單個 user 的 latency 可能漲 10×</li>
<li>同樣 model 服務 N 個 user → KV cache 占用要乘以 N、單卡 GPU 在容量限制下可能裝不下</li>
<li>Single-user 「200 ms latency」可能 production 變「p99 5 秒」</li>
</ul>
<h3 id="key-conceptbatching">Key concept：batching</h3>
<p><a href="/blog/llm/knowledge-cards/batching/" data-link-title="Batching" data-link-desc="多 request 一起跑、攤平 model load 成本：production LLM inference 的核心優化、決定 throughput vs latency 取捨">Batching</a> 跟 <a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> 設計讓 GPU 能多 user 的 request 一次 forward pass、是 production <a href="/blog/llm/knowledge-cards/inference-server/" data-link-title="Inference Server" data-link-desc="載入模型權重、處理 prompt、產生 token 的常駐 process">inference server</a> 的核心優化。但 batching 也帶取捨：</p>
<ul>
<li><strong>靜態 batching</strong>：等湊滿 N 個 request 才跑、提高 throughput、犧牲首字延遲</li>
<li><strong>連續 batching（continuous batching）</strong>：vLLM / TGI 等用、新 request 動態加入正在跑的 batch、平衡 throughput + latency</li>
<li><strong>No batching</strong>：每 request 獨立跑、latency 低、GPU 利用率差</li>
</ul>
<p>選 batching 策略主要取決於 latency 跟 throughput 哪個重要：</p>
<table>
  <thead>
      <tr>
          <th>應用場景</th>
          <th>適合 batching 策略</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>互動式對話（IDE plugin、chatbot UI）</td>
          <td>continuous batching、低 latency 優先</td>
      </tr>
      <tr>
          <td>批次處理（document summarization、code review）</td>
          <td>static batching、throughput 優先</td>
      </tr>
      <tr>
          <td>Embedding 服務</td>
          <td>batching 越大越好、embedding 是純 forward pass、batch 16-128 都 OK</td>
      </tr>
  </tbody>
</table>
<h3 id="評估-concurrent-throughput">評估 concurrent throughput</h3>
<p>要做的測試（不在本章 hands-on、是 framework）：</p>
<ol>
<li><strong>Single-user baseline</strong>：measure single request 在 idle server 上的 latency</li>
<li><strong>N-user load test</strong>：用 <a href="https://k6.io">k6</a> / <a href="https://github.com/tsenart/vegeta">vegeta</a> / 自寫 async client 跑 1、10、100 個並發 request</li>
<li><strong>觀察 p50 / p95 / p99 latency 隨並發數變化</strong>：通常 &lt; N=batch_size 時平、超過 batch_size 後 latency 線性漲</li>
<li><strong>GPU memory 飽和點</strong>：tokens-in-flight 超過某個量、新 request 開始排隊</li>
</ol>
<p>實務評估公式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Max concurrent users (steady state)
</span></span><span class="line"><span class="ln">2</span><span class="cl">    = (GPU memory available - model weights) / (per-user KV cache size)</span></span></code></pre></div><p>例：H100 80 GB - 31B model 60 GB = 20 GB 可用 / 每 user 平均 200 MB KV cache = 100 個並發 user。</p>
<p>公式的失效條件（用這幾個 signal 判讀公式何時不可信）：</p>
<ul>
<li><strong>變長 context</strong>：per-user KV cache 隨 context 長度線性增長、長 context 用戶（10K+ tokens）的 KV cache 是短 context 用戶的 5-10 倍、用平均值會嚴重低估。修法：依 P95 context 長度估、不用 average。</li>
<li><strong>Prefix cache 啟用</strong>：vLLM、TGI 等用 prefix sharing 大幅省 KV cache、實際容量比公式高 2-3 倍。修法：跑實測量 prefix cache hit rate。</li>
<li><strong>Speculative decoding</strong>：drafter 跟 target 的 KV cache 都要算進去、每 user 佔用會比 dense baseline 高 10-20%。修法：用 drafter+target 合計算。</li>
<li><strong>不同 batching 策略</strong>：static batching 上限是「batch_size × 等待時間」、continuous batching 是「平均 in-flight tokens」、不同策略下公式的「per-user」定義不同。</li>
</ul>
<p>但這是上限、實際還要考慮 latency target。</p>
<h2 id="維度-2latency-budget">維度 2：Latency budget</h2>
<h3 id="latency-sensitive-vs-throughput-sensitive">Latency-sensitive vs throughput-sensitive</h3>
<p>兩類應用的設計取捨完全不同：</p>
<table>
  <thead>
      <tr>
          <th>屬性</th>
          <th>Latency-sensitive</th>
          <th>Throughput-sensitive</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>範例</td>
          <td>IDE 補完、chat UI、search assistant</td>
          <td>批次標籤、文件摘要、離線 RAG ingest</td>
      </tr>
      <tr>
          <td>目標 metric</td>
          <td>p99 latency</td>
          <td>tokens / second / GPU</td>
      </tr>
      <tr>
          <td>User 經驗影響</td>
          <td>直接（卡住）</td>
          <td>間接（總時間）</td>
      </tr>
      <tr>
          <td>Batching</td>
          <td>小 batch / continuous</td>
          <td>大 batch</td>
      </tr>
      <tr>
          <td>資源規劃</td>
          <td>預留 headroom 給 spike</td>
          <td>跑滿 GPU 利用率</td>
      </tr>
  </tbody>
</table>
<p>混合應用（如 chat with RAG）有兩段：retrieval（throughput-friendly、可 batch）+ generation（latency-sensitive、要 stream）。兩段獨立優化。</p>
<h3 id="latency-預算分配">Latency 預算分配</h3>
<p>一個 RAG 應用的 p99 latency 是各段加總：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Total p99 = client → API gateway → retrieval → LLM prefill → LLM decode → response stream
</span></span><span class="line"><span class="ln">2</span><span class="cl">         ≈ 50 ms      20 ms        50 ms        500 ms       1500 ms      100 ms
</span></span><span class="line"><span class="ln">3</span><span class="cl">         ≈ 2.2 seconds</span></span></code></pre></div><p>如果 p99 budget 是 2 秒、要先確認<strong>最大消耗段是哪個</strong>：</p>
<ul>
<li>通常 LLM generation 是最大、是優化重心</li>
<li>Retrieval 在大 corpus 場景可能超過 100 ms、要 index 優化（HNSW、近似 nearest neighbor）</li>
<li>API gateway 通常可忽略、超過 50 ms 就有 SRE 議題</li>
</ul>
<p>各段監控分開、把監控拆到各段才找得到 root cause；只看 total latency 會錯失定位線索。</p>
<h2 id="維度-3cost-model">維度 3：Cost model</h2>
<h3 id="三種計費單位">三種計費單位</h3>
<table>
  <thead>
      <tr>
          <th>單位</th>
          <th>怎麼算</th>
          <th>適合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>$/request</td>
          <td>每 API call 固定價</td>
          <td>簡單應用、可預測流量</td>
      </tr>
      <tr>
          <td>$/token</td>
          <td>看 input + output token 數</td>
          <td>OpenAI / Anthropic 主流、混合輸入長度應用</td>
      </tr>
      <tr>
          <td>$/server-hour</td>
          <td>自家跑 GPU instance、月租</td>
          <td>高 throughput、可預測 utilization</td>
      </tr>
  </tbody>
</table>
<p>雲端 API（OpenAI / Anthropic）幾乎都 $/token、給定 model 不同 price tier。自家跑（vLLM on Lambda Labs / RunPod）是 $/server-hour。</p>
<h3 id="成本估算-worked-example">成本估算 worked example</h3>
<p>假設應用：</p>
<ul>
<li>1000 active users / day</li>
<li>每 user 平均 10 requests / day</li>
<li>每 request 平均 1000 input tokens + 500 output tokens</li>
<li>用 Claude Sonnet 4.6（假設 $3 input / $15 output per million tokens）</li>
</ul>
<p>每日 cost：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">total_requests = 1000 × 10 = 10000 / day
</span></span><span class="line"><span class="ln">2</span><span class="cl">input_tokens = 10000 × 1000 = 10M
</span></span><span class="line"><span class="ln">3</span><span class="cl">output_tokens = 10000 × 500 = 5M
</span></span><span class="line"><span class="ln">4</span><span class="cl">daily_cost = 10M × $3/M + 5M × $15/M = $30 + $75 = $105 / day
</span></span><span class="line"><span class="ln">5</span><span class="cl">monthly_cost ≈ $3150</span></span></code></pre></div><p>跑自家 GPU 比較：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">H100 instance: ~$2/hour（以 2026 年 spot price 為例、實際隨雲廠商與當期報價變動）
</span></span><span class="line"><span class="ln">2</span><span class="cl">H100 monthly = $2 × 24 × 30 = $1440
</span></span><span class="line"><span class="ln">3</span><span class="cl">若 utilization &gt; 50% 且團隊有 SRE 能力維運、自架較划算
</span></span><span class="line"><span class="ln">4</span><span class="cl">若 utilization &lt; 30%、或團隊無 GPU 維運經驗、API 較划算</span></span></code></pre></div><p><strong>Breakeven 點通常在「持續高 utilization + 團隊有維運能力」</strong>——尖峰流量短的應用、或團隊無 GPU 維運經驗、API 更划算（不用養閒置 capacity 跟 SRE 人力）。實際判讀還要加合規 / 資料主權 / vendor lock-in 等非價格因素。</p>
<h3 id="hidden-cost">Hidden cost</h3>
<p>容易漏算的：</p>
<ul>
<li><strong>Egress bandwidth</strong>：cloud GPU instance 出流量、AWS / GCP 都 $/GB</li>
<li><strong>Storage</strong>：vector DB / log retention / metric retention</li>
<li><strong>失敗 retry</strong>：5xx error 自動 retry、token 重算</li>
<li><strong>Cold start</strong>：scale-to-zero 設定、cold start 浪費 5-30 秒 GPU time / 次</li>
</ul>
<h2 id="維度-4storage--vector-db">維度 4：Storage / Vector DB</h2>
<p>本地 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> demo 用 pickle、production 不行——pickle 不支援並發 read、不支援 update、不支援 partition、必須換 <a href="/blog/llm/knowledge-cards/vector-database/" data-link-title="Vector Database" data-link-desc="為高維向量 (embedding) 設計的儲存 &#43; 近似最近鄰 (ANN) 檢索系統：RAG 從 prototype 跨到 production 的關鍵元件">vector database</a>。</p>
<h3 id="vector-db-的設計取捨">Vector DB 的設計取捨</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Hosted vs self-host</strong></td>
          <td>Hosted（Pinecone、Weaviate Cloud）省維護、self-host 控制成本</td>
      </tr>
      <tr>
          <td><strong>In-memory vs disk-based</strong></td>
          <td>In-memory 快但記憶體限制、disk-based 大但 latency 高</td>
      </tr>
      <tr>
          <td><strong>HNSW vs flat</strong></td>
          <td>HNSW 近似但 sublinear、flat 精確但 linear</td>
      </tr>
      <tr>
          <td><strong>Update strategy</strong></td>
          <td>Periodic batch index rebuild vs incremental update</td>
      </tr>
  </tbody>
</table>
<p>具體選型半年一變、本章不展開。<strong>設計時要回答的問題</strong>：</p>
<ol>
<li>Corpus 多大？1M 以下 in-memory 就好、1M 以上要 disk-based</li>
<li>Update 頻率？每天一次 vs 即時、影響 architecture</li>
<li>Latency target？&lt; 50 ms 要 in-memory / HNSW、&lt; 200 ms 用 disk-based</li>
<li>並發 query 量？每秒 100 query 跟每秒 10000 query 設計完全不同</li>
</ol>
<h3 id="index-大小成長">Index 大小成長</h3>
<p>從 hands-on 章節 extrapolate：</p>
<table>
  <thead>
      <tr>
          <th>Corpus 規模</th>
          <th>Index 大小（含 chunks + embeddings）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1K docs</td>
          <td>~50 MB</td>
      </tr>
      <tr>
          <td>100K docs</td>
          <td>~5 GB</td>
      </tr>
      <tr>
          <td>1M docs</td>
          <td>~50 GB</td>
      </tr>
      <tr>
          <td>10M docs</td>
          <td>~500 GB</td>
      </tr>
      <tr>
          <td>100M docs</td>
          <td>~5 TB</td>
      </tr>
  </tbody>
</table>
<p>10M docs 以上、單機（256GB RAM、商用 SSD）放不進 in-memory index、要 sharding + 分散式 index。</p>
<h2 id="維度-5observability">維度 5：Observability</h2>
<p>Single-user <code>tail log</code> 不夠 production 用。要看的 metric：</p>
<h3 id="latency-metrics">Latency metrics</h3>
<ul>
<li><strong>TTFT (Time to First Token)</strong>：user-perceived「響應時間」、streaming 場景關鍵</li>
<li><strong>TPS (Tokens per second)</strong>：generation 速度</li>
<li><strong>End-to-end latency</strong>：含 retrieval + LLM + post-processing</li>
<li><strong>Per-percentile breakdown</strong>：p50 / p90 / p95 / p99——p99 反映最差 user 體驗</li>
</ul>
<h3 id="throughput-metrics">Throughput metrics</h3>
<ul>
<li><strong>Requests per second</strong>：API 端 RPS</li>
<li><strong>Tokens per second</strong>（aggregate）：GPU 整體 throughput</li>
<li><strong>Queue depth</strong>：等待 batch 的 request 數量、暴漲表示 overload</li>
</ul>
<h3 id="cost-metrics">Cost metrics</h3>
<ul>
<li><strong>$ per active user per day</strong>：產品經濟學基本盤</li>
<li><strong>Cost per session</strong>：互動式應用單位成本</li>
<li><strong>Cache hit rate</strong>：prompt cache / embedding cache 命中率、直接影響 cost</li>
</ul>
<h3 id="quality-metrics">Quality metrics</h3>
<ul>
<li><strong>Refusal rate</strong>：模型 refuse 回應的比例</li>
<li><strong>Hallucination rate</strong>：（要 reviewer 標）</li>
<li><strong>User feedback score</strong>：thumb up / down</li>
</ul>
<h3 id="工具metrics--traces--logs-三層">工具：metrics / traces / logs 三層</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Metrics（Prometheus / Datadog / CloudWatch）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    → time-series、aggregate、適合 alerting
</span></span><span class="line"><span class="ln">3</span><span class="cl">Traces（OpenTelemetry / Datadog APM）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    → per-request、可追蹤跨服務 latency
</span></span><span class="line"><span class="ln">5</span><span class="cl">Logs（structured JSON、推 ELK / Loki）
</span></span><span class="line"><span class="ln">6</span><span class="cl">    → 詳細 context、debug 用</span></span></code></pre></div><p>三層各司其職、各層保留專屬職責：metric 看到 p99 漲、用 trace 找哪個 request 哪段慢、用 log 看那 request 的具體 prompt / response。</p>
<h2 id="維度-6reliability--sla">維度 6：Reliability / SLA</h2>
<h3 id="可預期的失敗模式">可預期的失敗模式</h3>
<table>
  <thead>
      <tr>
          <th>失敗類型</th>
          <th>處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Transient GPU OOM</strong></td>
          <td>retry with smaller batch、circuit breaker</td>
      </tr>
      <tr>
          <td><strong>Inference timeout</strong></td>
          <td>切短 max_tokens、拒絕過長 prompt</td>
      </tr>
      <tr>
          <td><strong>Model server crash</strong></td>
          <td>health check + auto-restart（systemd / k8s）</td>
      </tr>
      <tr>
          <td><strong>Vector DB unavailable</strong></td>
          <td>fallback：跳過 RAG、純 chat 答</td>
      </tr>
      <tr>
          <td><strong>Upstream API rate limit</strong></td>
          <td>exponential backoff + jitter</td>
      </tr>
  </tbody>
</table>
<h3 id="graceful-degradation">Graceful degradation</h3>
<p>設計 production LLM 應用、要回答「失敗時降級到什麼」：</p>
<table>
  <thead>
      <tr>
          <th>Component down</th>
          <th>Acceptable degradation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Vector DB</td>
          <td>用 LLM 內知識回答 + 標明「未查最新文件」</td>
      </tr>
      <tr>
          <td>RAG retrieval 但 LLM 仍跑</td>
          <td>用退役 cache 結果 + retry</td>
      </tr>
      <tr>
          <td>Primary LLM API</td>
          <td>fallback 到 secondary（OpenAI ↔ Anthropic ↔ 本地）</td>
      </tr>
      <tr>
          <td>全部 down</td>
          <td>顯示維護頁、回 503 + Retry-After、避免直接 5xx</td>
      </tr>
  </tbody>
</table>
<p>在 SLA 承諾下、每個 fallback 路徑都要事前設計、避免出事時臨時決策（早期 prototype / 內部工具可接受 reactive 處理、production 階段不行）。</p>
<h3 id="capacity-planning">Capacity planning</h3>
<p>簡單公式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Required capacity = peak_concurrent_users × per_user_RAM
</span></span><span class="line"><span class="ln">2</span><span class="cl">                  × overhead_factor (1.3-1.5)
</span></span><span class="line"><span class="ln">3</span><span class="cl">                  × redundancy_factor (2x for HA)</span></span></code></pre></div><p>例：peak 100 並發、每 user ~500 MB KV cache、overhead 1.3、HA 2x → 130 GB GPU memory。一張 H100 不夠、要兩張 A100 80GB 或 H100 + sharding。</p>
<h2 id="跟本地-hands-on-的對照">跟本地 hands-on 的對照</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>本地 hands-on 紀錄</th>
          <th>Production 該量什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Single-user latency</td>
          <td>30-60s for SDXL、5-20s for chat</td>
          <td>p50 / p95 / p99 latency</td>
      </tr>
      <tr>
          <td>Index size</td>
          <td>~3.7 MB / 463 chunks</td>
          <td>sharded index、GB-TB 規模</td>
      </tr>
      <tr>
          <td>Process management</td>
          <td><code>pkill -9</code></td>
          <td>systemd / k8s liveness probe</td>
      </tr>
      <tr>
          <td>Disk cleanup</td>
          <td>手動 <code>ollama rm</code></td>
          <td>自動 retention policy</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>一次性硬體</td>
          <td>$/token / day budget alerts</td>
      </tr>
      <tr>
          <td>Observability</td>
          <td><code>tail log</code></td>
          <td>Prometheus + Grafana / Datadog</td>
      </tr>
      <tr>
          <td>Failure response</td>
          <td>自己重啟</td>
          <td>auto-recover + alert + runbook</td>
      </tr>
  </tbody>
</table>
<p>本地數字是「能跑」的證明、production 數字是「能用」的驗證。本地驗證完 architecture 後、production deployment 該重做 load test、不能 assume 線性 scale。</p>
<h2 id="跨-framework-不變的設計問題">跨 framework 不變的設計問題</h2>
<p>不管你用 vLLM / TGI / Triton / SGLang / OpenAI API、production 設計都要回答：</p>
<ol>
<li><strong>Latency vs throughput</strong>：哪個是主要 metric？</li>
<li><strong>Batch strategy</strong>：static / continuous / per-request？</li>
<li><strong>Cost ceiling</strong>：$/day budget 多少？超過怎麼處理？</li>
<li><strong>Storage</strong>：vector DB 規模？update 頻率？</li>
<li><strong>Observability</strong>：哪些 metric 是 alert worthy？</li>
<li><strong>Reliability</strong>：failure mode + graceful degradation 設計</li>
<li><strong>Capacity</strong>：peak + redundancy 需要多少 GPU memory</li>
</ol>
<p>這 7 個問題回答一致時、framework 選擇通常不是 production 失敗的根因——資源評估跟設計取捨已對齊、framework 多半是配套選項。</p>
<h2 id="何時這篇會過時">何時這篇會過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>6 個維度（concurrency / latency / cost / storage / observability / reliability）</li>
<li>Latency-sensitive vs throughput-sensitive 應用的設計差異</li>
<li>三類計費單位的取捨</li>
<li>Metrics / traces / logs 三層觀測</li>
<li>Graceful degradation 設計</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 inference framework（vLLM / TGI / SGLang 等）的 ranking</li>
<li>雲端 API price tier</li>
<li>哪些 vector DB 主流</li>
</ul>
<p>新 framework 出來時、回到 6 維度 framework 問：它在哪個維度有突破？對既有設計問題的答案有沒有改變？通常會發現核心問題沒變、只是工具更熟。</p>
<h2 id="跟其他章節的關係">跟其他章節的關係</h2>
<ul>
<li><a href="/blog/llm/01-local-llm-services/hands-on/rag-mcp-resources/" data-link-title="Hands-on：RAG / MCP 的資源 footprint" data-link-desc="RAG ingest / query / MCP server 三階段的 RAM / 磁碟 / process 實測、多模型並存的 RAM 衝突、本地 LLM 跑 RAG 跟單純 chat 的差異">hands-on RAG/MCP 資源</a>：本地 baseline 數字、本章的 production extrapolation 起點</li>
<li><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> / <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use</a> / <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent</a>：應用層設計、本章是「應用如何跑」的補完</li>
<li><a href="/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">0.5 硬體記憶體預算</a>：本地單機 perspective、本章對應 multi-machine production</li>
<li><a href="/blog/llm/01-local-llm-services/troubleshooting/" data-link-title="1.7 排錯方法論：用三層架構做故障定位" data-link-desc="故障定位的分層思考、症狀到層級的對應反射、log 在三層的角色差異、最小可重現的縮減策略">1.7 排錯方法論</a>：本地 trouble-shooting、本章是 production observability 的對照</li>
</ul>
]]></content:encoded></item><item><title>4.10 衍生產物管理原理：什麼進 git、什麼不該</title><link>https://tarrragon.github.io/blog/llm/04-applications/artifact-management/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/artifact-management/</guid><description>&lt;p>LLM 應用的 codebase 不只 source code、還含 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding&lt;/a> index、cache、model weights、prompt config、lockfile、log 等各種「衍生」或「外部」產物。每個產物該不該進 git、有沒有共通邏輯？&lt;/p>
&lt;p>本章寫的是「&lt;strong>source / derived / external 三類產物的判讀框架&lt;/strong>」、跟「production deployment 怎麼處理 share + reproducibility 取捨」。對應到 hands-on 系列實際遇到的問題——為什麼 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a> demo 的 &lt;code>index.pkl&lt;/code> 進 &lt;code>.gitignore&lt;/code>、Hugging Face model weights 為什麼不能塞進 repo、prompt template 該怎麼版本管理。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning&lt;/a> 對應「production 怎麼跑」、本章對應「production 怎麼版本控制 + 部署」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>用「source / derived / external」三分類判讀任何產物該不該進 git。&lt;/li>
&lt;li>看到 &lt;code>.gitignore&lt;/code> 設計、能解釋每條規則的邏輯。&lt;/li>
&lt;li>在 reproducibility 跟 repo 大小之間做合理取捨。&lt;/li>
&lt;li>知道 derived / external 產物該用什麼機制 share（registry、build script、artifact storage）。&lt;/li>
&lt;/ol>
&lt;h2 id="三類產物-framework">三類產物 framework&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類別&lt;/th>
 &lt;th>定義&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;th>該進 git？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Source&lt;/strong>&lt;/td>
 &lt;td>人類撰寫、是真理來源&lt;/td>
 &lt;td>code、prompt template、test fixture、config schema&lt;/td>
 &lt;td>必須&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Derived&lt;/strong>&lt;/td>
 &lt;td>從 source 自動產出、可重建&lt;/td>
 &lt;td>binary、index、cache、compiled output、generated docs&lt;/td>
 &lt;td>不該&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>External&lt;/strong>&lt;/td>
 &lt;td>從外部下載、跟 source 解耦&lt;/td>
 &lt;td>model weights、dependency package、dataset&lt;/td>
 &lt;td>用 registry / manifest&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀問題：「&lt;strong>刪掉重來、用什麼能 reconstruct 一模一樣？&lt;/strong>」&lt;/p>
&lt;ul>
&lt;li>用人手寫 → source、必須 commit&lt;/li>
&lt;li>用 build script + source → derived、commit manifest（如 lockfile）不 commit output&lt;/li>
&lt;li>用 download script + URL → external、commit URL 不 commit content&lt;/li>
&lt;/ul>
&lt;p>這個 framework 跨任何技術 stack 都成立（不只 LLM）、但 LLM 應用尤其放大 derived / external 比例。&lt;/p>
&lt;h2 id="llm-應用具體對應">LLM 應用具體對應&lt;/h2>
&lt;h3 id="source進-git">Source（進 git）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>程式 source code&lt;/td>
 &lt;td>wrapper script、framework 整合 code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Prompt template&lt;/td>
 &lt;td>system prompt、few-shot example、prompt structure&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Config schema&lt;/td>
 &lt;td>哪些參數可調、合法範圍、default value&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Test fixture&lt;/td>
 &lt;td>測試輸入 / 預期輸出 pair&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Markdown content（如本 blog）&lt;/td>
 &lt;td>文章本身就是 source&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.gitignore&lt;/code> / lock file 規則&lt;/td>
 &lt;td>描述哪些不進 git 也是 source&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build script&lt;/td>
 &lt;td>&lt;code>ingest.py&lt;/code>、&lt;code>build.sh&lt;/code>、能從 source 重建 derived&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="derived不進-git但-build-path-進-git">Derived（不進 git、但 build path 進 git）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>為什麼不 commit&lt;/th>
 &lt;th>怎麼 share&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>index.pkl&lt;/code>（RAG embedding index）&lt;/td>
 &lt;td>從 corpus + embedding model 重建、跟 model 版本綁、3.7 MB-GB 級&lt;/td>
 &lt;td>&lt;code>ingest.py&lt;/code> script、跑一次就 reconstruct&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Embedding cache（per-document hash）&lt;/td>
 &lt;td>跑時動態建、避免重 embed 同 chunk&lt;/td>
 &lt;td>不 share、各自 rebuild&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python &lt;code>__pycache__/&lt;/code>&lt;/td>
 &lt;td>跑時自動產、Python 版本敏感&lt;/td>
 &lt;td>不 share、各自 rebuild&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Compiled binary（如 &lt;code>bin/mdtools&lt;/code>）&lt;/td>
 &lt;td>從 Go source build、平台敏感&lt;/td>
 &lt;td>source + build instructions、可選 release page 提供&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Generated docs（如 Hugo &lt;code>public/&lt;/code>）&lt;/td>
 &lt;td>從 markdown source build、deploy 時自動生&lt;/td>
 &lt;td>source + deploy pipeline&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Log files&lt;/td>
 &lt;td>runtime output、量大、有 PII 風險&lt;/td>
 &lt;td>不 share、log retention 政策另立&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="external不進-git用-manifest--registry">External（不進 git、用 manifest / registry）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>Manifest / registry&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>LLM model weights&lt;/td>
 &lt;td>Hugging Face / Ollama registry tag&lt;/td>
 &lt;td>&lt;code>nomic-embed-text:latest&lt;/code>、&lt;code>sd_xl_base_1.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python dependency&lt;/td>
 &lt;td>&lt;code>requirements.txt&lt;/code> / &lt;code>pyproject.toml&lt;/code>&lt;/td>
 &lt;td>&lt;code>requests==2.31.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Node modules&lt;/td>
 &lt;td>&lt;code>package.json&lt;/code> + &lt;code>package-lock.json&lt;/code>&lt;/td>
 &lt;td>&lt;code>react@18.2.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dataset&lt;/td>
 &lt;td>&lt;code>data.dvc&lt;/code> / S3 URL + checksum&lt;/td>
 &lt;td>training data、eval set&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Docker image&lt;/td>
 &lt;td>&lt;code>Dockerfile&lt;/code> + image tag&lt;/td>
 &lt;td>&lt;code>python:3.11-slim&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>External 跟 derived 的差別：external 來自 git 外的 source、derived 來自 git 內的 source。&lt;strong>機制上都用同套路徑&lt;/strong>——manifest 進 git、實際 bytes 存 registry、避免大檔直接進 commit history。&lt;/p></description><content:encoded><![CDATA[<p>LLM 應用的 codebase 不只 source code、還含 <a href="/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding</a> index、cache、model weights、prompt config、lockfile、log 等各種「衍生」或「外部」產物。每個產物該不該進 git、有沒有共通邏輯？</p>
<p>本章寫的是「<strong>source / derived / external 三類產物的判讀框架</strong>」、跟「production deployment 怎麼處理 share + reproducibility 取捨」。對應到 hands-on 系列實際遇到的問題——為什麼 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> demo 的 <code>index.pkl</code> 進 <code>.gitignore</code>、Hugging Face model weights 為什麼不能塞進 repo、prompt template 該怎麼版本管理。</p>
<p>跟 <a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning</a> 對應「production 怎麼跑」、本章對應「production 怎麼版本控制 + 部署」。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>用「source / derived / external」三分類判讀任何產物該不該進 git。</li>
<li>看到 <code>.gitignore</code> 設計、能解釋每條規則的邏輯。</li>
<li>在 reproducibility 跟 repo 大小之間做合理取捨。</li>
<li>知道 derived / external 產物該用什麼機制 share（registry、build script、artifact storage）。</li>
</ol>
<h2 id="三類產物-framework">三類產物 framework</h2>
<table>
  <thead>
      <tr>
          <th>類別</th>
          <th>定義</th>
          <th>例子</th>
          <th>該進 git？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Source</strong></td>
          <td>人類撰寫、是真理來源</td>
          <td>code、prompt template、test fixture、config schema</td>
          <td>必須</td>
      </tr>
      <tr>
          <td><strong>Derived</strong></td>
          <td>從 source 自動產出、可重建</td>
          <td>binary、index、cache、compiled output、generated docs</td>
          <td>不該</td>
      </tr>
      <tr>
          <td><strong>External</strong></td>
          <td>從外部下載、跟 source 解耦</td>
          <td>model weights、dependency package、dataset</td>
          <td>用 registry / manifest</td>
      </tr>
  </tbody>
</table>
<p>判讀問題：「<strong>刪掉重來、用什麼能 reconstruct 一模一樣？</strong>」</p>
<ul>
<li>用人手寫 → source、必須 commit</li>
<li>用 build script + source → derived、commit manifest（如 lockfile）不 commit output</li>
<li>用 download script + URL → external、commit URL 不 commit content</li>
</ul>
<p>這個 framework 跨任何技術 stack 都成立（不只 LLM）、但 LLM 應用尤其放大 derived / external 比例。</p>
<h2 id="llm-應用具體對應">LLM 應用具體對應</h2>
<h3 id="source進-git">Source（進 git）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式 source code</td>
          <td>wrapper script、framework 整合 code</td>
      </tr>
      <tr>
          <td>Prompt template</td>
          <td>system prompt、few-shot example、prompt structure</td>
      </tr>
      <tr>
          <td>Config schema</td>
          <td>哪些參數可調、合法範圍、default value</td>
      </tr>
      <tr>
          <td>Test fixture</td>
          <td>測試輸入 / 預期輸出 pair</td>
      </tr>
      <tr>
          <td>Markdown content（如本 blog）</td>
          <td>文章本身就是 source</td>
      </tr>
      <tr>
          <td><code>.gitignore</code> / lock file 規則</td>
          <td>描述哪些不進 git 也是 source</td>
      </tr>
      <tr>
          <td>Build script</td>
          <td><code>ingest.py</code>、<code>build.sh</code>、能從 source 重建 derived</td>
      </tr>
  </tbody>
</table>
<h3 id="derived不進-git但-build-path-進-git">Derived（不進 git、但 build path 進 git）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>為什麼不 commit</th>
          <th>怎麼 share</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>index.pkl</code>（RAG embedding index）</td>
          <td>從 corpus + embedding model 重建、跟 model 版本綁、3.7 MB-GB 級</td>
          <td><code>ingest.py</code> script、跑一次就 reconstruct</td>
      </tr>
      <tr>
          <td>Embedding cache（per-document hash）</td>
          <td>跑時動態建、避免重 embed 同 chunk</td>
          <td>不 share、各自 rebuild</td>
      </tr>
      <tr>
          <td>Python <code>__pycache__/</code></td>
          <td>跑時自動產、Python 版本敏感</td>
          <td>不 share、各自 rebuild</td>
      </tr>
      <tr>
          <td>Compiled binary（如 <code>bin/mdtools</code>）</td>
          <td>從 Go source build、平台敏感</td>
          <td>source + build instructions、可選 release page 提供</td>
      </tr>
      <tr>
          <td>Generated docs（如 Hugo <code>public/</code>）</td>
          <td>從 markdown source build、deploy 時自動生</td>
          <td>source + deploy pipeline</td>
      </tr>
      <tr>
          <td>Log files</td>
          <td>runtime output、量大、有 PII 風險</td>
          <td>不 share、log retention 政策另立</td>
      </tr>
  </tbody>
</table>
<h3 id="external不進-git用-manifest--registry">External（不進 git、用 manifest / registry）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>Manifest / registry</th>
          <th>例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LLM model weights</td>
          <td>Hugging Face / Ollama registry tag</td>
          <td><code>nomic-embed-text:latest</code>、<code>sd_xl_base_1.0</code></td>
      </tr>
      <tr>
          <td>Python dependency</td>
          <td><code>requirements.txt</code> / <code>pyproject.toml</code></td>
          <td><code>requests==2.31.0</code></td>
      </tr>
      <tr>
          <td>Node modules</td>
          <td><code>package.json</code> + <code>package-lock.json</code></td>
          <td><code>react@18.2.0</code></td>
      </tr>
      <tr>
          <td>Dataset</td>
          <td><code>data.dvc</code> / S3 URL + checksum</td>
          <td>training data、eval set</td>
      </tr>
      <tr>
          <td>Docker image</td>
          <td><code>Dockerfile</code> + image tag</td>
          <td><code>python:3.11-slim</code></td>
      </tr>
  </tbody>
</table>
<p>External 跟 derived 的差別：external 來自 git 外的 source、derived 來自 git 內的 source。<strong>機制上都用同套路徑</strong>——manifest 進 git、實際 bytes 存 registry、避免大檔直接進 commit history。</p>
<h2 id="為什麼-derived--external-不該進-git">為什麼 derived / external 不該進 git</h2>
<p>每條限制有具體技術理由：</p>
<h3 id="size">Size</h3>
<p>Git 設計給 source code（小、純文字、頻繁 diff）。Derived / external 通常大、binary、不適合：</p>
<ul>
<li>Git 對 large binary 沒有有效 delta 演算法、每次小改 → 完整 copy 進 history</li>
<li>Repo size 線性漲、clone 變慢、CI cache 爆炸</li>
<li>GitHub 等服務有 file size 上限（GitHub 100 MB / file）</li>
</ul>
<p>實例：<code>scripts/rag-demo/index.pkl</code> 3.7 MB、每次 corpus 改 → 重 ingest → 整檔變。Commit 100 次 = git history 多 370 MB。Clone 痛。</p>
<h3 id="reproducibility反直覺">Reproducibility（反直覺）</h3>
<p>直覺：「commit derived 保證每個 clone 都拿到一樣的 output」——錯。</p>
<p>實際：</p>
<ul>
<li>Derived 跟 build env 綁（Python 3.13 build 的 pickle 在 3.14 不一定能 load）</li>
<li>Embedding index 跟 model version 綁（pull 不同 model 結果不同）</li>
<li>用舊 commit 的 derived 跑在新 env 反而比 rebuild 更脆弱</li>
</ul>
<p>正確 reproducibility 機制：commit <strong>build instruction + lockfile</strong>、別人 rebuild 時用同樣輸入產同樣 output。</p>
<h3 id="update-frequency-mismatch">Update frequency mismatch</h3>
<p>Source 改慢、derived 改快。<code>content/</code> 加一句話、<code>index.pkl</code> 整個重建。如果都進 git：</p>
<ul>
<li>90% 的 commit 是「rebuild artifact」、語意上不是真正的「source change」</li>
<li>git log 看不出真正 source 改動</li>
<li>diff review 被 derived noise 淹沒</li>
</ul>
<h3 id="cost--performance">Cost / Performance</h3>
<p>CI / CD pipeline 通常自動 rebuild derived。不 commit 反而：</p>
<ul>
<li>Source-only PR 較易 review（沒 generated diff）</li>
<li>CI build cache 重用、不需從 git 拉 derived</li>
<li>Deploy artifact registry 跟 git 分離、各自 scale</li>
</ul>
<h2 id="llm-應用-gitignore-設計模式">LLM 應用 <code>.gitignore</code> 設計模式</h2>
<p>LLM 應用典型 <code>.gitignore</code> 結構：</p>





<pre tabindex="0"><code class="language-gitignore" data-lang="gitignore"># === Source-side build output (derived) ===
# Compiled binaries
bin/
dist/
build/
*.pyc
__pycache__/

# Hugo / static site generators
public/
.hugo_build.lock
resources/

# RAG / vector indexes (regenerable)
scripts/rag-demo/index.pkl
*.pkl
*.index

# Embedding caches
.embedding_cache/
.vector_cache/

# === External-bound (don&#39;t commit, use manifest) ===
# Python deps (commit requirements.txt instead)
.venv/
venv/
env/

# Node deps
node_modules/

# Model weights / large files
*.safetensors
*.gguf
*.onnx
*.bin

# Datasets
data/raw/
data/processed/

# === Runtime / Local ===
# Logs
*.log
logs/

# OS / IDE
.DS_Store
.vscode/
.idea/

# Local secrets / API keys
.env
.env.local
*.key

# Temp / cache
*.tmp
.cache/</code></pre><h3 id="邊界-case-思考">邊界 case 思考</h3>
<p>幾個容易誤判的：</p>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>該不該 commit</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>package-lock.json</code> / <code>poetry.lock</code></td>
          <td>commit</td>
          <td>是 manifest、保證 reproducibility</td>
      </tr>
      <tr>
          <td><code>node_modules/</code></td>
          <td>不 commit</td>
          <td>是 derived、可從 lockfile 重建</td>
      </tr>
      <tr>
          <td>小型 fixture data（&lt; 1 MB）</td>
          <td>commit（作 source）</td>
          <td>是 test 的一部分、不 reconstruct</td>
      </tr>
      <tr>
          <td>大型 eval dataset（&gt; 100 MB）</td>
          <td>用 dvc / S3 manifest</td>
          <td>量大、改用 dvc / S3 manifest 管理</td>
      </tr>
      <tr>
          <td>Pre-built model 用於 demo</td>
          <td>用 release artifact / Hugging Face</td>
          <td>量大、版本要可追蹤</td>
      </tr>
      <tr>
          <td>Prompt template (markdown / yaml)</td>
          <td>commit</td>
          <td>是 source、影響行為、要 diff</td>
      </tr>
      <tr>
          <td>從 LLM 生的 sample output</td>
          <td>不 commit（除非當 fixture）</td>
          <td>是 demo artifact、不 reconstruct 來源</td>
      </tr>
  </tbody>
</table>
<p>判讀 heuristic：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">這個檔案、半年後 production deploy 時要不要存在？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├─ 要：source 或 manifest 進 git
</span></span><span class="line"><span class="ln">3</span><span class="cl">└─ 不要：runtime / 開發環境 only、用 .gitignore</span></span></code></pre></div><h3 id="三分類的退化情境">三分類的退化情境</h3>
<p>三分類是 default framework、實務上有幾類「該不該 commit 的判讀走兩條岔路」的情境、需要特別判讀：</p>
<ul>
<li><strong>Generated client SDK in monorepo</strong>：protobuf / OpenAPI spec 產出的 client code 屬於 derived（從 .proto / .yaml 生）、但 monorepo 場景常 commit 進去、目的是「跨語言版本對齊 + CI 不用每次重生」。判讀：若 .proto / spec 改動頻率低 + 跨語言一致性比 build 速度重要、commit；變動頻繁就回到 derived 路徑。</li>
<li><strong>Jupyter notebook 的 output cell</strong>：技術上是 derived（執行 notebook 產出）、但語意上常被視為 source 的一部分（教學、demo、結果展示）。判讀：教學 / 展示 / 帶 figures 的 notebook 通常 commit 含 output；機械化的 batch run / CI notebook 走 derived、用 nbstripout 清掉 output 再 commit。</li>
<li><strong>Git LFS / git-annex 介於 commit 跟 manifest 之間</strong>：把大檔案 commit 進 git 但實際 bytes 存 LFS server、worktree 看起來像直接 commit、metadata 卻是 manifest pointer。判讀：適合「需要在 git history 中追蹤大檔案版本、但不想讓 repo 體積爆炸」的場景（如 game asset、訓練資料集 snapshot）。介於 commit 跟 dvc / S3 manifest 之間的折衷選項。</li>
<li><strong>Lockfile vs build artifact 的灰色帶</strong>：<code>yarn-error.log</code> 算 log（不 commit）還是 derived 但對 debug 重要（commit）？實務上多數選 .gitignore、但若團隊在 CI 失敗時要 reproduce 環境、保留少量 build log 也合理。</li>
</ul>
<p>判讀原則：三分類給 default、灰色帶用「reproducibility + 變動頻率 + 團隊協作需求」三軸決定具體路徑。</p>
<h2 id="source--derived--external-的-share-機制">Source / Derived / External 的 share 機制</h2>
<p>不 commit 不代表不 share、只是用對的 channel。</p>
<h3 id="source-share--git">Source share = git</h3>
<p>直接 clone 即可。</p>
<h3 id="derived-share-三種模式">Derived share 三種模式</h3>
<ol>
<li><strong>Build script in repo</strong>：別人 clone 後跑 script 重建（本 blog 用這條：<code>ingest.py</code> 重建 index）
<ul>
<li>優點：無外部依賴、self-contained</li>
<li>缺點：每個 clone 都要重跑、累積 compute time</li>
</ul>
</li>
<li><strong>Release artifact</strong>：把 build output 上傳 GitHub Releases / S3、clone 後下載
<ul>
<li>優點：clone 快、不用各自 rebuild</li>
<li>缺點：要 maintain release pipeline、artifact 版本管理另立</li>
</ul>
</li>
<li><strong>Artifact registry</strong>：用 OCI registry、Docker registry、artifact storage（如 GitHub Packages / JFrog Artifactory）
<ul>
<li>優點：production-grade、跨 team / 跨 org share</li>
<li>缺點：複雜、配 auth、cost</li>
</ul>
</li>
</ol>
<p>選擇：小專案用 script、中型用 release、大型 / 多人 collaboration 用 registry。</p>
<h3 id="external-share--manifest">External share = manifest</h3>
<p>把「<strong>從哪下載 + checksum</strong>」commit 進 git、實際 content 不進。常見 manifest format：</p>
<table>
  <thead>
      <tr>
          <th>Manifest</th>
          <th>描述</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>requirements.txt</code> / <code>pyproject.toml</code></td>
          <td>Python deps + version</td>
      </tr>
      <tr>
          <td><code>package.json</code> + <code>package-lock.json</code></td>
          <td>Node deps + exact version + integrity hash</td>
      </tr>
      <tr>
          <td><code>Dockerfile</code></td>
          <td>OS + 環境 + 依賴 + entrypoint</td>
      </tr>
      <tr>
          <td><code>dvc.yaml</code> + <code>dvc.lock</code></td>
          <td>dataset + model version</td>
      </tr>
      <tr>
          <td>Ollama Modelfile（如果寫了）</td>
          <td>LLM model + system prompt 組合</td>
      </tr>
      <tr>
          <td><code>Cargo.lock</code> / <code>go.sum</code></td>
          <td>Rust / Go 的 dep checksum</td>
      </tr>
  </tbody>
</table>
<p>Manifest 自己是 source（人寫、進 git）、它指向的 external content 不進 git（用 download script 取回）。</p>
<h2 id="prompt-跟-config-的版本控制">Prompt 跟 config 的版本控制</h2>
<p>LLM 應用特有的問題：<strong>prompt template 是 source、但 prompt 改變影響行為跟 derived 改變不同</strong>。</p>
<table>
  <thead>
      <tr>
          <th>Prompt 操作</th>
          <th>git 行為</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改一個字</td>
          <td>一個 commit</td>
          <td>模型行為可能大變、要重跑 eval</td>
      </tr>
      <tr>
          <td>加 few-shot example</td>
          <td>一個 commit</td>
          <td>同上</td>
      </tr>
      <tr>
          <td>換不同模型（在 config）</td>
          <td>config commit</td>
          <td>用 prompt 沒變、行為變</td>
      </tr>
  </tbody>
</table>
<p>Prompt + model 是一對組合、行為相依、改一個都要重 test。建議在 commit message / PR description 描述「這個 prompt 改動的 expected behavior change」、用規格層級的 review 對待、勿視為 trivial 小改。</p>
<h3 id="prompt-跟-evaluation-一起管理">Prompt 跟 evaluation 一起管理</h3>
<p>進階做法：每個 prompt 配 evaluation set、commit 在同 PR：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">prompts/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── code_review.md           ← prompt template
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── code_review_eval.json    ← input + expected output pair
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── code_review_history.md   ← 改動記錄 + 對應 eval score</span></span></code></pre></div><p>每次改 prompt、跑 eval、比較 score、進 commit message。這比「改完 push 看看效果」可控很多、是 prompt engineering 的基本姿勢。</p>
<h2 id="production-deployment-的對接">Production deployment 的對接</h2>
<p>本地 hands-on 跟 production 對應：</p>
<table>
  <thead>
      <tr>
          <th>本地 hands-on</th>
          <th>Production</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>python ingest.py</code> build index</td>
          <td>Build pipeline 跑同樣 script、output 進 artifact storage</td>
      </tr>
      <tr>
          <td><code>ollama pull nomic-embed-text</code></td>
          <td>Container image 預載 model 或 mount volume</td>
      </tr>
      <tr>
          <td><code>.gitignore</code> 排除 index.pkl</td>
          <td>CI 自動 rebuild、deploy 時讀 artifact storage</td>
      </tr>
      <tr>
          <td>Source code 進 git</td>
          <td>Source 觸發 CI、build &amp; deploy</td>
      </tr>
  </tbody>
</table>
<p>成熟的 LLM 應用部署 pipeline：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Source change → git push
</span></span><span class="line"><span class="ln">2</span><span class="cl">              → CI triggered
</span></span><span class="line"><span class="ln">3</span><span class="cl">              → Build derived artifacts (index, container image)
</span></span><span class="line"><span class="ln">4</span><span class="cl">              → Run evaluation suite (prompt + model behavior tests)
</span></span><span class="line"><span class="ln">5</span><span class="cl">              → Push artifacts to registry
</span></span><span class="line"><span class="ln">6</span><span class="cl">              → Deploy with manifest pointing to specific artifact version
</span></span><span class="line"><span class="ln">7</span><span class="cl">              → Smoke test against production data
</span></span><span class="line"><span class="ln">8</span><span class="cl">              → Auto-rollback if metrics regress</span></span></code></pre></div><p>每一步都要 commit-able 的 manifest。在可審計 / 多人協作 / 有 SLA 承諾的場景、「手動 build 完 ssh 進 prod scp」這種 ad-hoc 流程會破壞 reproducibility、出問題時無法 revert 到具體 build；早期 prototype / 單人專案 / 一次性 demo 可接受 ad-hoc 流程、進入 production 前再改成 manifest-based。Manifest 是 reproducibility 跟 audit 的基礎。</p>
<h2 id="何時這篇會過時">何時這篇會過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Source / derived / external 三分類 framework</li>
<li>「commit manifest、不 commit content」核心原則</li>
<li><code>.gitignore</code> 通用模式</li>
<li>Reproducibility 來自 build instruction、不來自 commit derived</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 manifest format（半年一個新 lockfile 格式）</li>
<li>Artifact registry 主流（OCI / Conda / npm 等都會演化）</li>
<li>LLM model registry（Hugging Face / Ollama 都會演化）</li>
</ul>
<p>新 lock 格式 / registry 出來時、回到三分類問：它解的是哪類產物？我能用它 commit manifest 不 commit content 嗎？通常答案 yes。</p>
<h2 id="跟其他章節的關係">跟其他章節的關係</h2>
<ul>
<li><a href="https://github.com/tarrragon/blog/blob/main/scripts/README.md">scripts/README.md</a>：本章原理的實作 reference</li>
<li><a href="/blog/llm/01-local-llm-services/hands-on/quickstart/" data-link-title="Hands-on Quickstart：clone repo 後跑通所有 demo" data-link-desc="4 步驟跑通 RAG / MCP / permission demo 的 setup 跟驗證指令、整合 hands-on 系列所有章節的 prerequisite">Hands-on quickstart</a>：跑通 demo 步驟、為什麼要 rebuild <code>index.pkl</code></li>
<li><a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning</a>：production runtime 視角、本章是 deployment 視角</li>
<li><a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流原理</a>：什麼可以離開機器、本章是「什麼可以進 git」的 sibling</li>
<li><a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a>：本章把 embedding index 判為 derived（不進 git、<code>ingest.py</code> 重建）、該章接手 vector index 存進 backend 之後的生命週期管理</li>
</ul>
]]></content:encoded></item><item><title>4.11 Long context engineering</title><link>https://tarrragon.github.io/blog/llm/04-applications/long-context-engineering/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/long-context-engineering/</guid><description>&lt;p>長 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window&lt;/a> 模型（128K、1M、甚至更長）在 2024-2026 變成主流標配。但「聲稱 context」跟「實用 effective context」之間有顯著落差、不理解這條鴻溝會讓 long context 變成資源浪費而非能力延伸。本章把 long context 的實際運作、典型失敗模式、prompt 設計策略、跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a> 的取捨拆成可操作的判讀。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>區分模型「聲稱 context」、「NIH context」、「實用 effective context」三個層級。&lt;/li>
&lt;li>看到 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle&lt;/a> 症狀時、知道怎麼緩解。&lt;/li>
&lt;li>對自己工作流的任務、判斷該用 long context 還是 RAG。&lt;/li>
&lt;li>設計 prompt 時、把關鍵資訊放對位置。&lt;/li>
&lt;li>評估「升級到更長 context 模型」的實際邊際收益。&lt;/li>
&lt;/ol>
&lt;h2 id="三層-context-概念claimed--nih--effective">三層 context 概念：claimed / NIH / effective&lt;/h2>
&lt;p>讀 model card 看到「128K context」「1M context」時、需要區分：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層級&lt;/th>
 &lt;th>定義&lt;/th>
 &lt;th>典型數字（128K 模型）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Claimed context&lt;/td>
 &lt;td>模型架構支援的上限（RoPE scaling 配置）&lt;/td>
 &lt;td>128K&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>NIH context&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/needle-in-haystack/" data-link-title="Needle in a Haystack" data-link-desc="把一個事實藏在 long context 不同位置、測試 LLM 能否抓出來的 benchmark 方法">Needle-in-haystack&lt;/a> 通過的長度（抓單一事實）&lt;/td>
 &lt;td>80K-128K&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Effective context&lt;/td>
 &lt;td>真實任務（reasoning over context）品質可接受的長度&lt;/td>
 &lt;td>8K-32K&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>落差來自：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>RoPE scaling 是延伸、不是「免費擴展」&lt;/strong>：訓練多在 8K-32K range、用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rope/" data-link-title="RoPE（Rotary Position Embedding）" data-link-desc="用旋轉矩陣把位置資訊直接旋轉進 Q/K 向量、現代 LLM 主流的位置編碼方式">RoPE&lt;/a> scaling 推到 128K+、實用上會 degrade&lt;/li>
&lt;li>&lt;strong>訓練資料偏短&lt;/strong>：trillion-token pretrain corpus 中、極長文件相對稀少、模型對 long context 中段不熟悉&lt;/li>
&lt;li>&lt;strong>Attention 衰減&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">attention&lt;/a> 機制對長距離 token 的注意能力隨距離下降、雖未真正 attention to 0、但「有效訊號」減弱&lt;/li>
&lt;/ol>
&lt;p>實務啟示：聲稱 1M context 不代表「能塞 1M 進 prompt 解任務」、實用 effective context 多半是聲稱的 1/4-1/8。&lt;/p>
&lt;h2 id="lost-in-the-middlelong-context-的主要失敗模式">Lost-in-the-middle：long context 的主要失敗模式&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">Lost-in-the-middle&lt;/a>（Liu et al., 2023）的核心發現：模型對 long context 中段內容的 recall 顯著低於開頭與結尾。實測：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Recall accuracy vs 答案位置（10K context）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 位置 0%（開頭） ：85%+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> 位置 25% ：70%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> 位置 50%（中段）：40-55%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 位置 75% ：65%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> 位置 100%（結尾）：80%+&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>成因細節見 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle 卡片&lt;/a>。本章聚焦緩解：&lt;/p></description><content:encoded><![CDATA[<p>長 <a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window</a> 模型（128K、1M、甚至更長）在 2024-2026 變成主流標配。但「聲稱 context」跟「實用 effective context」之間有顯著落差、不理解這條鴻溝會讓 long context 變成資源浪費而非能力延伸。本章把 long context 的實際運作、典型失敗模式、prompt 設計策略、跟 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> 的取捨拆成可操作的判讀。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>區分模型「聲稱 context」、「NIH context」、「實用 effective context」三個層級。</li>
<li>看到 <a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle</a> 症狀時、知道怎麼緩解。</li>
<li>對自己工作流的任務、判斷該用 long context 還是 RAG。</li>
<li>設計 prompt 時、把關鍵資訊放對位置。</li>
<li>評估「升級到更長 context 模型」的實際邊際收益。</li>
</ol>
<h2 id="三層-context-概念claimed--nih--effective">三層 context 概念：claimed / NIH / effective</h2>
<p>讀 model card 看到「128K context」「1M context」時、需要區分：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>定義</th>
          <th>典型數字（128K 模型）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claimed context</td>
          <td>模型架構支援的上限（RoPE scaling 配置）</td>
          <td>128K</td>
      </tr>
      <tr>
          <td>NIH context</td>
          <td><a href="/blog/llm/knowledge-cards/needle-in-haystack/" data-link-title="Needle in a Haystack" data-link-desc="把一個事實藏在 long context 不同位置、測試 LLM 能否抓出來的 benchmark 方法">Needle-in-haystack</a> 通過的長度（抓單一事實）</td>
          <td>80K-128K</td>
      </tr>
      <tr>
          <td>Effective context</td>
          <td>真實任務（reasoning over context）品質可接受的長度</td>
          <td>8K-32K</td>
      </tr>
  </tbody>
</table>
<p>落差來自：</p>
<ol>
<li><strong>RoPE scaling 是延伸、不是「免費擴展」</strong>：訓練多在 8K-32K range、用 <a href="/blog/llm/knowledge-cards/rope/" data-link-title="RoPE（Rotary Position Embedding）" data-link-desc="用旋轉矩陣把位置資訊直接旋轉進 Q/K 向量、現代 LLM 主流的位置編碼方式">RoPE</a> scaling 推到 128K+、實用上會 degrade</li>
<li><strong>訓練資料偏短</strong>：trillion-token pretrain corpus 中、極長文件相對稀少、模型對 long context 中段不熟悉</li>
<li><strong>Attention 衰減</strong>：<a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">attention</a> 機制對長距離 token 的注意能力隨距離下降、雖未真正 attention to 0、但「有效訊號」減弱</li>
</ol>
<p>實務啟示：聲稱 1M context 不代表「能塞 1M 進 prompt 解任務」、實用 effective context 多半是聲稱的 1/4-1/8。</p>
<h2 id="lost-in-the-middlelong-context-的主要失敗模式">Lost-in-the-middle：long context 的主要失敗模式</h2>
<p><a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">Lost-in-the-middle</a>（Liu et al., 2023）的核心發現：模型對 long context 中段內容的 recall 顯著低於開頭與結尾。實測：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Recall accuracy vs 答案位置（10K context）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  位置 0%（開頭）  ：85%+
</span></span><span class="line"><span class="ln">3</span><span class="cl">  位置 25%        ：70%
</span></span><span class="line"><span class="ln">4</span><span class="cl">  位置 50%（中段）：40-55%
</span></span><span class="line"><span class="ln">5</span><span class="cl">  位置 75%        ：65%
</span></span><span class="line"><span class="ln">6</span><span class="cl">  位置 100%（結尾）：80%+</span></span></code></pre></div><p>成因細節見 <a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle 卡片</a>。本章聚焦緩解：</p>
<ol>
<li><strong>關鍵資訊放開頭 / 結尾</strong>：system prompt、最新指示放在 prompt 開頭 / 最末段、剛好是 attention 最強的兩處</li>
<li><strong>重要內容重複出現</strong>：在 prompt 開頭跟結尾各放一次摘要、提高 recall</li>
<li><strong>避免在中段藏 deeply nested constraint</strong>：「請遵守附件中第 47 條規則」這類引用、長 context 中段容易被忽略</li>
<li><strong>拆 prompt 成多輪</strong>：把 long context 拆成「load context」+「query」兩輪、第二輪 query 在前一輪結尾、recall 較強</li>
</ol>
<h2 id="long-context-vs-rag什麼時候該選哪個">Long context vs RAG：什麼時候該選哪個</h2>
<p>兩者解的問題重疊但<strong>不完全替代</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Long context</th>
          <th><a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>知識量上限</td>
          <td>Context window（128K-1M token）</td>
          <td>無上限（向量資料庫可存任意大）</td>
      </tr>
      <tr>
          <td>知識動態更新</td>
          <td>每次 query 把 context 全塞進去、可變</td>
          <td>Retrieval 階段可隨時更新</td>
      </tr>
      <tr>
          <td>知識來源 traceable</td>
          <td>整段塞、無明確「答案來自哪一段」</td>
          <td>每個 chunk 有 source、可 cite</td>
      </tr>
      <tr>
          <td>Prompt 成本</td>
          <td>每次 query 都付 full context token 成本</td>
          <td>只付 retrieved chunks 的 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a></td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>知識集中、&lt; context window、需要整體理解</td>
          <td>知識量大、零散、明確 retrieval key</td>
      </tr>
      <tr>
          <td>失敗模式</td>
          <td>Lost-in-the-middle、context degradation</td>
          <td>Retrieval miss、chunk 邊界切壞</td>
      </tr>
  </tbody>
</table>
<p>判讀流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">知識總量 &lt; 你模型的 effective context（見後文表格、典型 7B-14B 約 8-16K、30B+ 約 16-32K）？
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├─ 是 → 直接 long context
</span></span><span class="line"><span class="ln">3</span><span class="cl">  └─ 否 → 知識結構化、retrieval key 明確？
</span></span><span class="line"><span class="ln">4</span><span class="cl">            ├─ 是 → RAG
</span></span><span class="line"><span class="ln">5</span><span class="cl">            └─ 否 → 嘗試 hybrid：RAG 把相關段 retrieve 出來 + 放進 long context</span></span></code></pre></div><p>注意「effective context」是你模型實際能 reliable 處理的範圍、不是 model card 上聲稱的 128K — 拿 7B 模型塞 16K 知識仍可能踩 lost-in-the-middle。</p>
<p>混用情境：</p>
<ol>
<li><strong>Codebase 理解</strong>：codebase 整體用 RAG retrieve、單檔 deep dive 用 long context（讀整個檔案）</li>
<li><strong>文件問答</strong>：文件用 RAG retrieve 相關段、塞進 32K context、模型可看到「retrieve 結果 + 自己的對話歷史」</li>
<li><strong>長對話</strong>：對話歷史進 long context、新指令在最末段（避免 lost-in-the-middle）</li>
</ol>
<h2 id="context-設計策略">Context 設計策略</h2>
<p>具體 prompt 結構建議（適用 long context 場景）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[1. System prompt 開頭]         ← attention 強、放核心指令
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  你的角色 / 主要任務 / 不變的約束
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">[2. Few-shot examples（若需）]   ← attention 仍強、放示範
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">[3. 大段 context]                ← 中段、可能 lost-in-the-middle
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  - 把最重要的內容也放這段開頭跟結尾、別只放中間
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - 若有多段 context、各段都帶明確 heading
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">[4. 當前查詢]                    ← attention 強、放使用者問題
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">[5. 重述關鍵約束（若需）]         ← 末段、attention 強、再次強調 critical rule</span></span></code></pre></div><p>典型反例（容易踩 lost-in-the-middle）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[1. 重要約束「使用者付費等級 = premium、回應應該詳細」]
</span></span><span class="line"><span class="ln">2</span><span class="cl">[2. 100K 文件全文]
</span></span><span class="line"><span class="ln">3</span><span class="cl">[3. 「請回答上述文件相關問題」]</span></span></code></pre></div><p>→ 改成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[1. 重要約束（同上）]
</span></span><span class="line"><span class="ln">2</span><span class="cl">[2. 文件摘要 + 「以下是完整文件、若需細節請參考」]
</span></span><span class="line"><span class="ln">3</span><span class="cl">[3. 100K 文件全文]
</span></span><span class="line"><span class="ln">4</span><span class="cl">[4. 重述「使用者付費等級 = premium、提供詳細答案」]
</span></span><span class="line"><span class="ln">5</span><span class="cl">[5. 「使用者問題：X」]</span></span></code></pre></div><p>第二版有兩處可靠出現核心指令、長 context 中段含有完整文件、但模型 recall instruction 時兩處任選一處都行、品質提升。</p>
<h2 id="reasoning-model--long-context-的特殊互動">Reasoning model + long context 的特殊互動</h2>
<p><a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models</a> 的 reasoning trace 跟 long context 有兩個衝突點：</p>
<ol>
<li><strong>Reasoning trace 擠 context budget</strong>：1000-10000 token reasoning trace 直接吃進 context、本來 effective 32K 的模型可能只剩 22K 給輸入</li>
<li><strong>Long thinking traces 自己也踩 lost-in-the-middle</strong>：reasoning trace 變長時、reasoning 過程中段也會「忘記前面想到的」</li>
</ol>
<p>緩解：</p>
<ol>
<li><strong>Reasoning model 配長 context 模型</strong>：DeepSeek-R1 distill 64K context 是合理 baseline</li>
<li><strong>Reasoning 階段引導模型「定期重述目標」</strong>：prompt 加「請每隔幾步重新確認任務目標」</li>
<li><strong>複雜任務拆步</strong>：別把整個任務丟給 reasoning model 一輪解、拆成多個 sub-task</li>
</ol>
<h2 id="量測自己模型的-effective-context">量測自己模型的 effective context</h2>
<p>不要相信 model card 上的數字、自己跑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 跑 needle-in-haystack（lower bound、寬鬆指標）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 用 ggerganov/llama.cpp 或 RULER 工具</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 看模型在 8K / 16K / 32K / 64K / 128K 各自的 NIH accuracy</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 2. 自己工作流的 real-task 評估</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 拿實際的長 prompt（如完整 codebase + 任務）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 對不同 context 長度比較輸出品質、找到 degradation 點</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 3. lost-in-the-middle 測試</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 同個 prompt 把關鍵指令分別放在開頭、中段、結尾</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 對比模型回答準確度</span></span></span></code></pre></div><p>實務上、寫 code 場景的 effective context 通常落在：</p>
<table>
  <thead>
      <tr>
          <th>模型大小</th>
          <th>聲稱 context</th>
          <th>實用 effective context（寫 code）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>7B-14B（如 Qwen3-Coder-14B）</td>
          <td>32K-128K</td>
          <td>8K-16K</td>
      </tr>
      <tr>
          <td>30B-32B（如 Qwen3-Coder-30B）</td>
          <td>64K-128K</td>
          <td>16K-32K</td>
      </tr>
      <tr>
          <td>雲端旗艦（Claude / GPT-5）</td>
          <td>200K-1M</td>
          <td>64K-200K</td>
      </tr>
  </tbody>
</table>
<h2 id="升級到更長-context-模型的判讀">升級到更長 context 模型的判讀</h2>
<p>讀 model card 看到「context 從 128K 提升到 1M」、判斷對自己的價值：</p>
<ol>
<li><strong>看 RULER benchmark、不只看 NIH</strong>：RULER 有 multi-needle、aggregation、reasoning 等任務、更貼近實用</li>
<li><strong>看 effective context（如 LongBench 數字）</strong>：聲稱 1M 但 effective 64K vs 聲稱 200K 但 effective 100K — 後者更有用</li>
<li><strong>看自己任務真實長度</strong>：如果你的任務 prompt 多在 8K 內、聲稱 128K → 1M 對你無收益</li>
<li><strong>看推論成本</strong>：long context 的 <a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> 跟 prefill 時間都隨長度增加、effective 64K 模型實用上比聲稱 1M 模型更快</li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Claimed / NIH / Effective context 三層概念</li>
<li>Lost-in-the-middle 的存在跟基本緩解策略</li>
<li>Long context vs RAG 的判讀框架</li>
<li>「關鍵資訊放開頭結尾」的 prompt 設計原則</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>各模型的聲稱 / effective context 數字（每代會推進）</li>
<li>Long context 訓練技術（RoPE scaling 變體、long-context fine-tuning 方法會演化）</li>
<li>Lost-in-the-middle 的減緩進展（可能透過新訓練方法部分解決）</li>
<li>Benchmark 工具（NIH → RULER → 未來新 benchmark）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 Embedding model 內部</a>、看 RAG retrieval 階段背後的 embedding 是怎麼運作。</p>
]]></content:encoded></item><item><title>4.12 Embedding model 內部：訓練、選型、in-domain fine-tune</title><link>https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG&lt;/a> 章節定義了 retrieval + augmentation 的二段式結構、但 retrieval 階段背後的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding model&lt;/a> 怎麼運作、怎麼選、什麼時候該換、什麼時候該自己 fine-tune、這些決策直接影響 RAG 品質。本章把 embedding model 的訓練機制、評估方法、實務選型展開。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 embedding model 跟 base LLM 的訓練差異。&lt;/li>
&lt;li>看到 MTEB / BEIR 分數時、知道對自己場景的意義。&lt;/li>
&lt;li>對自己 domain 選對 embedding model（通用 vs code vs multilingual）。&lt;/li>
&lt;li>判斷「需要 fine-tune 自己的 embedding model」的時機跟方法。&lt;/li>
&lt;/ol>
&lt;h2 id="embedding-model-vs-llm-的訓練差異">Embedding model vs LLM 的訓練差異&lt;/h2>
&lt;p>兩者底層架構可能類似（都用 Transformer）、但訓練 objective 完全不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>LLM（如 Llama / Gemma instruct）&lt;/th>
 &lt;th>Embedding model（如 bge-large、jina-v3）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>訓練 objective&lt;/td>
 &lt;td>Next-token prediction + RLHF&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/contrastive-learning/" data-link-title="Contrastive Learning" data-link-desc="用「相關 vs 不相關」成對 / 三元組樣本訓練 embedding 的方法、現代 embedding model 的核心訓練 paradigm">Contrastive learning&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>輸出形式&lt;/td>
 &lt;td>一連串 token&lt;/td>
 &lt;td>一個固定維度的向量（如 768、1024）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>訓練資料&lt;/td>
 &lt;td>Trillion-token 通用文字&lt;/td>
 &lt;td>億級的 (query, doc) 正向對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用法&lt;/td>
 &lt;td>Prompt → response&lt;/td>
 &lt;td>Text → vector&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pretrained 起點&lt;/td>
 &lt;td>從 scratch 或繼承 base&lt;/td>
 &lt;td>通常從 base LLM 抽 hidden state 開始&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵理解：&lt;strong>不能拿任意 LLM 的最後 hidden state 當 embedding&lt;/strong> — LLM hidden state 是為「預測下一個 token」優化、不為「相似度比較」優化。要再經過 contrastive learning fine-tune 才能當 embedding model 用。&lt;/p>
&lt;p>Embedding model 的典型訓練 pipeline：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">Stage 1: 從 base model 開始（如 BERT、RoBERTa、Mistral、Llama）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">Stage 2: Contrastive pre-training
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> 用大量 weak supervised pair（如 Reddit title-body、StackExchange QA）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> InfoNCE loss、batch size 大、hard negative mining
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">Stage 3: Supervised fine-tune
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> 用標註好的 (query, relevant_doc) pair
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> 來源如 MSMARCO、Natural Questions
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">Stage 4（可選）: Task-specific instruction tuning
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> 讓模型懂「task description」、可針對不同 retrieval 任務切換
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> 代表：bge-large、e5-mistral-7b-instruct&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Stage 4 的「instruction-tuned embedding」是 2024 後流行的設計：query 前加「Represent this sentence for retrieving relevant passages:」這類前綴、embedding model 學會依任務調整向量。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG</a> 章節定義了 retrieval + augmentation 的二段式結構、但 retrieval 階段背後的 <a href="/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding model</a> 怎麼運作、怎麼選、什麼時候該換、什麼時候該自己 fine-tune、這些決策直接影響 RAG 品質。本章把 embedding model 的訓練機制、評估方法、實務選型展開。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 embedding model 跟 base LLM 的訓練差異。</li>
<li>看到 MTEB / BEIR 分數時、知道對自己場景的意義。</li>
<li>對自己 domain 選對 embedding model（通用 vs code vs multilingual）。</li>
<li>判斷「需要 fine-tune 自己的 embedding model」的時機跟方法。</li>
</ol>
<h2 id="embedding-model-vs-llm-的訓練差異">Embedding model vs LLM 的訓練差異</h2>
<p>兩者底層架構可能類似（都用 Transformer）、但訓練 objective 完全不同：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>LLM（如 Llama / Gemma instruct）</th>
          <th>Embedding model（如 bge-large、jina-v3）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>訓練 objective</td>
          <td>Next-token prediction + RLHF</td>
          <td><a href="/blog/llm/knowledge-cards/contrastive-learning/" data-link-title="Contrastive Learning" data-link-desc="用「相關 vs 不相關」成對 / 三元組樣本訓練 embedding 的方法、現代 embedding model 的核心訓練 paradigm">Contrastive learning</a></td>
      </tr>
      <tr>
          <td>輸出形式</td>
          <td>一連串 token</td>
          <td>一個固定維度的向量（如 768、1024）</td>
      </tr>
      <tr>
          <td>訓練資料</td>
          <td>Trillion-token 通用文字</td>
          <td>億級的 (query, doc) 正向對</td>
      </tr>
      <tr>
          <td>用法</td>
          <td>Prompt → response</td>
          <td>Text → vector</td>
      </tr>
      <tr>
          <td>Pretrained 起點</td>
          <td>從 scratch 或繼承 base</td>
          <td>通常從 base LLM 抽 hidden state 開始</td>
      </tr>
  </tbody>
</table>
<p>關鍵理解：<strong>不能拿任意 LLM 的最後 hidden state 當 embedding</strong> — LLM hidden state 是為「預測下一個 token」優化、不為「相似度比較」優化。要再經過 contrastive learning fine-tune 才能當 embedding model 用。</p>
<p>Embedding model 的典型訓練 pipeline：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Stage 1: 從 base model 開始（如 BERT、RoBERTa、Mistral、Llama）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Stage 2: Contrastive pre-training
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   用大量 weak supervised pair（如 Reddit title-body、StackExchange QA）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   InfoNCE loss、batch size 大、hard negative mining
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Stage 3: Supervised fine-tune
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   用標註好的 (query, relevant_doc) pair
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   來源如 MSMARCO、Natural Questions
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">Stage 4（可選）: Task-specific instruction tuning
</span></span><span class="line"><span class="ln">12</span><span class="cl">   讓模型懂「task description」、可針對不同 retrieval 任務切換
</span></span><span class="line"><span class="ln">13</span><span class="cl">   代表：bge-large、e5-mistral-7b-instruct</span></span></code></pre></div><p>Stage 4 的「instruction-tuned embedding」是 2024 後流行的設計：query 前加「Represent this sentence for retrieving relevant passages:」這類前綴、embedding model 學會依任務調整向量。</p>
<h2 id="選型維度">選型維度</h2>
<p>主流 embedding model 的選型維度：</p>
<h3 id="1-domain-相符">1. Domain 相符</h3>
<table>
  <thead>
      <tr>
          <th>Domain</th>
          <th>推薦模型</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>通用英文</td>
          <td>bge-large-en-v1.5、mxbai-embed-large-v1</td>
          <td>通用 corpus、MTEB Retrieval 高分</td>
      </tr>
      <tr>
          <td>通用多語</td>
          <td>jina-embeddings-v3、bge-m3、multilingual-e5</td>
          <td>多語 pretrain、中日韓阿等支援</td>
      </tr>
      <tr>
          <td>Code（讀 / 寫 code）</td>
          <td>jina-embeddings-v2-base-code、voyage-code-3</td>
          <td>code corpus 訓練、語意（函式名、註解）+ syntax 結合</td>
      </tr>
      <tr>
          <td>中文</td>
          <td>bge-large-zh、Conan-embedding</td>
          <td>中文 corpus 為主</td>
      </tr>
      <tr>
          <td>跨語言（中英混合）</td>
          <td>jina-embeddings-v3、multilingual-e5</td>
          <td>跨語言對齊訓練、中英 query 找對方語言 doc</td>
      </tr>
  </tbody>
</table>
<h3 id="2-大小模型大小--向量維度">2. 大小（模型大小 / 向量維度）</h3>
<table>
  <thead>
      <tr>
          <th>Tier</th>
          <th>模型大小</th>
          <th>向量維度</th>
          <th>Latency / 記憶體</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>小（&lt; 200M）</td>
          <td>nomic-embed (137M)、all-MiniLM (23M)</td>
          <td>384-768</td>
          <td>快、本機 CPU 可跑</td>
          <td>本地 RAG、簡單 retrieval</td>
      </tr>
      <tr>
          <td>中（200-500M）</td>
          <td>bge-large (335M)、mxbai-embed-large</td>
          <td>1024</td>
          <td>中、需要 GPU 或 fast CPU</td>
          <td>主力 RAG、品質敏感場景</td>
      </tr>
      <tr>
          <td>大（500M-7B）</td>
          <td>e5-mistral-7b、Linq-Embed-Mistral</td>
          <td>4096</td>
          <td>慢、需要 GPU</td>
          <td>高品質、雲端、Reranking 場景</td>
      </tr>
      <tr>
          <td>雲端 API</td>
          <td>OpenAI text-embedding-3、voyage-3</td>
          <td>1024-3072</td>
          <td>網路 latency + API 成本</td>
          <td>雲端 RAG、高 QPS</td>
      </tr>
  </tbody>
</table>
<h3 id="3-context-window-上限">3. Context window 上限</h3>
<p>不同 embedding model 對單次 embed 的 token 上限不同：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>Context limit</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>早期 sentence-transformers</td>
          <td>256-512 tokens</td>
      </tr>
      <tr>
          <td>bge-large / mxbai-embed</td>
          <td>512 tokens</td>
      </tr>
      <tr>
          <td>nomic-embed-text-v1.5</td>
          <td>8192 tokens</td>
      </tr>
      <tr>
          <td>jina-embeddings-v3</td>
          <td>8192 tokens</td>
      </tr>
      <tr>
          <td>voyage-3</td>
          <td>32K tokens</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>事實查核註</strong>：本節所列具體型號（bge-large-en-v1.5、jina-embeddings-v3、nomic-embed-text-v1.5、voyage-3 等）、向量維度、context limit、訓練資料 domain、MTEB / BEIR 排名 — 都是 2026/5 主流版本的估計、各模型升級節奏快、引用前以 <a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB Leaderboard</a> 跟對應 model card 當前狀態為準。</p></blockquote>
<p>選擇影響 chunking 策略（見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> 的 chunking 段）：短 context embedding 要切細、長 context embedding 可保留更完整段落、但內部 attention 對長段中段仍可能 lost-in-the-middle。</p>
<h3 id="4-cosine-similarity-設計">4. Cosine similarity 設計</h3>
<p>部分 embedding model 訓練時就 L2-normalized、用 cosine = dot product；部分沒 normalize、要自己處理：</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>Normalize 預設</th>
          <th>推薦 distance metric</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>bge-large、mxbai-embed</td>
          <td>已 L2-normalize</td>
          <td>Dot product（高效、結果同 cosine）</td>
      </tr>
      <tr>
          <td>nomic-embed-text</td>
          <td>已 L2-normalize</td>
          <td>Dot product</td>
      </tr>
      <tr>
          <td>OpenAI ada-002 / 3</td>
          <td>已 L2-normalize</td>
          <td>Dot product</td>
      </tr>
      <tr>
          <td>自訓練 / 早期模型</td>
          <td>未 normalize</td>
          <td>Cosine similarity</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>詳細見 <a href="/blog/llm/knowledge-cards/vector-norm/" data-link-title="Vector Norm" data-link-desc="衡量向量大小的純量值、L1 / L2 / L∞ 各有用途、cosine similarity 的基礎">vector-norm</a> 跟 <a href="/blog/llm/knowledge-cards/dot-product/" data-link-title="Dot Product" data-link-desc="兩個向量對應位置相乘再加總、attention score 跟相似度判讀的基礎">dot-product</a> 卡片。</p></blockquote>
<h2 id="評估mteb-跟自己-domain-的對齊">評估：MTEB 跟自己 domain 的對齊</h2>
<p><a href="/blog/llm/knowledge-cards/mteb-benchmark/" data-link-title="MTEB" data-link-desc="Massive Text Embedding Benchmark：8 大類 56 任務、評估 embedding model 跨任務通用能力的標準">MTEB</a> 是現在挑選 embedding model 最常用的 leaderboard、但要正確讀：</p>
<ol>
<li><strong>看 Retrieval 子分數、不是 Overall</strong>：MTEB 含 8 大類、跟 RAG 最直接相關的是 Retrieval 跟 Reranking</li>
<li><strong>跟自己 domain 對齊</strong>：MTEB 通用 corpus、自己 domain 可能跟 MTEB 落差大</li>
<li><strong>In-domain benchmark 才是 final test</strong>：用自己工作流的真實 query 跟 expected doc、自建小型評估集（如 100-200 對）、看候選 embedding model 的 hit rate / nDCG</li>
</ol>
<p>In-domain 評估的最小可行流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 偽代碼</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="mf">1.</span> <span class="n">蒐集</span> <span class="mi">50</span><span class="o">-</span><span class="mi">100</span> <span class="n">個</span> <span class="n">query</span> <span class="o">+</span> <span class="n">expected_doc</span><span class="err">（</span><span class="n">已知答案的對</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="mf">2.</span> <span class="n">對</span> <span class="n">candidate</span> <span class="n">embedding</span> <span class="n">models</span> <span class="n">各跑</span><span class="err">：</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">   <span class="o">-</span> <span class="n">embed</span> <span class="n">所有</span> <span class="n">doc</span><span class="err">（</span><span class="n">含</span> <span class="n">expected</span> <span class="n">跟</span> <span class="n">distractor</span><span class="err">、</span><span class="o">~</span><span class="mi">1000</span> <span class="n">個</span> <span class="n">distractor</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">   <span class="o">-</span> <span class="n">embed</span> <span class="n">每個</span> <span class="n">query</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">   <span class="o">-</span> <span class="n">算</span> <span class="n">query</span><span class="o">-</span><span class="n">doc</span> <span class="n">similarity</span><span class="err">、</span><span class="n">看</span> <span class="n">expected</span> <span class="n">是否在</span> <span class="n">top</span><span class="o">-</span><span class="mi">5</span> <span class="o">/</span> <span class="n">top</span><span class="o">-</span><span class="mi">10</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="mf">3.</span> <span class="n">比較</span> <span class="n">candidate</span> <span class="n">的</span> <span class="n">hit_rate</span><span class="o">@</span><span class="mi">5</span> <span class="o">/</span> <span class="n">hit_rate</span><span class="o">@</span><span class="mi">10</span></span></span></code></pre></div><p>跑完這個再決定用哪個 embedding model、比看 MTEB leaderboard 可靠很多。</p>
<h2 id="實務選型的-constraint-優先序">實務選型的 constraint 優先序</h2>
<p>上面四個維度（domain / 大小 / context / cosine 設計）跟 MTEB 評估是「品質軸」— 哪個 embedding model 最能解你的 retrieval 問題。但實際選型時，品質軸之前通常有一組<strong>工程 constraint 先砍掉大量選項</strong>，剩下的候選才進品質比較。</p>
<p>常見的工程 constraint 依砍選項力度排序：</p>
<ol>
<li><strong>Runtime 可用性</strong>：推論伺服器支援哪些模型？Ollama 目前原生支援 <code>nomic-embed-text</code>、<code>mxbai-embed-large</code>、<code>snowflake-arctic-embed</code> 等，但不支援所有 Hugging Face 模型。用 cloud API（OpenAI / Cohere / Voyage）則受 vendor 綁定跟成本約束。這一條通常砍掉最多選項。</li>
<li><strong>體積 / 記憶體預算</strong>：個人機器常駐 embedding model 跟 chat model 共用記憶體。137M 的 <code>nomic-embed-text</code> 跟 7B 的 <code>e5-mistral</code> 在記憶體佔用上差一個數量級。</li>
<li><strong>已有驗證基線</strong>：團隊或前期 demo 已用某個模型跑過、retrieval 品質已確認可用。換模型要重建 index + 重新驗證，成本不只是 MTEB 分數比較。</li>
<li><strong>向量維度的 storage 成本</strong>：維度影響 index 大小（n × d × 4 bytes）跟 brute-force search 延遲。768 維 vs 1024 維在小規模無感，但 100K+ chunks 時差異開始有意義。詳見 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a>。</li>
</ol>
<p>實務流程是：先用 constraint 1-3 收窄到 2-3 個候選，再跑 in-domain benchmark（上段的 hit rate 流程）做最終決定。直接從 MTEB leaderboard 挑最高分的模型、到實際場景才發現 runtime 不支援或體積太大，是常見的繞路。</p>
<h2 id="何時該-fine-tune-自己的-embedding-model">何時該 fine-tune 自己的 embedding model</h2>
<p>通常<strong>不該</strong> fine-tune embedding model — 用現成的 bge-large、jina-v3 已經很好。但下列情境值得評估：</p>
<ol>
<li>
<p><strong>Domain 跟通用 corpus 差距大</strong>：</p>
<ul>
<li>醫療 / 法律 / 金融的專業術語、通用 embedding model 對「同義詞」「同概念不同表述」recall 差</li>
<li>In-domain term frequency 跟通用 corpus 差距大（如「IRA」在金融 vs 政治語境）</li>
</ul>
</li>
<li>
<p><strong>In-domain benchmark hit rate 顯著低於通用 benchmark</strong>：</p>
<ul>
<li>用 MTEB 高分模型、in-domain hit rate@5 仍 &lt; 60%</li>
<li>換多個候選 embedding model、所有都類似低分</li>
</ul>
</li>
<li>
<p><strong>有足夠 in-domain (query, doc) 對</strong>：</p>
<ul>
<li>Fine-tune 需要至少數千對、最好 1-10 萬對</li>
<li>對少於 1000 對的場景、fine-tune 收益通常低於數據增強 / 提升 retrieval pipeline</li>
</ul>
</li>
</ol>
<p>Fine-tune 流程（詳細）：</p>
<h3 id="step-1蒐集-in-domain-training-data">Step 1：蒐集 in-domain training data</h3>
<p>三種主流形態：</p>
<table>
  <thead>
      <tr>
          <th>Format</th>
          <th>結構</th>
          <th>蒐集難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Positive pair</td>
          <td>(query, relevant_doc)</td>
          <td>容易（從 click log、QA pair）</td>
      </tr>
      <tr>
          <td>Triplet</td>
          <td>(anchor, positive, negative)</td>
          <td>中（要明確 negative）</td>
      </tr>
      <tr>
          <td>Score / label</td>
          <td>(query, doc, relevance_score)</td>
          <td>難（要人工標）</td>
      </tr>
  </tbody>
</table>
<p>實務多從 positive pair 開始（InfoNCE loss 在 batch 內自動取其他樣本當 negative）、品質提升再進 triplet（hard negative mining）。</p>
<h3 id="step-2選-base-model">Step 2：選 base model</h3>
<p>選擇看資料量跟硬體：</p>
<table>
  <thead>
      <tr>
          <th>起始 base model</th>
          <th>適合資料量</th>
          <th>適合硬體</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>sentence-transformers MiniLM</td>
          <td>1K - 50K 對</td>
          <td>一般 CPU / 小 GPU</td>
      </tr>
      <tr>
          <td>BGE-base / bge-small</td>
          <td>10K - 100K 對</td>
          <td>16GB+ GPU</td>
      </tr>
      <tr>
          <td>BGE-large / jina-v3 / mxbai</td>
          <td>50K+ 對</td>
          <td>24GB+ GPU</td>
      </tr>
      <tr>
          <td>E5-Mistral-7B-instruct</td>
          <td>100K+ 對</td>
          <td>多卡 / A100</td>
      </tr>
  </tbody>
</table>
<p>選擇原則：base model 在 generic benchmark 越強、fine-tune 後上限越高、但訓練成本越高。</p>
<h3 id="step-3loss-選擇">Step 3：Loss 選擇</h3>
<table>
  <thead>
      <tr>
          <th>Loss</th>
          <th>機制</th>
          <th>適合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MultipleNegativesRankingLoss</td>
          <td>InfoNCE 變體、batch 內其他樣本當 negative</td>
          <td>Positive pair only、大 batch</td>
      </tr>
      <tr>
          <td>Triplet loss</td>
          <td>直接比 (anchor, positive, negative) 距離</td>
          <td>有明確 triplet、傳統選擇</td>
      </tr>
      <tr>
          <td>Cosine similarity loss</td>
          <td>預測相似度標籤</td>
          <td>Score / label data</td>
      </tr>
      <tr>
          <td>Contrastive tension loss</td>
          <td>對比學習變體、效果好</td>
          <td>大規模 fine-tune</td>
      </tr>
  </tbody>
</table>
<p>實務 default：MultipleNegativesRankingLoss + batch size 64-128（越大 negatives 越多、品質越高）。</p>
<h3 id="step-4hard-negative-mining">Step 4：Hard negative mining</h3>
<p>純隨機 negative（batch 內其他樣本）容易、但 hard negative（看似相關但實際無關）才能 push 模型品質：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 用初版 fine-tuned model 對每個 query 跑 retrieve top-50
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 對每個 query 的 top-50：
</span></span><span class="line"><span class="ln">3</span><span class="cl">   - 真正 relevant doc（known positive）→ skip
</span></span><span class="line"><span class="ln">4</span><span class="cl">   - 其他 → 候選 hard negative
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. 篩 hard negatives（LLM-as-judge 或人工確認真的「看似相關但不對」）
</span></span><span class="line"><span class="ln">6</span><span class="cl">4. 用 (query, positive, hard_negative) 重訓
</span></span><span class="line"><span class="ln">7</span><span class="cl">5. Iterate 2-3 輪</span></span></code></pre></div><p>Hard negative 是 embedding fine-tune 品質的關鍵差距 — 沒做的 fine-tune 通常 plateau 早、做了的可超越通用 model。</p>
<h3 id="step-5lora-fine-tune-而非-full-fine-tune">Step 5：LoRA fine-tune 而非 full fine-tune</h3>
<p>跟 LLM fine-tune 一樣、embedding model fine-tune 也用 <a href="/blog/llm/knowledge-cards/lora/" data-link-title="LoRA" data-link-desc="Low-Rank Adaptation：凍住原模型權重、只訓兩個小矩陣的 parameter-efficient fine-tuning">LoRA</a>：</p>
<table>
  <thead>
      <tr>
          <th>方式</th>
          <th>訓練成本</th>
          <th>通用能力保留</th>
          <th>推論方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Full fine-tune</td>
          <td>高</td>
          <td>易 <a href="/blog/llm/knowledge-cards/catastrophic-forgetting/" data-link-title="Catastrophic Forgetting" data-link-desc="Fine-tune 模型時、新訓練資料覆蓋掉原本學到的能力的現象、LoRA / 資料 mixing 是主要緩解">catastrophic forgetting</a></td>
          <td>部署新權重</td>
      </tr>
      <tr>
          <td>LoRA fine-tune</td>
          <td>低</td>
          <td>保留好</td>
          <td>載入 base + adapter</td>
      </tr>
  </tbody>
</table>
<p>主流 framework：sentence-transformers + PEFT、Hugging Face Transformers + LoRA library。</p>
<h3 id="step-6evaluate">Step 6：Evaluate</h3>
<p>不只看 training loss、要實測：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. Build in-domain test set（held-out、跟 training 完全分開）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 算 [hit_rate@K](/llm/knowledge-cards/retrieval-recall/)（query 的 expected doc 是否在 top-K retrieval result）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 跟「base model 未 fine-tune」對比：
</span></span><span class="line"><span class="ln">4</span><span class="cl">   - Fine-tune 後 hit_rate@5 提升 ≥ 10 percentage point → 成功
</span></span><span class="line"><span class="ln">5</span><span class="cl">   - 提升 &lt; 5pp → fine-tune 沒效益、不如優化 retrieval pipeline
</span></span><span class="line"><span class="ln">6</span><span class="cl">4. 確認沒崩通用能力：在 MTEB 跑、看主流 retrieval 任務沒大降</span></span></code></pre></div><h3 id="失敗模式">失敗模式</h3>
<table>
  <thead>
      <tr>
          <th>失敗</th>
          <th>緩解</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資料太少（&lt; 1000 對）、模型沒學到</td>
          <td>數據增強（用 LLM 生 synthetic pair）、改用 prompt + RAG</td>
      </tr>
      <tr>
          <td>訓練 loss 降但 hit_rate 沒升</td>
          <td>Hard negative 不夠、要重 mine</td>
      </tr>
      <tr>
          <td>In-domain 提升但通用能力崩</td>
          <td>加 mixed dataset（80% domain + 20% MTEB）</td>
      </tr>
      <tr>
          <td>Embedding dim 不能改</td>
          <td>Base model 已固定 dim、自己訓 from scratch 才能改</td>
      </tr>
      <tr>
          <td>部署時跟 base model 衝突</td>
          <td>LoRA adapter merge 進 base 後部署、或同時 serve 兩版</td>
      </tr>
  </tbody>
</table>
<h2 id="跟-llm-的整合retrieval-pipeline">跟 LLM 的整合：retrieval pipeline</h2>
<p>完整 RAG pipeline 裡 embedding model 的位置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[Ingestion 階段（離線）]
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  Documents
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    ↓ chunking
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  Chunks
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    ↓ embedding model
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  Chunk vectors → 存進 vector DB
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">[Query 階段（線上）]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  User query
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ↓ embedding model
</span></span><span class="line"><span class="ln">11</span><span class="cl">  Query vector
</span></span><span class="line"><span class="ln">12</span><span class="cl">    ↓ vector DB ANN search
</span></span><span class="line"><span class="ln">13</span><span class="cl">  Top-K chunks
</span></span><span class="line"><span class="ln">14</span><span class="cl">    ↓ (optional) reranking
</span></span><span class="line"><span class="ln">15</span><span class="cl">  Top-N chunks
</span></span><span class="line"><span class="ln">16</span><span class="cl">    ↓ augment LLM prompt
</span></span><span class="line"><span class="ln">17</span><span class="cl">  LLM response</span></span></code></pre></div><p>關鍵設計決策：</p>
<ol>
<li><strong>Embedding model 一致性</strong>：ingestion 跟 query 必須用同個 model（換 model = 整批 re-embed）；chunk vectors 存進 vector DB 之後的 index 結構、維度成本與生命週期見 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a></li>
<li><strong>Chunking 策略對齊 embedding context</strong>：見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG chunking</a></li>
<li><strong>Reranking model 通常用 cross-encoder</strong>：embedding model 是 bi-encoder（query 跟 doc 分開 embed）、reranker 是 cross-encoder（query + doc 一起算）、品質更高但慢、適合在 top-50 → top-5 之間做 reranking</li>
<li><strong>Hybrid retrieval</strong>：BM25（字面）+ embedding（語意）混用、用 RRF（Reciprocal Rank Fusion）合併、是 production 常見配置</li>
</ol>
<h2 id="本地-vs-雲端-embedding">本地 vs 雲端 embedding</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>本地（如 nomic-embed）</th>
          <th>雲端（如 OpenAI text-embedding-3）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>隱私</td>
          <td>完全本地、no exfil</td>
          <td>API 送 doc、依政策 log</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>一次硬體 + 電費</td>
          <td>按 token 計費、長期可累積</td>
      </tr>
      <tr>
          <td>品質</td>
          <td>bge-large / jina-v3 已接近雲端旗艦</td>
          <td>略高（旗艦如 voyage-3 仍領先）</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>視硬體、本地 SSD 快</td>
          <td>網路 latency</td>
      </tr>
      <tr>
          <td>多語 / domain</td>
          <td>開源選擇多、可挑 domain-specific</td>
          <td>API 是通用、不一定最佳 domain match</td>
      </tr>
  </tbody>
</table>
<p>寫 code 場景的判讀：</p>
<ul>
<li><strong>codebase 內部 RAG（NDA / 機密 code）</strong>：本地 embedding 必選</li>
<li><strong>個人開源專案 RAG</strong>：本地 embedding 是合理 default、簡單、free</li>
<li><strong>公司內部 RAG（需高品質、量大）</strong>：評估 voyage-3 / OpenAI v3 vs 本地 bge-large</li>
<li><strong>產品級 production RAG</strong>：通常雲端 API + 自己 fine-tune 的 embedding（最佳品質）</li>
</ul>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Contrastive learning 是 embedding model 的核心訓練 paradigm</li>
<li>MTEB 作為通用 embedding 評估的角色</li>
<li>「跟自己 domain 對齊」的 in-domain benchmark 必要性</li>
<li>Bi-encoder vs cross-encoder 的分工（retrieval vs reranking）</li>
<li>Hybrid retrieval（BM25 + embedding）的設計</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 embedding model（bge → bge-v2 → &hellip;、jina-v3 → v4 → &hellip;）</li>
<li>MTEB leaderboard 排名（每月變）</li>
<li>Instruction-tuned embedding 的 prompt format（標準化中）</li>
<li>Embedding model 的 context window 上限（推升中）</li>
<li>Long-context embedding 的研究（如 ColBERT-style late interaction）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>沒 backend 的靜態場景（個人 blog / docs site）做 embedding 搜尋的 deployment 選擇見 <a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16 靜態 / serverless RAG deployment</a>。</p>
<p>下一章：<a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系</a>、看 eval 三軸八象限 meta 框架（先選軸再選工具）、再進 <a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 Benchmarking 與評估方法論</a> 看具體 benchmark 設計。</p>
]]></content:encoded></item><item><title>4.13 Eval 設計座標系：三軸、八象限、何時測什麼</title><link>https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/</guid><description>&lt;p>LLM 應用的「怎麼測」問題大家都在問、但答案常常是「跑某個 benchmark」「找個 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM judge&lt;/a>」這類&lt;strong>工具層&lt;/strong>回答。實務上工具是末端、設計重點是&lt;strong>先選測什麼軸、再選工具&lt;/strong>。軸選錯了、再好的工具也測不出有用訊號——用 subjective 工具測 objective 行為（例如用 LLM judge 看金額計算對不對）、或用 end-to-end 工具測 component bug（例如看 user satisfaction 但其實是 retrieval pipeline 在漏 chunk）、都是常見的軸誤選。&lt;/p>
&lt;p>本章寫 eval 設計的座標系：三個 binary 軸、八個象限、每個象限對應什麼工具、軸選錯的訊號怎麼識別。這層 framing 是 meta、不是具體 eval 方法——具體方法在 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge&lt;/a>。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>把任何 eval 需求放到三軸座標、定位象限。&lt;/li>
&lt;li>對每個象限選對應的 eval 工具。&lt;/li>
&lt;li>識別軸誤選的訊號、避免「工具對、軸錯」的常見坑。&lt;/li>
&lt;li>規劃 eval 路線：初期該做哪幾個象限、規模化後再補哪些。&lt;/li>
&lt;li>把 eval 設計跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 tracing&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge&lt;/a> 串成完整 pipeline。&lt;/li>
&lt;/ol>
&lt;h2 id="三軸">三軸&lt;/h2>
&lt;p>Eval 設計的三個正交軸：&lt;/p>
&lt;h3 id="軸-1objective--subjective">軸 1：Objective ↔ Subjective&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Objective&lt;/strong>：有明確 ground truth、檢驗可以寫成 deterministic check（金額對不對、SQL 跑得通不通、JSON schema 合不合法）。&lt;/li>
&lt;li>&lt;strong>Subjective&lt;/strong>：沒有單一正確答案、需要評分或比較（語氣好不好、解釋清楚不清楚、推薦的 trip 合不合用戶）。&lt;/li>
&lt;/ul>
&lt;p>判讀訊號：「能不能用 Python 函數判定對錯」、能 → objective、不能 → subjective。&lt;/p>
&lt;h3 id="軸-2component--end-to-end">軸 2：Component ↔ End-to-End&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Component&lt;/strong>：測單一元件、孤立評估（retrieval 拿對 chunk 沒、tool call 參數對沒、prompt 抽出正確 entity 沒）。&lt;/li>
&lt;li>&lt;strong>End-to-End&lt;/strong>：測完整流程、user 視角結果（user 問題有沒有被解決、訂單有沒有完成、conversation 滿意度）。&lt;/li>
&lt;/ul>
&lt;p>判讀訊號：「失敗時你想知道是哪一段壞掉」→ component；「你只在乎最終體驗」→ end-to-end。&lt;/p>
&lt;h3 id="軸-3quantitative--qualitative">軸 3：Quantitative ↔ Qualitative&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Quantitative&lt;/strong>：產出數字（accuracy / latency / cost / pass rate）、可以追蹤、可以比較、可以 alert。&lt;/li>
&lt;li>&lt;strong>Qualitative&lt;/strong>：產出觀察（error pattern、user 抱怨、reviewer 註記）、無法直接 aggregate、但能引導 hypothesis。&lt;/li>
&lt;/ul>
&lt;p>判讀訊號：「結果能算平均嗎」→ quantitative；「結果是讀完才知道」→ qualitative。&lt;/p></description><content:encoded><![CDATA[<p>LLM 應用的「怎麼測」問題大家都在問、但答案常常是「跑某個 benchmark」「找個 <a href="/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM judge</a>」這類<strong>工具層</strong>回答。實務上工具是末端、設計重點是<strong>先選測什麼軸、再選工具</strong>。軸選錯了、再好的工具也測不出有用訊號——用 subjective 工具測 objective 行為（例如用 LLM judge 看金額計算對不對）、或用 end-to-end 工具測 component bug（例如看 user satisfaction 但其實是 retrieval pipeline 在漏 chunk）、都是常見的軸誤選。</p>
<p>本章寫 eval 設計的座標系：三個 binary 軸、八個象限、每個象限對應什麼工具、軸選錯的訊號怎麼識別。這層 framing 是 meta、不是具體 eval 方法——具體方法在 <a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking</a> 跟 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a>。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>把任何 eval 需求放到三軸座標、定位象限。</li>
<li>對每個象限選對應的 eval 工具。</li>
<li>識別軸誤選的訊號、避免「工具對、軸錯」的常見坑。</li>
<li>規劃 eval 路線：初期該做哪幾個象限、規模化後再補哪些。</li>
<li>把 eval 設計跟 <a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking</a> / <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 tracing</a> / <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a> 串成完整 pipeline。</li>
</ol>
<h2 id="三軸">三軸</h2>
<p>Eval 設計的三個正交軸：</p>
<h3 id="軸-1objective--subjective">軸 1：Objective ↔ Subjective</h3>
<ul>
<li><strong>Objective</strong>：有明確 ground truth、檢驗可以寫成 deterministic check（金額對不對、SQL 跑得通不通、JSON schema 合不合法）。</li>
<li><strong>Subjective</strong>：沒有單一正確答案、需要評分或比較（語氣好不好、解釋清楚不清楚、推薦的 trip 合不合用戶）。</li>
</ul>
<p>判讀訊號：「能不能用 Python 函數判定對錯」、能 → objective、不能 → subjective。</p>
<h3 id="軸-2component--end-to-end">軸 2：Component ↔ End-to-End</h3>
<ul>
<li><strong>Component</strong>：測單一元件、孤立評估（retrieval 拿對 chunk 沒、tool call 參數對沒、prompt 抽出正確 entity 沒）。</li>
<li><strong>End-to-End</strong>：測完整流程、user 視角結果（user 問題有沒有被解決、訂單有沒有完成、conversation 滿意度）。</li>
</ul>
<p>判讀訊號：「失敗時你想知道是哪一段壞掉」→ component；「你只在乎最終體驗」→ end-to-end。</p>
<h3 id="軸-3quantitative--qualitative">軸 3：Quantitative ↔ Qualitative</h3>
<ul>
<li><strong>Quantitative</strong>：產出數字（accuracy / latency / cost / pass rate）、可以追蹤、可以比較、可以 alert。</li>
<li><strong>Qualitative</strong>：產出觀察（error pattern、user 抱怨、reviewer 註記）、無法直接 aggregate、但能引導 hypothesis。</li>
</ul>
<p>判讀訊號：「結果能算平均嗎」→ quantitative；「結果是讀完才知道」→ qualitative。</p>
<h3 id="三軸的正交性">三軸的正交性</h3>
<p>這三軸是正交的、不是同義詞：</p>
<ul>
<li>「Objective + component + quantitative」典型是 unit test（function 返回對不對）。</li>
<li>「Subjective + end-to-end + qualitative」典型是 user 訪談（user 整體滿意度）。</li>
<li>中間象限存在多種混合、各有對應工具。</li>
</ul>
<h2 id="八象限">八象限</h2>
<p>3 個 binary 軸 = 8 象限。每個象限的常見對應工具：</p>
<table>
  <thead>
      <tr>
          <th>象限</th>
          <th>典型問題</th>
          <th>對應工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Objective + Component + Quantitative</td>
          <td>這個函數 / tool / RAG 元件對嗎</td>
          <td>Unit test、deterministic check、<a href="/blog/llm/knowledge-cards/retrieval-recall/" data-link-title="Retrieval Recall" data-link-desc="衡量 RAG 檢索是否把應該命中的文件或 chunk 放進 top-k 結果，是 component-level eval 的核心指標">retrieval recall@k</a></td>
      </tr>
      <tr>
          <td>Objective + Component + Qualitative</td>
          <td>這個元件失敗 pattern 是什麼</td>
          <td>Error log 分析、trace inspection</td>
      </tr>
      <tr>
          <td>Objective + End-to-end + Quantitative</td>
          <td>整套系統的 success rate / latency</td>
          <td>E2E test、success metric、latency p95</td>
      </tr>
      <tr>
          <td>Objective + End-to-end + Qualitative</td>
          <td>整套系統的 catastrophic 失敗 case 是什麼</td>
          <td>Production incident review、抽樣 trace 讀</td>
      </tr>
      <tr>
          <td>Subjective + Component + Quantitative</td>
          <td>這個 step 的輸出評分</td>
          <td>LLM-as-judge pairwise / rubric、human rating</td>
      </tr>
      <tr>
          <td>Subjective + Component + Qualitative</td>
          <td>這個 step 的 output 哪裡讓人不舒服</td>
          <td>Human review、error analysis with comments</td>
      </tr>
      <tr>
          <td>Subjective + End-to-end + Quantitative</td>
          <td>User 整體 NPS / 滿意度評分</td>
          <td>CSAT、thumbs up/down、appeal rate</td>
      </tr>
      <tr>
          <td>Subjective + End-to-end + Qualitative</td>
          <td>User 想要的是什麼、現在哪裡沒滿足</td>
          <td>User 訪談、開放問卷、social listening</td>
      </tr>
  </tbody>
</table>
<p>不是「八個都要做」、是「先看你的問題在哪個象限、用對應工具」。</p>
<p>兩個最容易誤判的象限展開：</p>
<p><strong>Subjective + Component + Quantitative</strong>（這個 step 輸出評分）：對應工具列「LLM-as-judge pairwise / rubric、human rating」、但 <strong>pairwise 是首選、不是 rubric</strong>——pairwise 比較讓 judge 的偏差更可控（兩個答案放在一起比、誰好誰差比較好判）、rubric 容易受 verbosity / position bias 影響。Rubric 留給「需要絕對分數而非相對排序」的場景（如要追蹤絕對品質漂移）。詳見 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a> 的 bias 緩解段。</p>
<p><strong>Objective + Component + Quantitative</strong>（元件對嗎）：這象限最容易做、cost 也最低——deterministic check 配 component test、CI 跑、production trace 隨抽即驗。Production AI 系統若這象限沒覆蓋、bug 永遠靠 user 抱怨才發現、debug 跟 incident review 成本高。對應反例：把這象限的測試交給 LLM judge（見軸誤選一）。</p>
<h2 id="軸誤選的訊號">軸誤選的訊號</h2>
<p>軸選錯時、工具會給出「看起來合理但其實沒用」的訊號。三個常見軸誤選：</p>
<h3 id="誤選一用-subjective-工具測-objective-行為">誤選一：用 subjective 工具測 objective 行為</h3>
<p>例：訂單金額計算對不對、找 LLM judge 來看「這個金額合理嗎」。</p>
<ul>
<li><strong>問題</strong>：金額計算有 ground truth、應該 deterministic check（<code>assert order.total == expected</code>）。LLM judge 對「合理」的判斷有偏差、會放過明顯錯誤、會挑剔正確但不直觀的答案。</li>
<li><strong>訊號</strong>：你發現自己在寫「judge prompt」描述「什麼樣的金額是合理的」、但其實該行為有客觀標準。</li>
<li><strong>修正</strong>：把 judge prompt 翻成 deterministic check。</li>
</ul>
<h3 id="誤選二用-end-to-end-工具測-component-bug">誤選二：用 end-to-end 工具測 component bug</h3>
<p>例：整套系統 success rate 從 90% 掉到 80%、追了一週、結果是 retrieval 漏 chunk。</p>
<ul>
<li><strong>問題</strong>：E2E metric 告訴你「有問題」、不告訴你「在哪」。Component eval 缺失時、debug 從 trace 倒推、耗時。</li>
<li><strong>訊號</strong>：incident 後 root cause analysis 經常超過一天、查到的東西其實 component eval 該秒抓。</li>
<li><strong>修正</strong>：對 critical component（retrieval、tool 調用、parse 階段）加 component eval、production 持續跑。</li>
</ul>
<h3 id="誤選三用-quantitative-工具找-qualitative-訊號">誤選三：用 quantitative 工具找 qualitative 訊號</h3>
<p>例：user 滿意度從 4.2 掉到 4.0、團隊看數字盯一週、不知道發生什麼。</p>
<ul>
<li><strong>問題</strong>：Quantitative metric 只告訴你「有變化」、不告訴你「為什麼」。Qualitative 訊號（user 抱怨內容、抽樣 conversation）才能浮現 hypothesis。</li>
<li><strong>訊號</strong>：團隊看 dashboard 看了很久、卻沒人去讀 actual user feedback。</li>
<li><strong>修正</strong>：quantitative trigger（指標漂移）、qualitative 跟進（讀樣本、找 pattern）。</li>
</ul>
<h2 id="eval-演化路徑">Eval 演化路徑</h2>
<p>不同階段的 LLM 應用、該優先補哪些象限不同。</p>
<h3 id="階段-0mvp沒任何-eval">階段 0：MVP（沒任何 eval）</h3>
<p>問題：「能不能 demo 一下就好」、行為對不對全靠手測。</p>
<ul>
<li><strong>第一個該補的</strong>：Objective + End-to-end + Quantitative。最少跑 10 個 representative case、能看「跑得起來率」就好。</li>
<li><strong>不該太早做</strong>：subjective eval、需要 judge / human rating 的東西。MVP 階段先讓系統穩定運行。</li>
</ul>
<h3 id="階段-1有-user-在用">階段 1：有 user 在用</h3>
<p>問題：production 偶爾有 bug、user 偶爾抱怨、不知道哪些是 systematic、哪些是 random。</p>
<ul>
<li><strong>第二個該補的</strong>：Objective + End-to-end + Qualitative。讀 incident、讀抽樣 trace、找 pattern。</li>
<li><strong>第三個該補的</strong>：Objective + Component + Quantitative。對 critical component（retrieval / tool call / parse）加 component-level eval、production 跑。</li>
<li><strong>不該做</strong>：完整 subjective rubric。先把 objective 失敗修了再說。</li>
</ul>
<h3 id="階段-2要持續優化品質">階段 2：要持續優化品質</h3>
<p>問題：objective 部分已經穩、user 抱怨主要在 subjective 層（語氣、helpful 程度、推薦合不合用）。</p>
<ul>
<li><strong>第四個該補的</strong>：Subjective + Component + Quantitative。用 LLM-as-judge 給每個 step 評分、做 A/B test 比較 prompt 變動。</li>
<li><strong>第五個該補的</strong>：Subjective + End-to-end + Quantitative。CSAT、thumbs up/down、appeal rate。</li>
<li><strong>要做的</strong>：Subjective eval 跟 qualitative review 必須配合進行——quantitative 給出方向、qualitative 給出修法 hypothesis。</li>
</ul>
<h3 id="階段-3規模化跨團隊">階段 3：規模化、跨團隊</h3>
<p>問題：多個產品 / 團隊用同一套 LLM infra、eval 要 cross-cutting。</p>
<ul>
<li><strong>要做的</strong>：標準化 eval pipeline、把象限 1-7 都 cover、qualitative review 進入 ritual（每週 incident review、每月抽樣 trace 讀）。</li>
<li><strong>重點不是「全部都有」、而是「每個象限的 owner 清楚」</strong>。</li>
</ul>
<h2 id="eval-跟-trace-的閉環">Eval 跟 Trace 的閉環</h2>
<p>Eval 不是孤立的——它跟 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a> 形成閉環：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[Production traffic]
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   [LLM trace]  ← 每次 call / agent step / tool 都記錄
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ├── 即時 monitoring（latency / cost / error rate）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ├── 抽樣進 eval set（人工標 + LLM judge）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   └── failed case 進 regression set（防止改 prompt 又壞同樣 case）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   [Eval pipeline]
</span></span><span class="line"><span class="ln">10</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ├── Component eval（單元件 accuracy）
</span></span><span class="line"><span class="ln">12</span><span class="cl">   ├── E2E eval（整套 success rate）
</span></span><span class="line"><span class="ln">13</span><span class="cl">   └── Subjective eval（judge / human rating）
</span></span><span class="line"><span class="ln">14</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">   [Insights]
</span></span><span class="line"><span class="ln">16</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">   ├── Quantitative：metric 漂移 alert
</span></span><span class="line"><span class="ln">18</span><span class="cl">   └── Qualitative：error pattern → hypothesis → 修 prompt / tool / RAG
</span></span><span class="line"><span class="ln">19</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">20</span><span class="cl">   [改動進 production]
</span></span><span class="line"><span class="ln">21</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">22</span><span class="cl">   [回到 production traffic、看 metric 收斂]</span></span></code></pre></div><p>Production trace 不只是 debug 工具、是 eval set 的活泉。Trace + eval 閉環的設計細節見 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20</a>。</p>
<h2 id="跟其他-eval-章節的分工">跟其他 Eval 章節的分工</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>焦點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 本章</a></td>
          <td><strong>Meta</strong>：先選軸、再選工具的設計座標系</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 Benchmarking</a></td>
          <td>具體 benchmark 跟自家 eval set 的方法論</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a></td>
          <td>Trace 怎麼接 eval、production observability</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a></td>
          <td>Subjective eval 的核心工具、rubric / pairwise / bias 緩解</td>
      </tr>
  </tbody>
</table>
<p>讀法建議：先讀本章建立座標系、再依當前痛點往對應章節展開。Subjective eval 痛點 → 4.21；自家 benchmark 設計 → 4.14；production observability → 4.20。</p>
<h2 id="有效-eval-系統的四個設計條件">有效 eval 系統的四個設計條件</h2>
<p>Eval 系統要持續產生有用訊號、必須滿足四個條件。每個條件對應一個常見退化模式、可同時當 checklist 用。</p>
<h3 id="條件一judge-只用在-subjective-軸">條件一：Judge 只用在 subjective 軸</h3>
<p>LLM-as-judge 留給沒 ground truth 的 subjective 行為（語氣、helpful 程度、解釋清楚）、objective 行為（金額、JSON schema、API 參數）用 deterministic check。Judge 的 cost 比 deterministic check 高 1-2 個數量級、精度反而不如、明顯不划算。</p>
<p>對應反例：「全部 eval 都做成 LLM judge」——judge 被誤用在 objective 行為、cost 翻倍、精度反降。</p>
<h3 id="條件二每個-metric-有-ownerthresholdaction">條件二：每個 metric 有 owner、threshold、action</h3>
<p>每個 production metric 都要明確：誰負責看（owner）、什麼數字觸發 alert（threshold）、alert 後做什麼（action）。沒這三項的 metric 是 noise。</p>
<p>對應反例：dashboard 上 50 個 metric 圖、沒人定期看、bug 還是靠 user 抱怨才知道。</p>
<h3 id="條件三eval-set-跟-production-traffic-同步">條件三：Eval set 跟 production traffic 同步</h3>
<p>Production trace 持續抽樣補進 eval set、每季 review eval set 跟 traffic 分佈是否一致。</p>
<p>對應反例：eval set 是兩年前定的、production traffic 已經漂得很遠、eval 通過不代表 user 滿意。</p>
<h3 id="條件四保留-frozen-baseline">條件四：保留 frozen baseline</h3>
<p><a href="/blog/llm/knowledge-cards/frozen-baseline/" data-link-title="Frozen baseline" data-link-desc="Eval 系統中固定特定 prompt &#43; model 當長期對照、讓行為漂移可見的標準作法">Frozen baseline</a> 是把某個特定 prompt + 特定 model 跑 production 一段時間後 freeze 起來、每次新版本跟它比、定期 refresh 並標明時點。漂移看得見才能管理。</p>
<p>對應反例：每次 A/B 都跟「最新版本」比、長期累積漂移完全不可見、「整體變好了沒」無從回答。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>三軸座標（objective / component / quantitative 三個 binary 軸）。</li>
<li>八象限對應工具的結構分類。</li>
<li>三類軸誤選的識別訊號跟修正。</li>
<li>Eval 演化路徑（MVP → user → 優化 → 規模化）。</li>
<li>Eval / trace 閉環的設計。</li>
<li>有效 eval 系統的四個設計條件。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 eval framework（OpenAI Evals、Promptfoo、Braintrust、Langfuse 等會持續演化）。</li>
<li>LLM-as-judge 的具體 prompt 模板跟 bias 緩解技巧。</li>
<li>各 benchmark 的權威性（半年一換）。</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 Benchmarking 與評估方法論</a>、把座標系落到具體 benchmark 設計。Subjective eval 的工具見 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a>、production trace 怎麼接 eval 見 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a>、跟 fuzzy engineering 典範的關係見 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a>（fuzzy 行為的測試本質就是 distribution metric）。</p>
]]></content:encoded></item><item><title>4.14 Benchmarking 與評估方法論</title><link>https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/</guid><description>&lt;p>讀 model card 看到「MMLU 78.5」「HumanEval 82.3」「SWE-bench 12.6」等數字、要能判讀對自己場景的意義；自己跑本地 LLM、要能量化「tok/s、TTFT、實際品質」；想對比不同 model / 量化等級、要有可重現的 evaluation 方法。本章把「LLM 能力評估」跟「本地推論性能評估」兩條軸拆成可操作的方法論。&lt;/p>
&lt;p>本章是 eval 設計的&lt;strong>具體實作層&lt;/strong>——meta 層的 eval 軸選擇（先看軸再看工具的三軸座標）見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系&lt;/a>、subjective eval 的核心工具見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge&lt;/a>。三章合起來才是 production AI app 的完整 eval pipeline。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>看 model card benchmark 數字、判讀對自己場景的相關性。&lt;/li>
&lt;li>區分 capability benchmark（MMLU 等）跟 performance benchmark（tok/s 等）。&lt;/li>
&lt;li>跑 &lt;code>llama-bench&lt;/code> 量測自己硬體 + 模型的真實速度。&lt;/li>
&lt;li>設計 in-house benchmark 評估自己工作流的真實品質。&lt;/li>
&lt;li>看到 benchmark 異常數字時、知道可能的陷阱。&lt;/li>
&lt;/ol>
&lt;h2 id="capability-benchmarks衡量模型會什麼">Capability benchmarks：衡量模型「會什麼」&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-benchmarks/" data-link-title="LLM Benchmarks（MMLU / HumanEval / SWE-bench 等）" data-link-desc="LLM 能力評估的標準 benchmark 集合：MMLU / HumanEval / MBPP / SWE-bench / MT-Bench 等的覆蓋範圍與失效情境">LLM benchmarks&lt;/a> 卡片列了主流 benchmark 的覆蓋面。本節展開對寫 code 場景最相關的幾個：&lt;/p>
&lt;h3 id="coding-benchmarks-的演化">Coding benchmarks 的演化&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Benchmark&lt;/th>
 &lt;th>任務性質&lt;/th>
 &lt;th>適合衡量&lt;/th>
 &lt;th>飽和狀態&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>HumanEval&lt;/td>
 &lt;td>寫一個 Python function 通過簡單 unit test&lt;/td>
 &lt;td>初級 coding 能力&lt;/td>
 &lt;td>飽和（90%+）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MBPP&lt;/td>
 &lt;td>同 HumanEval、規模較大&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;td>飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HumanEval+&lt;/td>
 &lt;td>HumanEval + 更嚴格 test cases&lt;/td>
 &lt;td>排除 edge case 漏寫&lt;/td>
 &lt;td>部分飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BigCodeBench&lt;/td>
 &lt;td>真實 library use（pandas、numpy 等）&lt;/td>
 &lt;td>中級 coding&lt;/td>
 &lt;td>進行中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>LiveCodeBench&lt;/td>
 &lt;td>LeetCode 風格 problems、定期更新避免污染&lt;/td>
 &lt;td>Algorithm + reasoning&lt;/td>
 &lt;td>進行中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>SWE-bench&lt;/strong>&lt;/td>
 &lt;td>真實 GitHub issue 修復、要看懂 codebase&lt;/td>
 &lt;td>真實 coding agent 能力&lt;/td>
 &lt;td>仍有大空間（前沿 &amp;lt; 60%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>SWE-bench Verified&lt;/strong>&lt;/td>
 &lt;td>SWE-bench 的人工 verify 子集&lt;/td>
 &lt;td>同上、更可靠&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀建議：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>看 SWE-bench、別只看 HumanEval&lt;/strong>：HumanEval 早飽和、無法區分前沿模型；SWE-bench 仍有大差距、可信度高&lt;/li>
&lt;li>&lt;strong>HumanEval 90% vs 95% 差異不大&lt;/strong>：飽和區間的 noise 大、判斷 coding 能力靠 SWE-bench / 真實任務測&lt;/li>
&lt;li>&lt;strong>LiveCodeBench 避免污染&lt;/strong>：定期出新題、模型訓練 cutoff 後的題目不在 pretrain corpus、更能反映真實能力&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：本章所列 benchmark 飽和狀態（HumanEval 90%+、MMLU 85%+、GSM8K 90%+）、SOTA 數字（SWE-bench &amp;lt; 60%）、各模型在各 benchmark 的相對排名 — 都是 2026/5 估計、隨新模型推出快速變動、引用前以 &lt;a href="https://paperswithcode.com/">Papers with Code&lt;/a> 跟 &lt;a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard&lt;/a> 當前狀態為準。&lt;/p></description><content:encoded><![CDATA[<p>讀 model card 看到「MMLU 78.5」「HumanEval 82.3」「SWE-bench 12.6」等數字、要能判讀對自己場景的意義；自己跑本地 LLM、要能量化「tok/s、TTFT、實際品質」；想對比不同 model / 量化等級、要有可重現的 evaluation 方法。本章把「LLM 能力評估」跟「本地推論性能評估」兩條軸拆成可操作的方法論。</p>
<p>本章是 eval 設計的<strong>具體實作層</strong>——meta 層的 eval 軸選擇（先看軸再看工具的三軸座標）見 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系</a>、subjective eval 的核心工具見 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a>。三章合起來才是 production AI app 的完整 eval pipeline。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>看 model card benchmark 數字、判讀對自己場景的相關性。</li>
<li>區分 capability benchmark（MMLU 等）跟 performance benchmark（tok/s 等）。</li>
<li>跑 <code>llama-bench</code> 量測自己硬體 + 模型的真實速度。</li>
<li>設計 in-house benchmark 評估自己工作流的真實品質。</li>
<li>看到 benchmark 異常數字時、知道可能的陷阱。</li>
</ol>
<h2 id="capability-benchmarks衡量模型會什麼">Capability benchmarks：衡量模型「會什麼」</h2>
<p><a href="/blog/llm/knowledge-cards/llm-benchmarks/" data-link-title="LLM Benchmarks（MMLU / HumanEval / SWE-bench 等）" data-link-desc="LLM 能力評估的標準 benchmark 集合：MMLU / HumanEval / MBPP / SWE-bench / MT-Bench 等的覆蓋範圍與失效情境">LLM benchmarks</a> 卡片列了主流 benchmark 的覆蓋面。本節展開對寫 code 場景最相關的幾個：</p>
<h3 id="coding-benchmarks-的演化">Coding benchmarks 的演化</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>適合衡量</th>
          <th>飽和狀態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HumanEval</td>
          <td>寫一個 Python function 通過簡單 unit test</td>
          <td>初級 coding 能力</td>
          <td>飽和（90%+）</td>
      </tr>
      <tr>
          <td>MBPP</td>
          <td>同 HumanEval、規模較大</td>
          <td>同上</td>
          <td>飽和</td>
      </tr>
      <tr>
          <td>HumanEval+</td>
          <td>HumanEval + 更嚴格 test cases</td>
          <td>排除 edge case 漏寫</td>
          <td>部分飽和</td>
      </tr>
      <tr>
          <td>BigCodeBench</td>
          <td>真實 library use（pandas、numpy 等）</td>
          <td>中級 coding</td>
          <td>進行中</td>
      </tr>
      <tr>
          <td>LiveCodeBench</td>
          <td>LeetCode 風格 problems、定期更新避免污染</td>
          <td>Algorithm + reasoning</td>
          <td>進行中</td>
      </tr>
      <tr>
          <td><strong>SWE-bench</strong></td>
          <td>真實 GitHub issue 修復、要看懂 codebase</td>
          <td>真實 coding agent 能力</td>
          <td>仍有大空間（前沿 &lt; 60%）</td>
      </tr>
      <tr>
          <td><strong>SWE-bench Verified</strong></td>
          <td>SWE-bench 的人工 verify 子集</td>
          <td>同上、更可靠</td>
          <td>同上</td>
      </tr>
  </tbody>
</table>
<p>判讀建議：</p>
<ol>
<li><strong>看 SWE-bench、別只看 HumanEval</strong>：HumanEval 早飽和、無法區分前沿模型；SWE-bench 仍有大差距、可信度高</li>
<li><strong>HumanEval 90% vs 95% 差異不大</strong>：飽和區間的 noise 大、判斷 coding 能力靠 SWE-bench / 真實任務測</li>
<li><strong>LiveCodeBench 避免污染</strong>：定期出新題、模型訓練 cutoff 後的題目不在 pretrain corpus、更能反映真實能力</li>
</ol>
<blockquote>
<p><strong>事實查核註</strong>：本章所列 benchmark 飽和狀態（HumanEval 90%+、MMLU 85%+、GSM8K 90%+）、SOTA 數字（SWE-bench &lt; 60%）、各模型在各 benchmark 的相對排名 — 都是 2026/5 估計、隨新模型推出快速變動、引用前以 <a href="https://paperswithcode.com/">Papers with Code</a> 跟 <a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard</a> 當前狀態為準。</p></blockquote>
<h3 id="reasoning-benchmarks">Reasoning benchmarks</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>主要 audience</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MMLU</td>
          <td>通用知識多選</td>
          <td>Pretrain 能力</td>
      </tr>
      <tr>
          <td>MMLU-Pro</td>
          <td>MMLU 更困難版本、5 → 10 選 1</td>
          <td>同上、區分前沿模型</td>
      </tr>
      <tr>
          <td>GSM8K</td>
          <td>小學數學 word problem</td>
          <td>早期 reasoning</td>
      </tr>
      <tr>
          <td>MATH</td>
          <td>高中 / 競賽數學</td>
          <td>中級 reasoning</td>
      </tr>
      <tr>
          <td>AIME / GPQA</td>
          <td>競賽數學 / graduate-level science</td>
          <td><a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models</a></td>
      </tr>
      <tr>
          <td>ARC-AGI</td>
          <td>視覺 reasoning puzzle</td>
          <td>General reasoning</td>
      </tr>
  </tbody>
</table>
<p>判讀：</p>
<ol>
<li><strong>Reasoning model 在 AIME / GPQA 顯著領先 instruct model</strong>：這正是 reasoning model 的優勢區</li>
<li><strong>MMLU 飽和</strong>：85%+ 後差別意義不大、改看 MMLU-Pro</li>
<li><strong>GSM8K 接近飽和</strong>：90%+、改看 MATH / AIME</li>
</ol>
<h3 id="long-context-benchmarks">Long context benchmarks</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>衡量</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/needle-in-haystack/" data-link-title="Needle in a Haystack" data-link-desc="把一個事實藏在 long context 不同位置、測試 LLM 能否抓出來的 benchmark 方法">Needle in haystack</a></td>
          <td>抓單一事實</td>
          <td>Lower bound effective context</td>
      </tr>
      <tr>
          <td>RULER</td>
          <td>Multi-needle、aggregation、reasoning</td>
          <td>真實 long context 能力</td>
      </tr>
      <tr>
          <td>LongBench</td>
          <td>QA、summarization、code 等真實任務</td>
          <td>全方面 long context</td>
      </tr>
      <tr>
          <td>∞Bench</td>
          <td>100K+ context tasks</td>
          <td>極長 context</td>
      </tr>
  </tbody>
</table>
<p>判讀：聲稱「128K context」要配 RULER / LongBench 分數才知道實用、見 <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 Long context engineering</a>。</p>
<h2 id="performance-benchmarks衡量跑多快">Performance benchmarks：衡量「跑多快」</h2>
<p>跟 capability 並列的另一條軸 — 推論速度：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>定義</th>
          <th>影響使用者體感</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/tokens-per-second/" data-link-title="Tokens Per Second" data-link-desc="LLM 每秒能生成幾個 token：生字速度的標準量化指標">Tokens per second</a></td>
          <td>生成速度（tok/s）</td>
          <td>連續輸出感受</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a></td>
          <td>Time to first token</td>
          <td>「按下 enter 多久才看到字」</td>
      </tr>
      <tr>
          <td>Prefill speed</td>
          <td>Prompt 處理速度（tok/s）</td>
          <td>長 prompt 的等待時間</td>
      </tr>
      <tr>
          <td>Memory footprint</td>
          <td>推論記憶體佔用</td>
          <td>能不能塞進機器</td>
      </tr>
      <tr>
          <td>Energy consumption</td>
          <td>推論電力</td>
          <td>長期使用成本</td>
      </tr>
  </tbody>
</table>
<h3 id="llama-bench標準工具">llama-bench：標準工具</h3>
<p>llama.cpp 內建 benchmark 工具：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 基本測試：純 generation 速度</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">llama-bench -m model.gguf -p <span class="m">512</span> -n <span class="m">128</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># -p 512：prompt 512 token（測 prefill）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># -n 128：generate 128 token（測 decode）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 不同 context 長度的影響</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">llama-bench -m model.gguf -p 512,2048,8192 -n <span class="m">128</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 開 flash attention</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">llama-bench -m model.gguf -p <span class="m">512</span> -n <span class="m">128</span> -fa <span class="m">1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Speculative decoding 對比</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">llama-bench -m target.gguf --draft-model drafter.gguf <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>            -p <span class="m">512</span> -n <span class="m">128</span> --speculative-draft <span class="m">5</span></span></span></code></pre></div><p>輸出範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">| model                |       size |     params | backend    | ngl |   test |              t/s |
</span></span><span class="line"><span class="ln">2</span><span class="cl">| -------------------- | ---------: | ---------: | ---------- | --: | -----: | ---------------: |
</span></span><span class="line"><span class="ln">3</span><span class="cl">| gemma3 31B Q4_K - M  |  18.45 GiB |    31.21 B | Metal      |  99 |  pp512 |    324.21 ± 1.27 |
</span></span><span class="line"><span class="ln">4</span><span class="cl">| gemma3 31B Q4_K - M  |  18.45 GiB |    31.21 B | Metal      |  99 |  tg128 |     28.43 ± 0.31 |</span></span></code></pre></div><p>讀法：</p>
<ul>
<li><code>pp512</code>：prefill 512 token 的 throughput（tok/s）</li>
<li><code>tg128</code>：generate 128 token 的 throughput（tok/s、即 tok/s）</li>
<li><code>± 0.31</code>：多次跑的 std deviation、&lt; 5% 是穩定基線</li>
</ul>
<h3 id="推論成本-vs-品質的-trade-off-矩陣">推論成本 vs 品質的 trade-off 矩陣</h3>
<p>對自己機器跑 <code>llama-bench</code> 後、可以建一個矩陣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                     tok/s 高           tok/s 中           tok/s 低
</span></span><span class="line"><span class="ln">2</span><span class="cl">品質（HumanEval）
</span></span><span class="line"><span class="ln">3</span><span class="cl">     高              [Q4 7B coder]      [Q4 14B coder]    [Q4 30B reasoning]
</span></span><span class="line"><span class="ln">4</span><span class="cl">     中              [Q4 14B instruct]  [Q4 30B instruct]
</span></span><span class="line"><span class="ln">5</span><span class="cl">     低              [Q4 30B base]      [unused]          [unused]</span></span></code></pre></div><p>對應到實際選型：</p>
<ul>
<li>自動補完（高頻、低品質需求）：左上 tok/s 高的小模型</li>
<li>對話（中頻、中品質需求）：中段</li>
<li>複雜 reasoning（低頻、高品質需求）：右下大 reasoning model</li>
</ul>
<h2 id="in-house-benchmark自己工作流的真實評估">In-house benchmark：自己工作流的真實評估</h2>
<p>最重要的 benchmark 是「自己真實任務上的表現」、公開 benchmark 是粗略 filter。</p>
<h3 id="建立-in-house-benchmark-的步驟">建立 in-house benchmark 的步驟</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 蒐集真實案例
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   - 從過往工作流挑 30-100 個有代表性的任務
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   - 含「容易任務」「中等任務」「困難任務」三類
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - 每個任務記錄 (input prompt, expected output 或評分標準)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">2. 定義評分機制
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   - Objective（最理想）：unit test、exact match、能機械驗證
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - Semi-objective：rubric 評分、人工或 LLM-as-judge
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - Subjective（最後手段）：人工 A/B 偏好
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">3. 跑 candidate models
</span></span><span class="line"><span class="ln">12</span><span class="cl">   - 對每個模型、每個任務都跑、記錄輸出
</span></span><span class="line"><span class="ln">13</span><span class="cl">   - 注意推論參數一致（temperature、top-p、max_tokens 一樣）
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 注意 prompt 一致（chat template、system prompt）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">4. 評分
</span></span><span class="line"><span class="ln">17</span><span class="cl">   - Objective：跑 test、算 pass rate
</span></span><span class="line"><span class="ln">18</span><span class="cl">   - Semi-objective：建 rubric、評分
</span></span><span class="line"><span class="ln">19</span><span class="cl">   - Subjective：人工 / LLM 評
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">5. 看分佈、不只看平均
</span></span><span class="line"><span class="ln">22</span><span class="cl">   - 平均 80% 可能來自「20 題滿分 + 80 題 70%」或「100 題 80%」
</span></span><span class="line"><span class="ln">23</span><span class="cl">   - 看 std、看哪些任務崩、針對性 debug</span></span></code></pre></div><h3 id="llm-as-judge-的注意點">LLM-as-judge 的注意點</h3>
<p>用 LLM（如 GPT-4、Claude）評其他 LLM 是省人力的方法、但有 bias：</p>
<ol>
<li><strong>Verbosity bias</strong>：judge 傾向給「答得長」的高分、即使內容沒提升</li>
<li><strong>Position bias</strong>：A/B 比較時、judge 對 A、B 位置敏感、要做 swap 平均</li>
<li><strong>Self-preference bias</strong>：judge 模型偏好自己風格的答案</li>
<li><strong>Judge 能力上限</strong>：judge 模型本身不夠強、評不出兩個強模型的差距</li>
</ol>
<p>緩解：</p>
<ol>
<li><strong>用結構化 rubric</strong>：給 judge 明確評分標準、不只「哪個好」</li>
<li><strong>多 judge 取共識</strong>：用 2-3 個不同 judge model 各評、取一致 / 平均</li>
<li><strong>Critical task 仍要人工 review</strong>：高 stake 任務不能全靠 LLM-as-judge</li>
</ol>
<h2 id="常見陷阱跟反例">常見陷阱跟反例</h2>
<h3 id="陷阱-1訓練資料污染">陷阱 1：訓練資料污染</h3>
<p>模型在 benchmark 題目上「看似強」、實際是 memorization：</p>
<p>判讀訊號：</p>
<ul>
<li>benchmark cutoff date 之前的 dataset、新模型分數異常高</li>
<li>同模型在「同 dataset 變體（rephrase）」上分數顯著低</li>
</ul>
<p>緩解：用較新出題的 benchmark（如 LiveCodeBench 定期更新）。</p>
<h3 id="陷阱-2single-benchmark-過擬合">陷阱 2：Single benchmark 過擬合</h3>
<p>模型廠商針對特定 benchmark fine-tune、benchmark 高但通用能力沒提升：</p>
<p>判讀訊號：</p>
<ul>
<li>在 benchmark A 顯著領先、在 benchmark B（測類似能力）沒差</li>
<li>同模型實際使用後評價跟 benchmark 不符</li>
</ul>
<p>緩解：看多個 benchmark + in-house benchmark。</p>
<h3 id="陷阱-3prompt-sensitivity">陷阱 3：Prompt sensitivity</h3>
<p>同 benchmark 用不同 prompt 格式、score 差幾個百分點：</p>
<p>判讀訊號：</p>
<ul>
<li>model card 報的數字跟自己跑差很多</li>
<li>同模型不同 prompt template 結果差距大</li>
</ul>
<p>緩解：自己跑、用一致的 prompt template；report 時明確標 prompt 版本。</p>
<h3 id="陷阱-4sampling-設定不一致">陷阱 4：Sampling 設定不一致</h3>
<p>不同模型用不同 temperature / top-p、結果不可比：</p>
<p>判讀訊號：</p>
<ul>
<li>兩篇 paper 用同 benchmark 報不同數字、推論參數不同</li>
</ul>
<p>緩解：對 reproduction 用 temperature=0 + greedy decoding 確保一致。</p>
<h2 id="benchmark-之間的關係跟導讀路徑">Benchmark 之間的關係跟導讀路徑</h2>
<p>各 benchmark 在不同階段的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">研究模型能力（paper 階段）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  HELM / MT-Bench / Chatbot Arena → 通用能力 baseline
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  MMLU / GSM8K / AIME            → reasoning 能力
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  HumanEval / SWE-bench           → coding 能力
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  RULER / LongBench               → long context
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">挑選模型（user 階段）：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  Open LLM Leaderboard            → 快速 filter
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  MTEB（若 RAG）                  → embedding model
</span></span><span class="line"><span class="ln">10</span><span class="cl">  In-house benchmark              → final 確認
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">監控模型（production 階段）：
</span></span><span class="line"><span class="ln">13</span><span class="cl">  自己工作流 KPI                  → 真實品質
</span></span><span class="line"><span class="ln">14</span><span class="cl">  A/B test                       → 部署前的決策
</span></span><span class="line"><span class="ln">15</span><span class="cl">  User feedback                  → 持續迭代</span></span></code></pre></div><h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Benchmark 跟自己任務對齊的必要性</li>
<li>訓練污染 / 飽和 / single-task overfit 的陷阱</li>
<li>LLM-as-judge bias 的存在</li>
<li>In-house benchmark 是最後 final test</li>
<li><code>llama-bench</code> 是量測本地推論的標準工具</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>各 benchmark 的飽和狀態跟前沿 score</li>
<li>主流 benchmark 的選擇（HumanEval → MBPP → SWE-bench → &hellip;）</li>
<li>LLM-as-judge model 的偏好（隨 judge model 更新而變）</li>
<li>新 benchmark 出現（特別是 reasoning / long-context 領域）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15 Vision in coding workflow</a>、把 vision 維度加進 coding 工作流的設計取捨。讀完 4.10、模組四覆蓋了 LLM 作為系統元件的設計取捨（RAG、tool use、agent、應用層協議、workflow、resource planning、long context、embedding、benchmarking、vision）、寫 code 場景需要的應用層概念完整、之後可進入 <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五 PC 獨立 GPU</a> 或 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六 安全</a>。</p>
]]></content:encoded></item><item><title>4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code</title><link>https://tarrragon.github.io/blog/llm/04-applications/vision-in-coding-workflow/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/vision-in-coding-workflow/</guid><description>&lt;p>寫 code 工作流不只是文字進文字出 — 大量任務需要看圖：browser 截圖 debug UI、Figma mockup 寫前端、架構白板照片寫文件、log 截圖找 error。&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/vlm/" data-link-title="VLM（Vision-Language Model）" data-link-desc="同時吃圖片 &amp;#43; 文字輸入、產生文字輸出的 LLM 變體、coding 工作流中處理截圖 / 設計稿 / UI debug 的基底">VLM&lt;/a>（Vision-Language Model）把這些任務從「人類用文字描述給 LLM」升級到「LLM 直接看圖理解」。本章把 vision 在 coding 場景的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、IDE 整合現狀拆成可操作的判讀。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>本章 framing 重點&lt;/strong>：教材整體聲明過「不放多模態」、但 VLM 在 coding 工作流的 trigger 已經響（雲端 IDE 普遍整合、本地推論伺服器陸續支援）、重新評估後加入本章。本章聚焦「跨工具世代不變的原理 + 寫 code 場景特有的判讀」、避開「具體 IDE plugin API」這類易過時內容。&lt;/p>&lt;/blockquote>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 VLM 跟純文字 LLM 在 coding 場景的能力差異。&lt;/li>
&lt;li>看到截圖 / mockup / 設計稿時、判斷該用 VLM 還是純文字描述。&lt;/li>
&lt;li>對自己硬體預算選擇本地 VLM（Qwen2.5-VL / Llama 3.2 Vision / Gemma 3 Vision）。&lt;/li>
&lt;li>估算 VLM 推論的 context budget（image token + text token）。&lt;/li>
&lt;li>知道 IDE 整合 VLM 的現狀跟 trigger 訊號（什麼時候該升級到 vision-native workflow）。&lt;/li>
&lt;/ol>
&lt;h2 id="coding-場景的-vision-use-cases">Coding 場景的 vision use cases&lt;/h2>
&lt;p>寫 code 工作流中、有 vision 跟沒 vision 的差距：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>任務&lt;/th>
 &lt;th>沒 vision&lt;/th>
 &lt;th>有 vision&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>UI bug debug&lt;/td>
 &lt;td>人類手寫「按鈕對齊不對、應該 vertically centered」&lt;/td>
 &lt;td>截圖貼進來、VLM 看 layout 直接判讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Figma → React code&lt;/td>
 &lt;td>人類描述「navbar、3 col grid、卡片含 icon + text」&lt;/td>
 &lt;td>把 mockup 截圖貼進來、VLM 直接生對應 code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error dialog / stack trace 截圖&lt;/td>
 &lt;td>人類複製貼上完整 error message&lt;/td>
 &lt;td>截圖、VLM OCR + 理解 context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>白板 / 紙上 architecture&lt;/td>
 &lt;td>人類重新描述「3 個 microservice、訊息經過 queue&amp;hellip;」&lt;/td>
 &lt;td>拍照、VLM 看圖生 mermaid 圖 / documentation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Browser DevTools 看 console&lt;/td>
 &lt;td>人類複製 log&lt;/td>
 &lt;td>截圖、VLM 看 stack trace + 周圍 panel context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跟設計師對齊 visual style&lt;/td>
 &lt;td>人類描述配色、字體&lt;/td>
 &lt;td>截圖比較、VLM 抓 RGB / 字體 hint&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Code screenshot 從別人帖文&lt;/td>
 &lt;td>人類重打&lt;/td>
 &lt;td>截圖、VLM OCR + 解讀&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀反射：&lt;strong>任務需要看「整體 visual context」&lt;/strong>（如 layout 對齊、設計稿 → code）→ VLM 顯著贏；&lt;strong>純 OCR&lt;/strong>（只認字）→ 專門 OCR 工具（Tesseract / PaddleOCR）可能更穩。&lt;/p></description><content:encoded><![CDATA[<p>寫 code 工作流不只是文字進文字出 — 大量任務需要看圖：browser 截圖 debug UI、Figma mockup 寫前端、架構白板照片寫文件、log 截圖找 error。<a href="/blog/llm/knowledge-cards/vlm/" data-link-title="VLM（Vision-Language Model）" data-link-desc="同時吃圖片 &#43; 文字輸入、產生文字輸出的 LLM 變體、coding 工作流中處理截圖 / 設計稿 / UI debug 的基底">VLM</a>（Vision-Language Model）把這些任務從「人類用文字描述給 LLM」升級到「LLM 直接看圖理解」。本章把 vision 在 coding 場景的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、IDE 整合現狀拆成可操作的判讀。</p>
<blockquote>
<p><strong>本章 framing 重點</strong>：教材整體聲明過「不放多模態」、但 VLM 在 coding 工作流的 trigger 已經響（雲端 IDE 普遍整合、本地推論伺服器陸續支援）、重新評估後加入本章。本章聚焦「跨工具世代不變的原理 + 寫 code 場景特有的判讀」、避開「具體 IDE plugin API」這類易過時內容。</p></blockquote>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 VLM 跟純文字 LLM 在 coding 場景的能力差異。</li>
<li>看到截圖 / mockup / 設計稿時、判斷該用 VLM 還是純文字描述。</li>
<li>對自己硬體預算選擇本地 VLM（Qwen2.5-VL / Llama 3.2 Vision / Gemma 3 Vision）。</li>
<li>估算 VLM 推論的 context budget（image token + text token）。</li>
<li>知道 IDE 整合 VLM 的現狀跟 trigger 訊號（什麼時候該升級到 vision-native workflow）。</li>
</ol>
<h2 id="coding-場景的-vision-use-cases">Coding 場景的 vision use cases</h2>
<p>寫 code 工作流中、有 vision 跟沒 vision 的差距：</p>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>沒 vision</th>
          <th>有 vision</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>UI bug debug</td>
          <td>人類手寫「按鈕對齊不對、應該 vertically centered」</td>
          <td>截圖貼進來、VLM 看 layout 直接判讀</td>
      </tr>
      <tr>
          <td>Figma → React code</td>
          <td>人類描述「navbar、3 col grid、卡片含 icon + text」</td>
          <td>把 mockup 截圖貼進來、VLM 直接生對應 code</td>
      </tr>
      <tr>
          <td>Error dialog / stack trace 截圖</td>
          <td>人類複製貼上完整 error message</td>
          <td>截圖、VLM OCR + 理解 context</td>
      </tr>
      <tr>
          <td>白板 / 紙上 architecture</td>
          <td>人類重新描述「3 個 microservice、訊息經過 queue&hellip;」</td>
          <td>拍照、VLM 看圖生 mermaid 圖 / documentation</td>
      </tr>
      <tr>
          <td>Browser DevTools 看 console</td>
          <td>人類複製 log</td>
          <td>截圖、VLM 看 stack trace + 周圍 panel context</td>
      </tr>
      <tr>
          <td>跟設計師對齊 visual style</td>
          <td>人類描述配色、字體</td>
          <td>截圖比較、VLM 抓 RGB / 字體 hint</td>
      </tr>
      <tr>
          <td>Code screenshot 從別人帖文</td>
          <td>人類重打</td>
          <td>截圖、VLM OCR + 解讀</td>
      </tr>
  </tbody>
</table>
<p>判讀反射：<strong>任務需要看「整體 visual context」</strong>（如 layout 對齊、設計稿 → code）→ VLM 顯著贏；<strong>純 OCR</strong>（只認字）→ 專門 OCR 工具（Tesseract / PaddleOCR）可能更穩。</p>
<h2 id="vlm-在-coding-場景的失敗模式">VLM 在 coding 場景的失敗模式</h2>
<p>VLM 不是萬能、寫 code 場景的常見失敗：</p>
<ol>
<li><strong>看不清細節</strong>：低解析度模式下、截圖中的小字 / 細邊框 / 1px 對齊看不出來；要開高解析度模式 + 高 image token budget</li>
<li><strong>OCR 出錯</strong>：手寫字 / 模糊截圖 / 特殊字型上錯字、特別是中文 / 程式碼 special character</li>
<li><strong>空間關係推理弱</strong>：「左上角的按鈕」「flexbox 第二行第三個」這類描述、VLM 推理仍不穩</li>
<li><strong>DPI 跟縮放問題</strong>：Retina 截圖、放大縮小、subpixel 等情況、不同 VLM 結果差異大</li>
<li><strong>多張圖比較</strong>：比兩張截圖差異、VLM 容易遺漏細節；最好給明確指令「請對比 A、B 兩張圖的 X 元素」</li>
</ol>
<p>緩解：</p>
<ul>
<li>截圖前裁切到「跟問題相關的區域」、別整個螢幕丟</li>
<li>高細節任務開高解析度模式（API 的 <code>detail: high</code> 或本地 VLM 的 <code>min_pixels</code> 設高）</li>
<li>OCR-only 任務改用專門工具、不靠 VLM</li>
</ul>
<h2 id="本地-vlm-選型20265">本地 VLM 選型（2026/5）</h2>
<p>本地可跑的主流 VLM：</p>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>大小</th>
          <th>Q4 量化後記憶體</th>
          <th>適合硬體</th>
          <th>Coding 場景強項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Qwen2.5-VL-7B / Qwen3-VL-7B</strong></td>
          <td>7B（vision + LLM）</td>
          <td>~6 GB</td>
          <td>16GB+ Mac / 12GB+ VRAM</td>
          <td>中英 OCR、UI 元素辨識</td>
      </tr>
      <tr>
          <td><strong>Qwen2.5-VL-32B / 72B</strong></td>
          <td>32B / 72B</td>
          <td>~18 / 40 GB</td>
          <td>32GB+ Mac / 24GB+ VRAM</td>
          <td>強 reasoning、多圖比較</td>
      </tr>
      <tr>
          <td><strong>Llama 3.2 Vision-11B</strong></td>
          <td>11B</td>
          <td>~7 GB</td>
          <td>16GB+ Mac / 12GB+ VRAM</td>
          <td>英文場景、通用</td>
      </tr>
      <tr>
          <td><strong>Llama 3.2 Vision-90B</strong></td>
          <td>90B</td>
          <td>~50 GB</td>
          <td>64GB+ Mac / 多卡</td>
          <td>接近雲端品質、本地高端</td>
      </tr>
      <tr>
          <td><strong>Gemma 3 Vision-4B / 12B / 27B</strong></td>
          <td>4-27B</td>
          <td>~3-16 GB</td>
          <td>24GB+ Mac / 16GB+ VRAM</td>
          <td>多語、輕量本地</td>
      </tr>
      <tr>
          <td>Pixtral 12B / 124B</td>
          <td>12B / 124B</td>
          <td>~7 / 70 GB</td>
          <td>同上</td>
          <td>Mistral 系、研究 / 評估</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>事實查核註</strong>：本地 VLM 的推論伺服器支援度（llama.cpp、Ollama、MLX）依模型 / 推論伺服器版本變動很快、引用前以對應 release notes 為準。2026/5 主流是 llama.cpp 對 Qwen2-VL / Llama 3.2 Vision / Gemma 3 Vision 支援度較好、其他模型可能要等。</p></blockquote>
<h3 id="硬體-vs-模型對照">硬體 vs 模型對照</h3>
<table>
  <thead>
      <tr>
          <th>硬體</th>
          <th>推薦 VLM</th>
          <th>預期體感</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M4 Pro 24GB / 4090 16GB</td>
          <td>Qwen2.5-VL-7B / Llama 3.2 Vision-11B</td>
          <td>可用、品質中等、適合輕度 vision 工作</td>
      </tr>
      <tr>
          <td>M4 Pro 36GB / 5090 24GB</td>
          <td>Qwen2.5-VL-32B / Gemma 3 Vision-27B</td>
          <td>寬鬆、品質接近 2024 雲端中階</td>
      </tr>
      <tr>
          <td>M4 Max 48-64GB</td>
          <td>Qwen2.5-VL-32B / Llama 3.2 Vision-90B（Q4 緊）</td>
          <td>高品質、coding-vision 主力</td>
      </tr>
      <tr>
          <td>M4 Max 128GB / 多卡 PC</td>
          <td>Llama 3.2 Vision-90B / Qwen2.5-VL-72B</td>
          <td>接近雲端旗艦</td>
      </tr>
  </tbody>
</table>
<h3 id="跟純文字-llm-對照的記憶體成本">跟純文字 LLM 對照的記憶體成本</h3>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>純文字 LLM</th>
          <th>VLM</th>
          <th>額外成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模型本體</td>
          <td>18 GB（31B Q4）</td>
          <td>~25 GB（32B VLM Q4）</td>
          <td>+30-40% 給 vision encoder</td>
      </tr>
      <tr>
          <td>Context budget 影響</td>
          <td>純 text</td>
          <td>一張 1024×1024 圖 ≈ 1500-2500 <a href="/blog/llm/knowledge-cards/image-token/" data-link-title="Image Token" data-link-desc="VLM 把圖片轉成「對 Transformer 而言跟 text token 同質」的向量、計入 context window 預算">image tokens</a></td>
          <td>多張圖直接擠 context</td>
      </tr>
      <tr>
          <td>Prefill 時間（TTFT）</td>
          <td>視 prompt 長度</td>
          <td>圖處理階段顯著拉長 TTFT</td>
          <td>第一個字等較久</td>
      </tr>
      <tr>
          <td>Tokens/s 生成速度</td>
          <td>同模型大小</td>
          <td>比同規模純文字 LLM 慢 ~10-30%</td>
          <td>Vision encoder overhead</td>
      </tr>
  </tbody>
</table>
<h2 id="本地-vlm-vs-雲端-vlm-的分工">本地 VLM vs 雲端 VLM 的分工</h2>
<p>跟模組六的 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">跨雲端 / 本地資料邊界</a> 同邏輯、按任務分流：</p>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>推薦</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>看 NDA / 機密 codebase 截圖</td>
          <td>本地 VLM（Qwen2.5-VL 7B+）</td>
          <td>截圖含敏感程式碼、不能送雲端</td>
      </tr>
      <tr>
          <td>看自家內部 UI debug</td>
          <td>本地 VLM</td>
          <td>UI 設計可能機密</td>
      </tr>
      <tr>
          <td>看公開 OSS 截圖</td>
          <td>雲端 VLM（Claude 4 / GPT-5 vision）</td>
          <td>雲端品質高、無隱私顧慮</td>
      </tr>
      <tr>
          <td>看 Figma mockup（高品質要求）</td>
          <td>雲端 VLM</td>
          <td>Figma → React code 雲端目前仍領先</td>
      </tr>
      <tr>
          <td>看自己 whiteboard 拍照</td>
          <td>本地 VLM</td>
          <td>個人 thinking 不送雲端</td>
      </tr>
      <tr>
          <td>看 Stack Overflow 截圖</td>
          <td>雲端 / 本地都行</td>
          <td>公開內容、看品質需求</td>
      </tr>
  </tbody>
</table>
<p>混用配置（同 <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 long-context</a> 跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 cross-cloud</a> 推薦模式）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Continue.dev config：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  Local VLM（default for vision）：Qwen2.5-VL-32B
</span></span><span class="line"><span class="ln">3</span><span class="cl">    日常 vision 工作、敏感內容
</span></span><span class="line"><span class="ln">4</span><span class="cl">  Cloud VLM（manual switch）：Claude 4 vision
</span></span><span class="line"><span class="ln">5</span><span class="cl">    複雜 Figma → code、高品質要求
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">Local text model：Qwen3-Coder-30B-Instruct
</span></span><span class="line"><span class="ln">8</span><span class="cl">  純文字 coding 任務</span></span></code></pre></div><h2 id="image-token-跟-context-budget">Image token 跟 context budget</h2>
<p>VLM 推論時、<a href="/blog/llm/knowledge-cards/image-token/" data-link-title="Image Token" data-link-desc="VLM 把圖片轉成「對 Transformer 而言跟 text token 同質」的向量、計入 context window 預算">image token</a> 跟 text token 共用同一個 context window。預算估算：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">一張 1024×1024 截圖：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  低細節（low detail）：~85-256 image tokens
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  中等：~500-1000 image tokens
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  高細節（high detail）：~1500-3000 image tokens
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">VLM 對話的典型 context 構成：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  System prompt：~500 token
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  之前對話歷史：~2000-5000 token
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  3 張截圖：~3000-6000 token
</span></span><span class="line"><span class="ln">10</span><span class="cl">  使用者當前 prompt：~200 token
</span></span><span class="line"><span class="ln">11</span><span class="cl">  → 合計 ~6K-12K input
</span></span><span class="line"><span class="ln">12</span><span class="cl">  → 加上 generated answer 跟 reasoning trace（若 VLM 也支援 reasoning）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  → 16K context 模型開始吃緊</span></span></code></pre></div><p>實務建議：</p>
<ol>
<li><strong>VLM 工作流配 long context 模型</strong>：至少 32K context、64K 更好</li>
<li><strong>多輪對話控制歷史長度</strong>：每幾輪 trim 舊截圖、避免 context 爆</li>
<li><strong>裁切截圖、只貼相關區域</strong>：別把整個 4K 螢幕貼進來、跟問題相關的窗口就行</li>
<li><strong>看清楚 API 文件的 detail 模式</strong>：不需要看小字的任務用 low detail、省 token</li>
</ol>
<h2 id="ide-整合的現狀20265">IDE 整合的現狀（2026/5）</h2>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>Vision 支援程度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Desktop</td>
          <td>完整、拖拉截圖進 chat</td>
      </tr>
      <tr>
          <td>Cursor</td>
          <td>完整、<code>@image</code> 或拖拉</td>
      </tr>
      <tr>
          <td>Continue.dev</td>
          <td>部分（依 provider 跟版本）、本地 VLM 仍演化中</td>
      </tr>
      <tr>
          <td>Aider</td>
          <td>CLI 支援 image input、本地 VLM 看 backend</td>
      </tr>
      <tr>
          <td>Ollama</td>
          <td>Vision 支援部分模型（如 llava、gemma3-vision）</td>
      </tr>
      <tr>
          <td>llama.cpp</td>
          <td>部分模型支援（依 release）</td>
      </tr>
      <tr>
          <td>LM Studio</td>
          <td>部分 GUI 支援</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>事實查核註</strong>：IDE 跟推論伺服器對 VLM 的支援度 2026/5 仍在快速演化、引用前以各工具當前 release notes 為準。雲端 IDE（Cursor / Claude Code）的 vision 支援多半成熟、本地 IDE plugin + 本地 VLM 的組合仍在追趕。</p></blockquote>
<h3 id="trigger-訊號">Trigger 訊號</h3>
<p>判斷「該升級到 vision-native coding workflow」的訊號：</p>
<ol>
<li>Continue.dev / Ollama release notes 出現「first-class vision support」「image input now stable」</li>
<li>本地 VLM 在自己工作流的 use case（如 debug UI）品質追上 2024 年的 Claude 3 vision</li>
<li>同事 / 社群開始日常用截圖 + IDE 互動</li>
<li>自己工作流出現「人類花時間文字描述視覺問題給 LLM」的 friction</li>
</ol>
<p>任一觸發 → 開始 explore 本地 vision plugin、設配置。</p>
<h2 id="multimodal-rag-跟-vlm-的關係">Multimodal RAG 跟 VLM 的關係</h2>
<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG</a> 章節覆蓋了 text-based retrieval。Multimodal RAG 加上 vision 維度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">傳統 RAG：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  text query → text embedding → 檢索 text docs
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">Multimodal RAG：
</span></span><span class="line"><span class="ln">5</span><span class="cl">  text or image query → multimodal embedding → 檢索 text + image
</span></span><span class="line"><span class="ln">6</span><span class="cl">  例：「跟這張 UI 截圖相似的設計」、「跟這個 error 一樣的 issue ticket」</span></span></code></pre></div><p>Multimodal RAG 的 embedding 通常用 <a href="/blog/llm/knowledge-cards/clip/" data-link-title="CLIP" data-link-desc="OpenAI 2021 提出的 contrastive image-text pretraining、現代 VLM 的 vision encoder 大多衍生自它">CLIP</a>-style 模型（跟 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model internals</a> 介紹的 text-only embedding model 訓練 paradigm 同源、都用 contrastive learning、但同時 embed 圖跟文字到共享空間）。</p>
<p>寫 code 場景的潛在應用：</p>
<ul>
<li><strong>設計系統 RAG</strong>：把過去設計稿、UI screenshots 都 embed 起來、給新 task 截圖時 retrieve 相似 case</li>
<li><strong>Bug screenshot 知識庫</strong>：歷史 bug 截圖 + 解法 embed、給新 bug 截圖時找相似 case</li>
<li><strong>Architecture 圖譜</strong>：架構圖 retrieve、給新需求找對應的舊架構</li>
</ul>
<p>目前實用度比 text RAG 低、需要的 infrastructure（multimodal embedding service、image-friendly vector DB）尚不普及。</p>
<p>Tripwire（什麼時候值得評估 multimodal RAG）：</p>
<ol>
<li>推論伺服器（Ollama / llama.cpp）的 release notes 出現 first-class CLIP-style embedding 支援</li>
<li>Vector DB（Qdrant / Milvus / Weaviate）的 image embedding 索引從 experimental 變 stable</li>
<li>自己工作流累積 1000+ 截圖（設計稿 / UI bug / 架構圖）、且 text 描述 retrieval 已撞天花板</li>
<li>Team 開始把「跟 X 類似的舊 case」當常規查詢、不只是「找特定關鍵字」</li>
</ol>
<p>任一觸發 → 評估 multimodal RAG；都沒觸發 → 仍用 text RAG。</p>
<h2 id="不在本章內的主題">不在本章內的主題</h2>
<ol>
<li><strong>影片理解 / video LLM</strong>：寫 code 場景用得到的相對少（screen recording 倒是會用、但實作上多半切 keyframe 變多張圖）、見專門 video LLM 教材</li>
<li><strong>Vision-only model（不含語言）</strong>：OCR、object detection、image classification 等專門 vision 任務、用 specialised 工具更好</li>
<li><strong>生圖</strong>（Diffusion 等）：跟 VLM 完全不同 paradigm、見 <a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion 卡片</a> 跟 ComfyUI 教材</li>
<li><strong>3D / point cloud</strong>：CAD / 3D 模型理解、目前 VLM 支援少、屬研究階段</li>
<li><strong>具體 IDE plugin 設定</strong>：Continue.dev 的 image upload UI、Ollama 的 vision API 細節等、隨版本變、見各工具當前文件</li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Coding 場景 vision 的 use case 分類（UI debug、mockup → code、OCR 等）</li>
<li>本地 vs 雲端的分流邏輯（沿用 cross-cloud-local-data-boundary 框架）</li>
<li>Image token 跟 context budget 的關係</li>
<li>VLM 的失敗模式分類（細節、OCR、空間推理、DPI）</li>
<li>Multimodal RAG 的概念框架</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體本地 VLM 模型（Qwen2.5-VL → 2.6 → &hellip;、Llama 3.2 → 4 → &hellip;）</li>
<li>推論伺服器對 VLM 的支援度（llama.cpp、Ollama、LM Studio 都在追）</li>
<li>IDE plugin 的 vision integration（Continue.dev、Cursor、Aider 都在演化）</li>
<li>Vision encoder 設計（CLIP → SigLIP → DFN → &hellip;）</li>
<li>雲端跟本地的品質差距（會持續縮小）</li>
</ul>
<h2 id="跟其他章節的關係">跟其他章節的關係</h2>
<p>本章是 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> / <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use</a> / <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model</a> 在 vision 延伸的補完；隱私 / 跨雲端分流邏輯沿用 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4</a>；本地 VLM 配 IDE 的 hands-on 屬於 <a href="/blog/llm/01-local-llm-services/hands-on/" data-link-title="Hands-on：本地 AI 工具實作筆記" data-link-desc="Ollama / ComfyUI / Whisper / Piper TTS：實際安裝、驗證、跑通的紀錄。隨工具版本演化、跟 1.x 原理章節互補。">模組一 hands-on</a> 範圍、視推論伺服器支援度成熟度補。</p>
]]></content:encoded></item><item><title>4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨</title><link>https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &amp;#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model&lt;/a> 寫的是「RAG 在做什麼、embedding 怎麼選」、預設「有 backend server」可跑 embedding 跟 LLM。但實際大量場景是&lt;strong>沒 backend&lt;/strong> — 個人 blog（Hugo / Jekyll / Astro）想加智能搜尋、docs site 想做 LLM 對話、demo 想離線跑。本章把這條「靜態 / serverless RAG」路線拆成四個方案、配合靜態場景&lt;strong>特有的資安議題&lt;/strong>（這些議題模組六沒覆蓋、屬本章新增）。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>區分四種 RAG deployment 方案（純前端 / edge serverless / RAG SaaS / 純文字 search）。&lt;/li>
&lt;li>對自己場景判斷該選哪個方案、看資料量 / 隱私 / 預算。&lt;/li>
&lt;li>認識靜態場景特有的資安議題：API key 暴露、CORS、abuse、第三方 SaaS 供應鏈、client-side 模型完整性。&lt;/li>
&lt;li>知道哪些資安議題在 &lt;a href="https://tarrragon.github.io/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六&lt;/a> 已覆蓋、哪些是本章獨有。&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼這個議題重要">為什麼這個議題重要&lt;/h2>
&lt;p>傳統 RAG 教材預設架構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">User → backend server → embedding API → vector DB → LLM API → response&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要 backend 可執行 server-side code、藏 API key、控制 rate limit。但個人開發者場景常見的 deployment：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Backend？&lt;/th>
 &lt;th>部署方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>個人 Hugo blog&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>GitHub Pages / Cloudflare Pages&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開源專案 docs site&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>GitHub Pages / Netlify / Vercel&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品 landing page&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>CDN + S3&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Static-export Next.js / Astro&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這些場景跟「個人 dev 跑本地 LLM」並列、是教材的合理覆蓋面。&lt;/p>
&lt;h2 id="四種-deployment-方案總覽">四種 deployment 方案總覽&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"> embedding vector LLM call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 搜尋 DB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">方案 1 純前端 browser browser browser（WebLLM）或 user-key 直 call
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">方案 2 edge serverless edge fn edge DB edge fn → LLM API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">方案 3 RAG SaaS SaaS SaaS SaaS（或自 call）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">方案 4 純文字 search N/A static idx N/A（不是 RAG）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>四方案快速對比：&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> 跟 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model</a> 寫的是「RAG 在做什麼、embedding 怎麼選」、預設「有 backend server」可跑 embedding 跟 LLM。但實際大量場景是<strong>沒 backend</strong> — 個人 blog（Hugo / Jekyll / Astro）想加智能搜尋、docs site 想做 LLM 對話、demo 想離線跑。本章把這條「靜態 / serverless RAG」路線拆成四個方案、配合靜態場景<strong>特有的資安議題</strong>（這些議題模組六沒覆蓋、屬本章新增）。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>區分四種 RAG deployment 方案（純前端 / edge serverless / RAG SaaS / 純文字 search）。</li>
<li>對自己場景判斷該選哪個方案、看資料量 / 隱私 / 預算。</li>
<li>認識靜態場景特有的資安議題：API key 暴露、CORS、abuse、第三方 SaaS 供應鏈、client-side 模型完整性。</li>
<li>知道哪些資安議題在 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 已覆蓋、哪些是本章獨有。</li>
</ol>
<h2 id="為什麼這個議題重要">為什麼這個議題重要</h2>
<p>傳統 RAG 教材預設架構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">User → backend server → embedding API → vector DB → LLM API → response</span></span></code></pre></div><p>需要 backend 可執行 server-side code、藏 API key、控制 rate limit。但個人開發者場景常見的 deployment：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Backend？</th>
          <th>部署方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>個人 Hugo blog</td>
          <td>無</td>
          <td>GitHub Pages / Cloudflare Pages</td>
      </tr>
      <tr>
          <td>開源專案 docs site</td>
          <td>無</td>
          <td>GitHub Pages / Netlify / Vercel</td>
      </tr>
      <tr>
          <td>商品 landing page</td>
          <td>無</td>
          <td>CDN + S3</td>
      </tr>
      <tr>
          <td>Static-export Next.js / Astro</td>
          <td>無</td>
          <td>同上</td>
      </tr>
  </tbody>
</table>
<p>這些場景跟「個人 dev 跑本地 LLM」並列、是教材的合理覆蓋面。</p>
<h2 id="四種-deployment-方案總覽">四種 deployment 方案總覽</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                          embedding   vector       LLM call
</span></span><span class="line"><span class="ln">2</span><span class="cl">                          搜尋          DB
</span></span><span class="line"><span class="ln">3</span><span class="cl">方案 1 純前端            browser       browser     browser（WebLLM）或 user-key 直 call
</span></span><span class="line"><span class="ln">4</span><span class="cl">方案 2 edge serverless   edge fn       edge DB     edge fn → LLM API
</span></span><span class="line"><span class="ln">5</span><span class="cl">方案 3 RAG SaaS          SaaS          SaaS        SaaS（或自 call）
</span></span><span class="line"><span class="ln">6</span><span class="cl">方案 4 純文字 search     N/A           static idx  N/A（不是 RAG）</span></span></code></pre></div><p>四方案快速對比：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>1 純前端</th>
          <th>2 edge serverless</th>
          <th>3 SaaS</th>
          <th>4 純文字 search</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>是否「真 RAG」</td>
          <td>是</td>
          <td>是</td>
          <td>是</td>
          <td><strong>否</strong>（無 LLM）</td>
      </tr>
      <tr>
          <td>隱私</td>
          <td>最強（不離 browser）</td>
          <td>中（信 edge provider）</td>
          <td>弱（信 SaaS）</td>
          <td>最強</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>完全 zero（build 一次）</td>
          <td>每 query 付 edge + LLM</td>
          <td>免費 tier / 按量計費</td>
          <td>Zero</td>
      </tr>
      <tr>
          <td>規模上限</td>
          <td>&lt; 10K chunks</td>
          <td>1M+</td>
          <td>視服務</td>
          <td>視工具</td>
      </tr>
      <tr>
          <td>開發複雜度</td>
          <td>中（要 build pipeline）</td>
          <td>中高（要寫 edge fn）</td>
          <td>低（API 直接用）</td>
          <td>低</td>
      </tr>
      <tr>
          <td>主要資安議題</td>
          <td>模型完整性、user-key 暴露</td>
          <td>edge provider 信任</td>
          <td>SaaS 信任 + 供應鏈</td>
          <td>較少（無 LLM）</td>
      </tr>
  </tbody>
</table>
<h2 id="方案-1純前端-ragbrowser-side-everything">方案 1：純前端 RAG（browser-side everything）</h2>
<p>整個 RAG pipeline 都跑在使用者瀏覽器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Build time（Hugo build / CI pipeline）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  content/*.md
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    ↓ 抽段、chunk
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓ embedding model（Node.js 版 sentence-transformers）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  embeddings.json（每個 chunk 一個 vector）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓ 跟 HTML 一起 deploy
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Runtime（user browser）：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  User query
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ↓ load @xenova/transformers + embeddings.json（首訪載 ~50MB）
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ↓ embed query in browser
</span></span><span class="line"><span class="ln">12</span><span class="cl">    ↓ cosine similarity vs embeddings.json
</span></span><span class="line"><span class="ln">13</span><span class="cl">  top-K chunks
</span></span><span class="line"><span class="ln">14</span><span class="cl">    ↓ LLM call（兩條子路線、見下）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  Response in browser</span></span></code></pre></div><p>LLM 的兩條子路線：</p>
<table>
  <thead>
      <tr>
          <th>子路線</th>
          <th>機制</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong><a href="/blog/llm/knowledge-cards/client-side-llm/" data-link-title="Client-Side LLM / Embedding" data-link-desc="在 browser 內直接跑 LLM 或 embedding model 的 paradigm、靜態網站做 RAG 的關鍵基底">Client-side LLM</a></strong></td>
          <td>WebLLM / wllama 跑 &lt; 4B model</td>
          <td>完全離線、首訪載 1-3GB 模型、隱私最強</td>
      </tr>
      <tr>
          <td><strong>User 自帶 API key</strong></td>
          <td>前端讀 localStorage 的 key、直 call API</td>
          <td>高品質（雲端旗艦）、key 暴露、需要使用者授信</td>
      </tr>
  </tbody>
</table>
<p>實作概要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Build time（Node.js script）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">npx @xenova/transformers-cli embed content/*.md &gt; static/embeddings.json
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Frontend（簡化版）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">import <span class="o">{</span> pipeline <span class="o">}</span> from <span class="s1">&#39;@xenova/transformers&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">const <span class="nv">embedder</span> <span class="o">=</span> await pipeline<span class="o">(</span><span class="s1">&#39;feature-extraction&#39;</span>, <span class="s1">&#39;nomic-embed-text-v1.5&#39;</span><span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">const <span class="nv">queryVec</span> <span class="o">=</span> await embedder<span class="o">(</span>userQuery, <span class="o">{</span> pooling: <span class="s1">&#39;mean&#39;</span> <span class="o">})</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">const <span class="nv">ranked</span> <span class="o">=</span> embeddings.map<span class="o">(</span><span class="nv">c</span> <span class="o">=</span>&gt; <span class="o">({</span> ...c, score: cosineSim<span class="o">(</span>c.vec, queryVec.data<span class="o">)</span> <span class="o">}))</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">                          .sort<span class="o">((</span>a,b<span class="o">)</span> <span class="o">=</span>&gt; b.score - a.score<span class="o">)</span>.slice<span class="o">(</span>0, 5<span class="o">)</span><span class="p">;</span></span></span></code></pre></div><p>規模上限：</p>
<ul>
<li>&lt; 1000 chunks：embeddings.json ~ 4MB（1024-dim float32）、輕鬆</li>
<li>1K-10K：~40MB、首訪載入慢但可接受</li>
<li>10K+：純前端開始勉強、考慮方案 2</li>
</ul>
<p><strong>適合場景</strong>：個人 blog、docs site、demo、隱私敏感、規模 &lt; 10K chunks。</p>
<h2 id="方案-2靜態--edge-serverless">方案 2：靜態 + edge serverless</h2>
<p>「靜態主站 + edge function 處理動態請求」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">靜態前端（HTML / JS、Hugo / Astro）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓ fetch /api/rag
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Edge function（Cloudflare Workers / Vercel Edge / Netlify Functions）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Embedding API（OpenAI / Voyage）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Vector DB（Cloudflare Vectorize / Pinecone / Turso vector / Upstash Vector）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">LLM API（OpenAI / Anthropic / Cloudflare AI Gateway）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ↓ response
</span></span><span class="line"><span class="ln">11</span><span class="cl">靜態前端</span></span></code></pre></div><p>對使用者體感跟「有 backend」一樣、但你不用維護 server / 不用 sysadmin。</p>
<p>主流元件搭配：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>Cloudflare 全家桶</th>
          <th>Vercel / 其他</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Edge runtime</td>
          <td>Workers</td>
          <td>Vercel Edge / Netlify Functions</td>
      </tr>
      <tr>
          <td>Vector DB</td>
          <td>Cloudflare Vectorize</td>
          <td>Pinecone / Turso / Upstash</td>
      </tr>
      <tr>
          <td>Embedding</td>
          <td>Workers AI 內建模型 / OpenAI</td>
          <td>OpenAI / Voyage</td>
      </tr>
      <tr>
          <td>LLM</td>
          <td>Workers AI / AI Gateway 轉發</td>
          <td>OpenAI / Anthropic</td>
      </tr>
  </tbody>
</table>
<p>關鍵特性：</p>
<ol>
<li><strong>API key 不暴露在 browser</strong>：edge function 內讀環境變數、安全</li>
<li><strong>可加 rate limit</strong>：edge function 內判斷 client IP / user agent、避免 abuse</li>
<li><strong>Build-time index 仍重要</strong>：embedding ingestion 通常在 build 階段、不在 runtime</li>
<li><strong>Edge cold start</strong>：第一次 query latency 略高（~100ms 額外）、後續 hot 路徑快</li>
</ol>
<p><strong>適合場景</strong>：規模 1K-100K chunks、想保留近 backend 體驗、可接受少量 cost。這條路線一旦升級到有 backend 的 vector DB、storage 選型（index 結構、維度、成本）就回到 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a> 的判讀。</p>
<h2 id="方案-3靜態--rag-saas">方案 3：靜態 + RAG SaaS</h2>
<p>把整個 RAG stack 外包：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>角色</th>
          <th>免費 tier 上限</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Algolia</td>
          <td>搜尋 + 向量檢索一條龍、build time 同步</td>
          <td>10K records、10K search / month</td>
      </tr>
      <tr>
          <td>Pinecone Cloud</td>
          <td>純 vector DB、自己 call embedding + LLM</td>
          <td>100K vectors（starter）</td>
      </tr>
      <tr>
          <td>Weaviate Cloud</td>
          <td>同上、hybrid search 內建</td>
          <td>14 天 trial</td>
      </tr>
      <tr>
          <td>MeiliSearch Cloud</td>
          <td>BM25 + vector hybrid</td>
          <td>試用</td>
      </tr>
  </tbody>
</table>
<p>API key 設計：</p>
<ul>
<li><strong>search-only key</strong>：只能查詢、無寫入權限、<strong>可安全暴露在 browser</strong>（這是設計支援的）</li>
<li><strong>admin key</strong>：build time CI 用、有寫入權限、必須藏 server-side</li>
</ul>
<p>前端範例（Algolia）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="nx">algoliasearch</span><span class="p">(</span><span class="s1">&#39;APP_ID&#39;</span><span class="p">,</span> <span class="s1">&#39;SEARCH_ONLY_KEY&#39;</span><span class="p">);</span>  <span class="c1">// 可公開
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nx">initIndex</span><span class="p">(</span><span class="s1">&#39;my-blog&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">hits</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">index</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">userQuery</span><span class="p">,</span> <span class="p">{</span> <span class="nx">hitsPerPage</span><span class="o">:</span> <span class="mi">5</span> <span class="p">});</span></span></span></code></pre></div><p><strong>適合場景</strong>：想最快上線、不在乎 vendor lock-in、規模中小、retrieval-only（不需要 LLM 對話）。</p>
<h2 id="方案-4靜態--純文字-search不是真-rag">方案 4：靜態 + 純文字 search（不是真 RAG）</h2>
<p>Pagefind、Stork、lunr.js、FlexSearch — build time 產靜態 search index、純前端查詢。</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pagefind</td>
          <td>static-first、自動 chunking、CJK 友善</td>
      </tr>
      <tr>
          <td>Stork</td>
          <td>Rust 寫的 keyword search、輕量</td>
      </tr>
      <tr>
          <td>lunr.js</td>
          <td>純 JS、tf-idf BM25 風格</td>
      </tr>
      <tr>
          <td>FlexSearch</td>
          <td>同上、體積更小</td>
      </tr>
  </tbody>
</table>
<p><strong>這不是 RAG</strong>：</p>
<ol>
<li><strong>無 embedding similarity</strong>：keyword / fuzzy match、不是語意相似</li>
<li><strong>無 LLM augmentation</strong>：只列文章連結、不生成回答</li>
<li><strong>算 retrieval 的「字面」變體</strong>：見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG</a> 的「語意 vs 字面」段</li>
</ol>
<p><strong>適合場景</strong>：blog 內搜尋只需要找文章、不需要對話、極致 zero-cost。</p>
<h2 id="規模門檻什麼時候該升級方案">規模門檻：什麼時候該升級方案</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">&lt; 1K chunks                    → 方案 1 純前端、最簡單
</span></span><span class="line"><span class="ln">2</span><span class="cl">1K - 10K chunks                → 方案 1 或 方案 4
</span></span><span class="line"><span class="ln">3</span><span class="cl">10K - 100K chunks              → 方案 2 edge serverless
</span></span><span class="line"><span class="ln">4</span><span class="cl">100K+ chunks                   → 完整 backend RAG（不再是「靜態」場景）
</span></span><span class="line"><span class="ln">5</span><span class="cl">非 RAG、只要找文章             → 方案 4（Pagefind 等）</span></span></code></pre></div><h2 id="靜態場景特有的資安議題">靜態場景特有的資安議題</h2>
<p>本章節最重要的部分。靜態 / serverless RAG 有些議題模組六沒覆蓋、要在本章補。</p>
<h3 id="1-api-key-暴露--靜態場景的根本問題">1. API key 暴露 — 靜態場景的根本問題</h3>
<p><strong>核心衝突</strong>：靜態網站沒 server-side runtime、藏不了 secret。任何寫在前端 JS / 編進 HTML 的東西、使用者按 F12 都看得到。</p>
<p>對應到 RAG：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>能否前端持有 key</th>
          <th>緩解</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Embedding API（生成方）</td>
          <td>否（admin key 不該暴露）</td>
          <td>build time 用、不放前端</td>
      </tr>
      <tr>
          <td>LLM API（生成方）</td>
          <td>否</td>
          <td>改方案 2 用 edge、或讓使用者自帶 key</td>
      </tr>
      <tr>
          <td>Vector DB（read）</td>
          <td><strong>可</strong>（search-only key 設計支援）</td>
          <td>API 設計時就分權、search-only 可公開</td>
      </tr>
      <tr>
          <td>完整 LLM 跑在前端</td>
          <td>N/A（無 server-side key）</td>
          <td>方案 1 的 Client-side LLM 子路線</td>
      </tr>
  </tbody>
</table>
<p>如果要 LLM 對話功能、三條合法路線：</p>
<ol>
<li><strong>使用者自帶 API key</strong>（如 Anthropic / OpenAI）、存 localStorage、前端直接 call API — 適合 power user、需要使用者授信</li>
<li><strong>WebLLM / wllama 跑前端 LLM</strong> — 模型在 browser、不需 server-side key</li>
<li><strong>方案 2 edge serverless</strong> — key 藏在 edge function、就不是純靜態了</li>
</ol>
<p>寫死 API key 在前端 JS 等於把 key 公開、會被 scraper 撿走燒爆 quota — 這是 <strong>anti-pattern</strong>、跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端 / 本地資料邊界</a> 提到「API key 寫死 config」的延伸版（前端更嚴重、所有訪客都看得到）。</p>
<h3 id="2-user-query-隱私">2. User query 隱私</h3>
<p>靜態場景的 query 走向：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>Query 走向</th>
          <th>誰能看到</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 純前端 + WebLLM</td>
          <td>從不離 browser</td>
          <td>只有使用者本人</td>
      </tr>
      <tr>
          <td>1 + user API key</td>
          <td>Browser → 雲端 vendor</td>
          <td>該 vendor（依政策）</td>
      </tr>
      <tr>
          <td>2 edge serverless</td>
          <td>Browser → edge → 雲端 API</td>
          <td>Edge provider + LLM vendor</td>
      </tr>
      <tr>
          <td>3 SaaS</td>
          <td>Browser → SaaS</td>
          <td>SaaS provider</td>
      </tr>
  </tbody>
</table>
<p>對應 framing 跟 <a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流</a> 同源 — 但靜態場景的特殊性是「<strong>前端直接出去</strong>」、不像 backend 場景可以加一層中介控制。</p>
<p>特別注意：</p>
<ol>
<li><strong>方案 3 SaaS 的 query 隱私</strong>：Algolia / Pinecone 都會 log query、依政策可能用於改進服務；對隱私敏感場景不適合</li>
<li><strong>Edge provider 的 region</strong>：Cloudflare Workers 的 edge node 可能在跟使用者不同 region 處理、跨境資料法規（GDPR 等）要考慮</li>
<li><strong>Browser extension 偷 query</strong>：使用者裝的 plugin 可能 access 整個頁面、包含 RAG 介面內的 query</li>
</ol>
<h3 id="3-cors--同源策略--browser-特有的安全模型">3. CORS / 同源策略 — Browser 特有的安全模型</h3>
<p>靜態前端 call 任意 API 會撞 CORS（Cross-Origin Resource Sharing）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">靜態網站：https://my-blog.com
</span></span><span class="line"><span class="ln">2</span><span class="cl">要 call：https://api.openai.com/v1/...
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">4</span><span class="cl">Browser 檢查 OpenAI 是否在 Access-Control-Allow-Origin 含 my-blog.com
</span></span><span class="line"><span class="ln">5</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">6</span><span class="cl">OpenAI 預設允許所有 origin（為了讓前端 SDK 能用）→ 通過
</span></span><span class="line"><span class="ln">7</span><span class="cl">某些 API（Anthropic 早期版本）不允許 browser 直 call → 失敗、必須走 edge</span></span></code></pre></div><p>判讀：</p>
<ul>
<li><strong>能在 browser 直 call 的 API</strong>：OpenAI、Voyage、Algolia（search-only）等明確設計 browser-friendly 的服務</li>
<li><strong>不能 browser 直 call、要 edge proxy</strong>：許多企業 LLM API、私有 vector DB、需要 server-only credentials 的服務</li>
</ul>
<p>CORS 不是「資安漏洞」、是 browser 對「JS 從一個網站 call 另一個網站」的設計約束、用來保護使用者。要繞 CORS 要嗎服務商配合（設 ACAO）、要嗎用 edge function proxy。</p>
<h3 id="4-第三方-saas-信任--跟-60-同源對象換">4. 第三方 SaaS 信任 — 跟 6.0 同源、對象換</h3>
<p><a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0 模型供應鏈與信任邊界</a> 處理的是「<strong>模型權重的信任</strong>」。靜態 RAG SaaS（Algolia / Pinecone / Weaviate Cloud）引入另一條供應鏈：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">模型供應鏈（6.0 覆蓋）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  原作者 → quantizer → registry → 你機器
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">RAG SaaS 供應鏈（本章新增）：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  你的 content → SaaS embedding service → SaaS vector DB → SaaS retrieval
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    └──────── 全程在 SaaS 內、你信任 SaaS 沒做以下事 ────────┘
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">              - 把你 index 用於訓練他們自己的模型
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">              - 把你 query log 賣給第三方
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">              - 沒做適當 isolation（你跟其他客戶的資料）
</span></span><span class="line"><span class="ln">10</span><span class="cl">              - 沒處理好 supply chain（他們用的 base embedding model）</span></span></code></pre></div><p>判讀類似 <a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 物理 vs 合約保證</a>：本地方案是物理保證（資料不離 browser）、SaaS 方案是合約保證（信 SaaS 的 ToS）。</p>
<h3 id="5-rate-limit--abuse--前端被-scrape-後濫用">5. Rate limit / abuse — 前端被 scrape 後濫用</h3>
<p>靜態 RAG 的特殊 abuse 路徑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">攻擊者掃到你的 demo blog
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ 找到前端載入的 embedding endpoint / LLM endpoint
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ 直接從攻擊者 server 重複 call（不經 browser）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ 你的 LLM API quota 燒爆 / SaaS 配額耗光</span></span></code></pre></div><p>緩解：</p>
<ol>
<li><strong>方案 2 edge</strong> + 加 rate limit by IP / token bucket：edge function 內 reject 過量請求</li>
<li><strong>方案 1 純前端 + WebLLM</strong>：根本沒 server-side endpoint 可被 abuse、最安全</li>
<li><strong>方案 3 SaaS</strong> + 用 search-only key 並設 query 上限：SaaS 通常內建 quota</li>
<li><strong>CAPTCHA / Turnstile</strong>：邊緣防護</li>
</ol>
<p>絕對不該做：把 OpenAI / Anthropic API key 寫在前端 JS、想用 rate limit 阻擋 — 攻擊者拿到 key 後不會經過你的 rate limit。</p>
<h3 id="6-client-side-llm-的模型完整性">6. Client-side LLM 的模型完整性</h3>
<p><a href="/blog/llm/knowledge-cards/client-side-llm/" data-link-title="Client-Side LLM / Embedding" data-link-desc="在 browser 內直接跑 LLM 或 embedding model 的 paradigm、靜態網站做 RAG 的關鍵基底">Client-side LLM</a> 把幾 GB 模型權重下載到 browser、引入新的供應鏈面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">你的網站
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ &lt;script&gt; 載入 WebLLM runtime（CDN）
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ runtime 從 HuggingFace CDN 抓 model weights
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ 使用者 browser 跑模型</span></span></code></pre></div><p>風險：</p>
<ol>
<li><strong>CDN 被 compromise</strong>：WebLLM runtime 或 model weights 在 CDN 上被換、注入 backdoor</li>
<li><strong>HTTPS 之外無額外驗證</strong>：不像本地 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">GGUF + hash 比對</a>、browser 載模型純信 CDN + HTTPS</li>
<li><strong>使用者本機沒 inventory 記錄</strong>：跟 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a> 推薦的「下載後記 hash」對比、browser 沒這機制</li>
</ol>
<p>緩解：</p>
<ol>
<li><strong>Subresource Integrity（SRI）</strong>：HTML 的 <code>&lt;script integrity=&quot;sha384-...&quot;&gt;</code> 屬性、browser 自動驗證 hash</li>
<li><strong>CSP（Content Security Policy）</strong>：限制可載入的 script / image source、減少 supply chain attack 面</li>
<li><strong>挑大廠 CDN</strong>：Cloudflare / jsdelivr / unpkg 等被 compromise 的歷史紀錄較少</li>
</ol>
<p>跟 <a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a> 的關係：6.0 講「本機跑的 GGUF 模型供應鏈」、本章補「browser 跑的 client-side 模型供應鏈」— 兩種場景的 framing 一致、但具體威脅面跟工具不同。</p>
<h2 id="跟模組六的-routing">跟模組六的 routing</h2>
<p>本章資安段跟既有 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 的對應：</p>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>06 對應章節</th>
          <th>本章補的角度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模型 / 供應鏈信任</td>
          <td><a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">6.0</a></td>
          <td>client-side 模型分發新形態</td>
      </tr>
      <tr>
          <td>Server 綁定</td>
          <td><a href="/blog/llm/06-security/inference-server-binding/" data-link-title="6.1 推論伺服器的綁定與暴露範圍" data-link-desc="個人 dev 場景下 llama-server / Ollama / LM Studio 的 bind address 判讀：127.0.0.1 vs LAN vs 反代、預設安全、誤開放給內網的後果">6.1</a></td>
          <td>靜態場景無 server、議題消失</td>
      </tr>
      <tr>
          <td>Tool use 權限</td>
          <td><a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2</a></td>
          <td>browser-side tool use（少數場景）</td>
      </tr>
      <tr>
          <td>Prompt injection</td>
          <td><a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">6.3</a></td>
          <td>靜態 RAG 仍適用、source 變 web fetched</td>
      </tr>
      <tr>
          <td>跨雲端 / 本地資料邊界</td>
          <td><a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4</a></td>
          <td>靜態場景 query 走向跟 backend 場景不同</td>
      </tr>
      <tr>
          <td>Production routing</td>
          <td><a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">6.5</a></td>
          <td>從個人靜態 RAG 升級到 production</td>
      </tr>
      <tr>
          <td><strong>API key 暴露 / browser</strong></td>
          <td>（無）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
      <tr>
          <td><strong>CORS / 同源策略</strong></td>
          <td>（無）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
      <tr>
          <td><strong>靜態場景 abuse / rate limit</strong></td>
          <td>（無、跟 6.1 server 議題不同）</td>
          <td><strong>本章獨有</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="判讀流程">判讀流程</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">你的場景：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 有 backend？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  │    └─ 是 → 用 4.0 RAG + 4.8 embedding 主章節、本章不適用
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  │    └─ 否 → 繼續
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 規模？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │    ├─ &lt; 1K chunks → 方案 1 純前端
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │    ├─ 1K-10K → 方案 1（embeddings.json ~ 40MB 仍可接受）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  │    ├─ 10K-100K → 方案 2 edge serverless
</span></span><span class="line"><span class="ln">10</span><span class="cl">  │    └─ 100K+ → 不再是靜態場景、回 backend
</span></span><span class="line"><span class="ln">11</span><span class="cl">  │
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 需要 LLM 對話、不只 retrieval？
</span></span><span class="line"><span class="ln">13</span><span class="cl">  │    ├─ 是 + 隱私第一 → 方案 1 + WebLLM
</span></span><span class="line"><span class="ln">14</span><span class="cl">  │    ├─ 是 + 品質第一 → 方案 1 + user-key 或 方案 2
</span></span><span class="line"><span class="ln">15</span><span class="cl">  │    └─ 否（只要找文章） → 方案 4 純文字 search
</span></span><span class="line"><span class="ln">16</span><span class="cl">  │
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ 預算 / vendor lock-in 容忍度？
</span></span><span class="line"><span class="ln">18</span><span class="cl">       ├─ 完全 zero-cost、無 vendor → 方案 1 純前端
</span></span><span class="line"><span class="ln">19</span><span class="cl">       ├─ 接受少量 cost、不想自己寫太多 → 方案 3 SaaS
</span></span><span class="line"><span class="ln">20</span><span class="cl">       └─ 接受少量 cost、想自己控 → 方案 2 edge</span></span></code></pre></div><h2 id="不在本章內的主題">不在本章內的主題</h2>
<ol>
<li><strong>完整 backend RAG</strong>：see <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> 跟 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 embedding model</a></li>
<li><strong>具體 SaaS API 教學</strong>：Algolia / Pinecone 等 API 細節隨版本變、見各 SaaS 文件</li>
<li><strong>WebGPU 內部細節</strong>：GPU shader、WebGPU API 設計屬 web platform 議題、不在 LLM 教材範圍</li>
<li><strong>Production 多租戶 RAG 服務</strong>：屬 backend/07、本章 framing 是「個人 / 小團隊靜態網站」</li>
<li><strong>企業合規 deployment</strong>：HIPAA / GDPR / SOC 2 跟具體 SaaS / cloud provider 強相關、見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend/07 合規卡片</a> 跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端</a></li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>四方案分類（純前端 / edge / SaaS / 純文字 search）</li>
<li>「靜態場景藏不了 secret」這個根本特性</li>
<li>API key 暴露 / CORS / abuse / 供應鏈 / 模型完整性 五大資安議題分類</li>
<li>跟 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 的 routing 關係</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 SaaS / edge provider（Cloudflare Vectorize / Pinecone / Algolia 等持續演化）</li>
<li>Client-side LLM runtime（WebLLM / wllama / transformers.js）的能力上限</li>
<li>WebGPU 支援度跟 browser 標準</li>
<li>哪些 LLM vendor 允許 browser 直 call（CORS 政策會變）</li>
<li>純文字 search 工具（Pagefind 等持續改進）</li>
</ul>
<h2 id="下一步">下一步</h2>
<p>本章是 <a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四</a> 最後一章。讀完整個模組四、完整覆蓋 LLM 作為系統元件的設計取捨。下一步可進入 <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五 PC 獨立 GPU</a> 或 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六 安全</a> 補本地 dev 視角的安全議題。</p>
]]></content:encoded></item><item><title>4.17 Coding agent harness：scaffold / context engineering / subagent</title><link>https://tarrragon.github.io/blog/llm/04-applications/coding-agent-harness/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/coding-agent-harness/</guid><description>&lt;p>教材整體 framing 是「LLM 寫 code 工程實務」、模組四前面 11 章寫的是&lt;strong>通用 LLM 應用層原理&lt;/strong>（RAG / tool use / agent / VLM 等）。本章補上「coding agent 怎麼設計」這層 — 為什麼 Claude Code / Cursor / Aider / Codex 這類工具長那樣、scaffold 跟 harness 怎麼分、context budget 怎麼配。本章把這些設計取捨從特定產品抽出來、寫成跨工具世代不變的工程原理。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/scaffold-vs-harness/" data-link-title="Scaffold vs Harness" data-link-desc="Coding agent 的兩個工程層次：scaffold 是建構時靜態結構、harness 是 runtime 的 tool dispatch &amp;#43; context management &amp;#43; safety">scaffold vs harness&lt;/a> 分層拆解任何 coding agent。&lt;/li>
&lt;li>對自己工作流計算 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-budget/" data-link-title="Context Budget" data-link-desc="Coding agent 的 context window 拆分配額：system prompt &amp;#43; tool schema &amp;#43; history &amp;#43; file content &amp;#43; reasoning &amp;#43; tool result 各佔多少、留多少 margin">context budget&lt;/a>、看到 budget 超標訊號時知道怎麼修。&lt;/li>
&lt;li>判斷何時值得拆 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/subagent/" data-link-title="Subagent" data-link-desc="Coding agent 中把特定責任拆給專門子 agent 的設計模式、各 subagent 有獨立 context、由 main agent 透過 handoff 調度">subagent&lt;/a>、何時用 single agent。&lt;/li>
&lt;li>看 Claude Code / Cursor / Aider 等 coding agent 的設計差異、能對應到本章 framing。&lt;/li>
&lt;/ol>
&lt;h2 id="scaffold-vs-harness-分層">Scaffold vs Harness 分層&lt;/h2>
&lt;p>Coding agent 的內部結構分兩層：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">Scaffold（建構時靜態結構、編譯 / 載入時就決定）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> - System prompt 模板（agent 角色、輸出約束、錯誤處理 policy）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> - Tool schema 註冊（read_file / write_file / run_bash / web_fetch 等 spec）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> - Subagent 拓樸（main agent + 子 agent 關係）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> - Skill / playbook 註冊（特定任務的 known recipe）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> - 安全 policy（permission boundary、要 confirm 的動作清單）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">Harness（runtime 動態運作、每個 query / loop iteration 跑）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> - Tool dispatch（接 LLM tool call、call function、回 result）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> - Context budget 管理（剪裁 history、塞新內容、避免超 budget）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> - Safety / 中斷（confirm UI、permission check、可逆性判斷）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> - Error recovery（tool failed → retry / fallback / escalate）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> - Telemetry（trace / metrics / cost、見 [4.20 OTel tracing](/llm/04-applications/llm-tracing-and-observability/)）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不同 coding agent 的 scaffold / harness 比較：&lt;/p></description><content:encoded><![CDATA[<p>教材整體 framing 是「LLM 寫 code 工程實務」、模組四前面 11 章寫的是<strong>通用 LLM 應用層原理</strong>（RAG / tool use / agent / VLM 等）。本章補上「coding agent 怎麼設計」這層 — 為什麼 Claude Code / Cursor / Aider / Codex 這類工具長那樣、scaffold 跟 harness 怎麼分、context budget 怎麼配。本章把這些設計取捨從特定產品抽出來、寫成跨工具世代不變的工程原理。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>用 <a href="/blog/llm/knowledge-cards/scaffold-vs-harness/" data-link-title="Scaffold vs Harness" data-link-desc="Coding agent 的兩個工程層次：scaffold 是建構時靜態結構、harness 是 runtime 的 tool dispatch &#43; context management &#43; safety">scaffold vs harness</a> 分層拆解任何 coding agent。</li>
<li>對自己工作流計算 <a href="/blog/llm/knowledge-cards/context-budget/" data-link-title="Context Budget" data-link-desc="Coding agent 的 context window 拆分配額：system prompt &#43; tool schema &#43; history &#43; file content &#43; reasoning &#43; tool result 各佔多少、留多少 margin">context budget</a>、看到 budget 超標訊號時知道怎麼修。</li>
<li>判斷何時值得拆 <a href="/blog/llm/knowledge-cards/subagent/" data-link-title="Subagent" data-link-desc="Coding agent 中把特定責任拆給專門子 agent 的設計模式、各 subagent 有獨立 context、由 main agent 透過 handoff 調度">subagent</a>、何時用 single agent。</li>
<li>看 Claude Code / Cursor / Aider 等 coding agent 的設計差異、能對應到本章 framing。</li>
</ol>
<h2 id="scaffold-vs-harness-分層">Scaffold vs Harness 分層</h2>
<p>Coding agent 的內部結構分兩層：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Scaffold（建構時靜態結構、編譯 / 載入時就決定）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  - System prompt 模板（agent 角色、輸出約束、錯誤處理 policy）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  - Tool schema 註冊（read_file / write_file / run_bash / web_fetch 等 spec）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - Subagent 拓樸（main agent + 子 agent 關係）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  - Skill / playbook 註冊（特定任務的 known recipe）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - 安全 policy（permission boundary、要 confirm 的動作清單）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Harness（runtime 動態運作、每個 query / loop iteration 跑）：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - Tool dispatch（接 LLM tool call、call function、回 result）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - Context budget 管理（剪裁 history、塞新內容、避免超 budget）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - Safety / 中斷（confirm UI、permission check、可逆性判斷）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - Error recovery（tool failed → retry / fallback / escalate）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  - Telemetry（trace / metrics / cost、見 [4.20 OTel tracing](/llm/04-applications/llm-tracing-and-observability/)）</span></span></code></pre></div><p>不同 coding agent 的 scaffold / harness 比較：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>Scaffold 特點</th>
          <th>Harness 特點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Code</td>
          <td>Skill registry、subagent system、structured permission</td>
          <td>強 context budget 管理、explicit handoff、trace</td>
      </tr>
      <tr>
          <td>Cursor</td>
          <td>Composer + chat + tab、tool list 較簡</td>
          <td>IDE-integrated、tool dispatch 在 client + server 切</td>
      </tr>
      <tr>
          <td>Aider</td>
          <td>跟 git 緊密、edit-format spec</td>
          <td>Repl-style、自動 commit、線性 loop</td>
      </tr>
      <tr>
          <td>Codex CLI</td>
          <td>跟 OpenAI assistants API 對齊</td>
          <td>Stream-based、tool call 即時執行</td>
      </tr>
      <tr>
          <td>Continue.dev</td>
          <td>Plugin-style、provider 抽象</td>
          <td>較輕量、tool dispatch 在 plugin host</td>
      </tr>
  </tbody>
</table>
<p>關鍵理解：所有 coding agent 都遵循這個 framing、差異在「scaffold 多複雜」「harness 多強」、不是有沒有這兩層。</p>
<h2 id="context-budget-工程實務">Context Budget 工程實務</h2>
<p><a href="/blog/llm/knowledge-cards/context-budget/" data-link-title="Context Budget" data-link-desc="Coding agent 的 context window 拆分配額：system prompt &#43; tool schema &#43; history &#43; file content &#43; reasoning &#43; tool result 各佔多少、留多少 margin">Context budget</a> 是 coding agent harness 的核心責任。實務拆分（以 200K context 模型為例）：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>預算 %</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>System prompt + tool schema</td>
          <td>5-15%</td>
          <td>Agent 角色、輸出約束、tool spec</td>
      </tr>
      <tr>
          <td>Conversation history</td>
          <td>10-30%</td>
          <td>過去回合的 user query + assistant + tool call</td>
      </tr>
      <tr>
          <td>Current task file context</td>
          <td>30-50%</td>
          <td>開啟檔案、grep 結果、@-mention</td>
      </tr>
      <tr>
          <td>Tool result（current step）</td>
          <td>0-20%</td>
          <td>file read / bash output / test result</td>
      </tr>
      <tr>
          <td>Reasoning trace（若 reasoning model）</td>
          <td>0-15%</td>
          <td><code>&lt;think&gt;...&lt;/think&gt;</code> 段</td>
      </tr>
      <tr>
          <td>Margin / safety buffer</td>
          <td>10-20%</td>
          <td>Generation 階段不被 context limit 截斷</td>
      </tr>
  </tbody>
</table>
<p>關鍵 25% 規則：<strong>Scaffold 部分（system prompt + tool schema + conversation history）合計不超過 25% context</strong>。剩 75% 給「當下任務」、避免 <a href="/blog/llm/knowledge-cards/lost-in-the-middle/" data-link-title="Lost in the Middle" data-link-desc="LLM 對 long context 中段內容的 attention / recall 顯著低於開頭與結尾的現象">lost-in-the-middle</a> 把指令吃掉。</p>
<p>超標訊號跟對應策略：</p>
<table>
  <thead>
      <tr>
          <th>超標訊號</th>
          <th>緩解策略</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模型開始忽略 system prompt 指令</td>
          <td>用 <a href="/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">prompt cache</a> 把 system prompt 攤平</td>
      </tr>
      <tr>
          <td>Tool call 重複過去步驟</td>
          <td>History 過長、需要 summarize 舊回合</td>
      </tr>
      <tr>
          <td>回答跟前文重複 / 矛盾</td>
          <td>中段 lost-in-the-middle、reorder 重要內容到末尾</td>
      </tr>
      <tr>
          <td>Generation 被截斷</td>
          <td>Margin 不夠、降低 file content 或 history</td>
      </tr>
      <tr>
          <td>Reasoning trace 截斷</td>
          <td>換更長 context 模型、或拆任務</td>
      </tr>
  </tbody>
</table>
<p>實作概要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">每個 turn 開始時、harness 算：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  available_input = context_window - reserve_margin
</span></span><span class="line"><span class="ln">3</span><span class="cl">  used = len(system + tool_schema + history + new_content)
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">  if used &gt; available_input × 0.75：
</span></span><span class="line"><span class="ln">6</span><span class="cl">    觸發 summarize：把舊 history 壓縮成 1 段摘要
</span></span><span class="line"><span class="ln">7</span><span class="cl">    或觸發 dispatch：交給 subagent 處理特定子任務、回主 agent 時只帶 summary</span></span></code></pre></div><h2 id="subagent-設計">Subagent 設計</h2>
<p><a href="/blog/llm/knowledge-cards/subagent/" data-link-title="Subagent" data-link-desc="Coding agent 中把特定責任拆給專門子 agent 的設計模式、各 subagent 有獨立 context、由 main agent 透過 handoff 調度">Subagent</a> 把單一大 agent 拆成多個專責子 agent、各自有獨立 context。何時用：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>用 subagent？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Single agent context 撐不住任務複雜度</td>
          <td>是</td>
      </tr>
      <tr>
          <td>Specialty 邊界清楚（test / docs / refactor 各自有專家）</td>
          <td>是</td>
      </tr>
      <tr>
          <td>任務簡單（autocomplete、單行修改）</td>
          <td>否</td>
      </tr>
      <tr>
          <td>Specialty 邊界模糊（強行拆增加 handoff overhead）</td>
          <td>否</td>
      </tr>
      <tr>
          <td>本地小模型（&lt; 14B）</td>
          <td>否（handoff 對小模型不穩）</td>
      </tr>
  </tbody>
</table>
<p>主流 subagent 模式：</p>
<h3 id="1-search-subagent">1. Search subagent</h3>
<p><strong>Specialty</strong>：在大 codebase 找相關片段、不污染 main agent context
<strong>Tool</strong>：grep / find / semantic search
<strong>Output</strong>：top-K 相關段落 + 摘要、main agent 不需要看完整 grep 結果</p>
<h3 id="2-test-runner-subagent">2. Test runner subagent</h3>
<p><strong>Specialty</strong>：跑測試、解讀失敗、提出 fix 建議
<strong>Tool</strong>：run_bash（pytest / jest 等）+ read failed test
<strong>Output</strong>：「測試結果 + 失敗根因 + 建議 fix」、不是完整 test log</p>
<h3 id="3-docs-writer-subagent">3. Docs writer subagent</h3>
<p><strong>Specialty</strong>：寫 docstring / README / commit message
<strong>System prompt</strong>：強化「寫作風格、語言、長度」、跟 main coding agent 完全不同的 system prompt
<strong>Output</strong>：寫好的 docs 文字</p>
<h3 id="4-code-review-subagent">4. Code review subagent</h3>
<p><strong>Specialty</strong>：對 PR diff 做 review、檢查 style / bug / security
<strong>Tool</strong>：git diff / grep
<strong>Output</strong>：comments 列表</p>
<h3 id="5-long-running-task-subagent">5. Long-running task subagent</h3>
<p><strong>Specialty</strong>：跑可能持續數分鐘的任務（如 large-scale refactor）、main agent 不阻塞
<strong>Tool</strong>：背景 process management
<strong>Output</strong>：階段性進度回報 + 最終結果</p>
<p>主 agent 對 subagent 的 handoff 設計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">main agent 收到任務
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ 判斷 specialty
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ 用 dispatch_subagent tool 呼叫
</span></span><span class="line"><span class="ln">4</span><span class="cl">   tool spec：{name, task_brief, expected_output_format}
</span></span><span class="line"><span class="ln">5</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">6</span><span class="cl">Subagent 在自己 context 內跑完
</span></span><span class="line"><span class="ln">7</span><span class="cl">   ↓ 回 summary（不是完整 trace）
</span></span><span class="line"><span class="ln">8</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">9</span><span class="cl">main agent 拿到 summary、繼續推進</span></span></code></pre></div><h2 id="跟既有概念的關係">跟既有概念的關係</h2>
<table>
  <thead>
      <tr>
          <th>既有章節</th>
          <th>跟本章的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 Tool use</a></td>
          <td>Tool spec 是 scaffold 的核心、tool dispatch 在 harness</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構</a></td>
          <td>Agent loop 是 harness 的內部執行迴圈</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a></td>
          <td>Function calling / MCP 是 tool 跟 subagent 之間的協議</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 Long context</a></td>
          <td>Context budget 是 long context 的工程實務面</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/prompt-caching-engineering/" data-link-title="4.18 Prompt caching 工程實務：cost / latency 最大槓桿" data-link-desc="Prompt cache 怎麼運作、cache_control 設計、coding agent 跟 long-context 的 cache pattern、anti-pattern 跟 cache miss 訊號">4.18 Prompt caching</a></td>
          <td>是 scaffold 部分（system + tool schema）的 cost / latency 優化</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19 Agent memory</a></td>
          <td>History 跟 long-term memory 是 harness 跟 storage 的界面</td>
      </tr>
  </tbody>
</table>
<h2 id="跟具體-coding-agent-的-mapping">跟具體 coding agent 的 mapping</h2>
<p>讀者實際用 / 想客製某個 coding agent 時、用本章的 framing 拆解：</p>
<h3 id="claude-code">Claude Code</h3>
<p><strong>Scaffold</strong>：CLAUDE.md（system prompt 入口）、Skills registry、SubagentTypes、tool schema
<strong>Harness</strong>：context budget management、Task tool（dispatch subagent）、permission system、trace
<strong>特色</strong>：完整 scaffold-harness 分層、強 subagent system、explicit context budget</p>
<h3 id="cursor">Cursor</h3>
<p><strong>Scaffold</strong>：System prompt 較固定、tool list 較簡、Composer mode 是 scaffold variant
<strong>Harness</strong>：IDE 整合度高、tool dispatch 跨 client / server、streaming response
<strong>特色</strong>：產品優化重於可客製、scaffold 半開放</p>
<h3 id="aider">Aider</h3>
<p><strong>Scaffold</strong>：edit-format（diff / udiff / whole）+ git integration、tool 較少（read / edit / run）
<strong>Harness</strong>：repl-style loop、自動 commit、線性對話
<strong>特色</strong>：CLI-first、scaffold 簡單、harness 圍繞 git 設計</p>
<h3 id="continuedev搭本地-llm">Continue.dev（搭本地 LLM）</h3>
<p><strong>Scaffold</strong>：Provider-agnostic、tool list 由 plugin 註冊
<strong>Harness</strong>：較輕量、tool dispatch 在 VS Code extension host
<strong>特色</strong>：適合本地 LLM、scaffold / harness 都相對開放</p>
<h2 id="失敗模式跟緩解">失敗模式跟緩解</h2>
<p>Coding agent 常見失敗：</p>
<table>
  <thead>
      <tr>
          <th>失敗</th>
          <th>根因</th>
          <th>緩解</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Context 用爆、模型失憶</td>
          <td>Budget 設計不當</td>
          <td>25% 規則、prompt cache、subagent 分擔</td>
      </tr>
      <tr>
          <td>Tool call infinite loop</td>
          <td>Harness 沒設 step 上限或 cost cap</td>
          <td>加 max_steps / max_cost、定期讓 user check</td>
      </tr>
      <tr>
          <td>Subagent 答錯仍被 main 採用</td>
          <td>Main agent 沒 verify subagent output</td>
          <td>加 verification step、let main 看 subagent trace</td>
      </tr>
      <tr>
          <td>修改檔案後 test 沒跑</td>
          <td>Scaffold 沒強制「先 test 後 commit」</td>
          <td>System prompt 加 explicit checklist、harness 加 hook</td>
      </tr>
      <tr>
          <td>Reasoning model 配短 context</td>
          <td>Reasoning trace 擠壓任務 context</td>
          <td>配 64K+ context、或拆任務</td>
      </tr>
      <tr>
          <td>Permission boundary 不夠細</td>
          <td>Scaffold 安全 policy 太寬</td>
          <td>副作用類 tool 拆細、加 confirm UI（見 <a href="/blog/llm/01-local-llm-services/hands-on/permission-boundary/" data-link-title="Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪" data-link-desc="四組對照實驗：Ollama 自己沒 FS / shell 權限、wrapper 才有；--dry-run / --confirm / --auto 三檔審查粒度的取捨">hands-on permission-boundary</a>）</td>
      </tr>
  </tbody>
</table>
<h2 id="本地小模型跑-coding-agent-的限制">本地小模型跑 coding agent 的限制</h2>
<p>本地 &lt; 14B 模型跑完整 coding agent 通常不穩、根因（跟 <a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">3.8 reasoning-models</a> / <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent-architecture</a> 已述）：</p>
<ol>
<li><strong>Tool use 不穩</strong>：小模型 function calling 訓練不足、tool call 格式錯誤率高</li>
<li><strong>Long context 退化</strong>：&lt; 14B 模型 effective context 通常 &lt; 16K、coding agent 場景容易撞 budget</li>
<li><strong>Reasoning 弱</strong>：multi-step planning、failure recovery 都需要 reasoning 能力</li>
<li><strong>Subagent handoff 失敗</strong>：小模型對「該 handoff 給誰」的判斷不穩</li>
</ol>
<p>實務組合：</p>
<ul>
<li><strong>Autocomplete + 簡單 chat</strong>：本地 7B-14B coder（Qwen3-Coder / Gemma 4 coder）可勝任</li>
<li><strong>完整 coding agent</strong>：30B+ 本地模型或雲端旗艦</li>
<li><strong>混用</strong>：本地小模型當 autocomplete + 雲端旗艦當 agent</li>
</ul>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Scaffold vs harness 分層 framing</li>
<li>Context budget 配額概念跟 25% 規則</li>
<li>Subagent 設計原則跟 handoff 機制</li>
<li>失敗模式分類（context 爆、infinite loop、permission 邊界）</li>
<li>本地小模型限制</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 coding agent（Claude Code / Cursor / Aider 等持續演化）</li>
<li>Subagent registry 標準化（目前各家不同）</li>
<li>Tool schema 標準化（MCP 是其中一條路）</li>
<li>本地小模型的 agent 能力（會逐步追上）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/prompt-caching-engineering/" data-link-title="4.18 Prompt caching 工程實務：cost / latency 最大槓桿" data-link-desc="Prompt cache 怎麼運作、cache_control 設計、coding agent 跟 long-context 的 cache pattern、anti-pattern 跟 cache miss 訊號">4.18 Prompt caching 工程實務</a>、看 scaffold 部分的 cost / latency 優化。</p>
]]></content:encoded></item><item><title>4.18 Prompt caching 工程實務：cost / latency 最大槓桿</title><link>https://tarrragon.github.io/blog/llm/04-applications/prompt-caching-engineering/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/prompt-caching-engineering/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">Prompt cache&lt;/a> 把重複 prefix 的計算結果在 LLM 服務端跨 request 持久化、後續 query 跳過 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill&lt;/a> 階段。Anthropic / OpenAI / Bedrock / Gemini 都列為 cost 跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT&lt;/a> 的最大單一槓桿 — 90% cost 折扣 + 顯著 latency 改善。本章把 prompt caching 的運作機制、設計原則、coding agent / long-context 場景的 pattern、常見 anti-pattern 拆成可操作的工程實務。&lt;/p>
&lt;p>注意三層 cache 概念的層次差異（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">prompt cache 卡片&lt;/a> 有完整對比表）：&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache&lt;/a> 是單次推論內、過去 token 的 K/V 暫存（autoregressive 才省重算）；&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">prefix cache&lt;/a> 是同一推論伺服器內跨 request 共用 KV cache；&lt;strong>prompt cache（本章聚焦）&lt;/strong> 是雲端 LLM API 商業 feature、跨 request 跨時間、有 TTL。三者不同層、要區分。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 prompt cache 跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">prefix cache&lt;/a> 的層次差異。&lt;/li>
&lt;li>對 coding agent / RAG / long-conversation 場景設計 cache breakpoint。&lt;/li>
&lt;li>估算自己應用開 prompt cache 的 cost / latency 收益。&lt;/li>
&lt;li>看到「cache 不命中」訊號時、能定位 anti-pattern 並修。&lt;/li>
&lt;/ol>
&lt;h2 id="prompt-cache-怎麼運作">Prompt cache 怎麼運作&lt;/h2>
&lt;p>LLM 推論的 prefill 階段對整個 prompt 算 KV cache、是長 prompt 的主要 latency 跟 compute 成本：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">無 cache：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> Request 1：[10K system prompt] + [tool schema 5K] + [user query 500] = 15.5K prefill
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> Request 2：[10K system prompt] + [tool schema 5K] + [user query 700] = 15.7K prefill
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> → 兩次都付 15K prefill 成本&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>開 prompt cache 後：&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">Prompt cache</a> 把重複 prefix 的計算結果在 LLM 服務端跨 request 持久化、後續 query 跳過 <a href="/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill</a> 階段。Anthropic / OpenAI / Bedrock / Gemini 都列為 cost 跟 <a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a> 的最大單一槓桿 — 90% cost 折扣 + 顯著 latency 改善。本章把 prompt caching 的運作機制、設計原則、coding agent / long-context 場景的 pattern、常見 anti-pattern 拆成可操作的工程實務。</p>
<p>注意三層 cache 概念的層次差異（<a href="/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">prompt cache 卡片</a> 有完整對比表）：<a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> 是單次推論內、過去 token 的 K/V 暫存（autoregressive 才省重算）；<a href="/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">prefix cache</a> 是同一推論伺服器內跨 request 共用 KV cache；<strong>prompt cache（本章聚焦）</strong> 是雲端 LLM API 商業 feature、跨 request 跨時間、有 TTL。三者不同層、要區分。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 prompt cache 跟 <a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> / <a href="/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">prefix cache</a> 的層次差異。</li>
<li>對 coding agent / RAG / long-conversation 場景設計 cache breakpoint。</li>
<li>估算自己應用開 prompt cache 的 cost / latency 收益。</li>
<li>看到「cache 不命中」訊號時、能定位 anti-pattern 並修。</li>
</ol>
<h2 id="prompt-cache-怎麼運作">Prompt cache 怎麼運作</h2>
<p>LLM 推論的 prefill 階段對整個 prompt 算 KV cache、是長 prompt 的主要 latency 跟 compute 成本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">無 cache：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  Request 1：[10K system prompt] + [tool schema 5K] + [user query 500] = 15.5K prefill
</span></span><span class="line"><span class="ln">3</span><span class="cl">  Request 2：[10K system prompt] + [tool schema 5K] + [user query 700] = 15.7K prefill
</span></span><span class="line"><span class="ln">4</span><span class="cl">  → 兩次都付 15K prefill 成本</span></span></code></pre></div><p>開 prompt cache 後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Request 1：[10K system + 5K tool schema] | cache_control | + [user query 500]
</span></span><span class="line"><span class="ln">2</span><span class="cl">  → 算出 prefix 的 KV cache、寫進服務端 cache（付 1.25× cost）
</span></span><span class="line"><span class="ln">3</span><span class="cl">  → 後段 prefill 500 token
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">Request 2（5 分鐘內）：[10K system + 5K tool schema] | + [user query 700]
</span></span><span class="line"><span class="ln">6</span><span class="cl">  → 服務端命中 cache、跳過 prefix 的 prefill（付 0.1× cost = 90% 折扣）
</span></span><span class="line"><span class="ln">7</span><span class="cl">  → 只 prefill 700 token
</span></span><span class="line"><span class="ln">8</span><span class="cl">  → TTFT 大幅降低</span></span></code></pre></div><p>關鍵運作細節：</p>
<ol>
<li><strong>Cache key = prefix 的 token sequence</strong>：完全相同的 token sequence 才命中、差一個 token 就 miss</li>
<li><strong>TTL（time-to-live）</strong>：cache 過一段時間（多數 5 min）自動失效、要 ext 1h 通常付額外 cost</li>
<li><strong>Write 比原價略貴、Read 大幅打折</strong>：Anthropic 模型 write 1.25×、read 0.1×；OpenAI 模型 read 0.5×</li>
<li><strong>Minimum cacheable size</strong>：通常 1K-4K token 起跳、短 prompt 不適合</li>
<li><strong>Cache 範圍</strong>：跨 request、跨 conversation、跨 session、但同一 model + 同一 region</li>
</ol>
<h2 id="cache-breakpoint-設計">Cache breakpoint 設計</h2>
<p>Anthropic 用 <code>cache_control</code> 標記顯式 breakpoint、OpenAI 用自動偵測。但設計原則一致：<strong>把不變的內容放 prefix、變動的放後面</strong>。</p>
<p>典型 coding agent 的 prompt 結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[1. System prompt]：agent 角色、規則、輸出格式             ← 不變
</span></span><span class="line"><span class="ln">2</span><span class="cl">[2. Tool schema]：所有 tool 的 spec                       ← 不變（除非加新 tool）
</span></span><span class="line"><span class="ln">3</span><span class="cl">[3. Skill registry / playbook]：known recipes              ← 半變（偶爾更新）
</span></span><span class="line"><span class="ln">4</span><span class="cl">[4. Codebase context]：固定載入的核心檔案                  ← 半變
</span></span><span class="line"><span class="ln">5</span><span class="cl">       ↓ cache_control breakpoint ↑
</span></span><span class="line"><span class="ln">6</span><span class="cl">[5. Conversation history]：過去回合                       ← 變動
</span></span><span class="line"><span class="ln">7</span><span class="cl">[6. Current user query]：當前 query                       ← 變動
</span></span><span class="line"><span class="ln">8</span><span class="cl">[7. Current tool result]：剛跑完的 tool output             ← 變動</span></span></code></pre></div><p>Breakpoint 放在「不變 vs 變動」交界處、讓 [1-4] 永遠 cache hit。</p>
<p>Anthropic 最多 4 個 breakpoint、可分層：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">breakpoint 1（最早）：[system prompt] → 永久 cache
</span></span><span class="line"><span class="ln">2</span><span class="cl">breakpoint 2：       [+ tool schema] → 永久 cache
</span></span><span class="line"><span class="ln">3</span><span class="cl">breakpoint 3：       [+ skill registry] → 半永久 cache
</span></span><span class="line"><span class="ln">4</span><span class="cl">breakpoint 4（最晚）：[+ recent stable context] → 短期 cache
</span></span><span class="line"><span class="ln">5</span><span class="cl">[後段]：             variable content（不 cache）</span></span></code></pre></div><p>每個 breakpoint 各自命中 / miss、layered cache 讓「加新 skill」只 invalidate breakpoint 3 之後、不影響 breakpoint 1-2。</p>
<h2 id="場景-1coding-agent">場景 1：Coding agent</h2>
<p>Coding agent 是 prompt cache 命中區 — system prompt + tool schema 動輒 10K-30K token、每個 user turn 都重用。</p>
<p>收益估算（200K context 模型、10K scaffold、5K user query、3K answer）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">無 cache：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  每 turn input cost = (10K + 5K) × $3/M = $0.045
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  每 turn TTFT = 10K-15K prefill time（200-400ms）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">開 cache：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  Turn 1（write）：(10K × 1.25 + 5K) × $3/M = $0.0525
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  Turn 2-N（read）：(10K × 0.1 + 5K) × $3/M = $0.018
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  TTFT：read 階段省掉 10K prefill、只剩 5K
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">10 turns 的累計 cost：
</span></span><span class="line"><span class="ln">11</span><span class="cl">  無 cache：10 × $0.045 = $0.45
</span></span><span class="line"><span class="ln">12</span><span class="cl">  開 cache：$0.0525 + 9 × $0.018 = $0.215
</span></span><span class="line"><span class="ln">13</span><span class="cl">  → 節省 52%</span></span></code></pre></div><p>長對話越長、cache 收益越大（cache write 是一次性成本）。</p>
<h2 id="場景-2rag--long-context">場景 2：RAG / long-context</h2>
<p>RAG 場景把 retrieved chunks 放 prefix、user query 放後面、可以 cache retrieved chunks：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[system prompt]
</span></span><span class="line"><span class="ln">2</span><span class="cl">       ↓ breakpoint 1（system 永久 cache）
</span></span><span class="line"><span class="ln">3</span><span class="cl">[retrieved chunks 來自 RAG]
</span></span><span class="line"><span class="ln">4</span><span class="cl">       ↓ breakpoint 2（同 chunks 在 5min 內 cache）
</span></span><span class="line"><span class="ln">5</span><span class="cl">[user query]</span></span></code></pre></div><p>注意：每次 retrieval 不同 chunks 就 cache miss、所以 cache 適合「同個對話多輪、retrieval 結果穩定」、不適合「每 query 都 fresh retrieve」；後者要回到 <a href="/blog/llm/knowledge-cards/retrieval-cost/" data-link-title="Retrieval Cost" data-link-desc="RAG 檢索帶來的 latency、token、embedding、reranker、LLM call 與維護成本，用來判斷增強是否划算">retrieval cost</a> 評估。</p>
<h2 id="場景-3long-document-qa">場景 3：Long document Q&amp;A</h2>
<p>讀者上傳 PDF / 文件、多輪問問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[system prompt]
</span></span><span class="line"><span class="ln">2</span><span class="cl">       ↓ breakpoint 1
</span></span><span class="line"><span class="ln">3</span><span class="cl">[完整文件內容（可能 100K token）]
</span></span><span class="line"><span class="ln">4</span><span class="cl">       ↓ breakpoint 2（文件永久 cache）
</span></span><span class="line"><span class="ln">5</span><span class="cl">[user query]</span></span></code></pre></div><p>第一次 query 付 1.25× 文件成本、後續 query 都 0.1×。100K 文件 + 10 個問題的場景下、節省極顯著（&gt; 80% cost）。</p>
<h2 id="常見-anti-pattern">常見 anti-pattern</h2>
<ol>
<li><strong>在 prefix 插入 timestamp / request-id</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">反例：System prompt: &#34;你是 coding assistant、當前時間 2026-05-12 16:30:42、...&#34;
</span></span><span class="line"><span class="ln">2</span><span class="cl">   → 每秒不同 cache key、永遠 cache miss、付 1.25× write 不回本
</span></span><span class="line"><span class="ln">3</span><span class="cl">正解：把 timestamp 放後段、或省略（多數場景模型不需要精確時間）</span></span></code></pre></div><ol start="2">
<li><strong>在 prefix 動態插入 user metadata</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">反例：System prompt: &#34;User: alice@example.com, plan: premium、...&#34;
</span></span><span class="line"><span class="ln">2</span><span class="cl">   → 每個 user 不同 cache、命中率低
</span></span><span class="line"><span class="ln">3</span><span class="cl">正解：User metadata 放後段、prefix 保持 user-agnostic</span></span></code></pre></div><ol start="3">
<li><strong>Tool schema 順序不固定</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">反例：每次 request 把 tool list 隨機 shuffle
</span></span><span class="line"><span class="ln">2</span><span class="cl">   → 同樣 tool 但 token sequence 不同、cache miss
</span></span><span class="line"><span class="ln">3</span><span class="cl">正解：Tool list 順序固定、新加 tool 都 append 到末尾</span></span></code></pre></div><ol start="4">
<li><strong>太短的 prompt 也想 cache</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">反例：500 token system prompt 開 cache
</span></span><span class="line"><span class="ln">2</span><span class="cl">   → 多數服務商 minimum 1K-4K、不到門檻不 cache、且 write cost 不回本
</span></span><span class="line"><span class="ln">3</span><span class="cl">正解：Cache 留給 &gt; 1K 的 prefix、短 prompt 不必開</span></span></code></pre></div><ol start="5">
<li><strong>混用 stream + cache 卻不檢查命中</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">反例：開 cache 後不檢查 response 的 cache_read_input_tokens 欄位
</span></span><span class="line"><span class="ln">2</span><span class="cl">   → 不知道實際命中率、可能 anti-pattern 已在燒 cost 沒察覺
</span></span><span class="line"><span class="ln">3</span><span class="cl">正解：監控 cache_read / cache_creation token 比例、低於 80% 命中率時 debug</span></span></code></pre></div><h2 id="cache-miss-訊號跟診斷">Cache miss 訊號跟診斷</h2>
<p>訊號：</p>
<ol>
<li><strong>Cost 比預期高</strong>：應該命中的場景仍付 full price</li>
<li><strong>TTFT 沒改善</strong>：cache hit 應該大幅降 TTFT、沒改善 = miss</li>
<li><strong>Response 的 usage 顯示 cache_read = 0</strong>：直接訊號</li>
</ol>
<p>診斷流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 印出 raw request 的 prefix（cache_control 之前）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 比對連續兩次 request 的 prefix token sequence
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 找出差異位置（diff）
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 移除 / 重構讓兩次 prefix 完全相同
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 跑 2-3 次 request、看 cache_read_input_tokens 是否上升</span></span></code></pre></div><p>常見差異：timestamp、request id、user id、tool list 順序、retrieved chunks 順序、conversation summary 變動。</p>
<h2 id="跟其他-cost-優化技巧的關係">跟其他 cost 優化技巧的關係</h2>
<table>
  <thead>
      <tr>
          <th>技巧</th>
          <th>攻擊的 cost / latency 來源</th>
          <th>跟 prompt cache 的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">Speculative decoding</a></td>
          <td>Generation 階段 token cost</td>
          <td>正交、可疊加</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/batching/" data-link-title="Batching" data-link-desc="多 request 一起跑、攤平 model load 成本：production LLM inference 的核心優化、決定 throughput vs latency 取捨">Batching</a></td>
          <td>Throughput per GPU</td>
          <td>Production 才用、跟 prompt cache 都用</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">Prefix cache</a></td>
          <td>同 server 跨 request 共用 KV cache</td>
          <td>本地推論伺服器特性、prompt cache 是雲端 API 商業 feature</td>
      </tr>
      <tr>
          <td>模型量化</td>
          <td>Generation tok/s</td>
          <td>正交、可疊加</td>
      </tr>
      <tr>
          <td>RAG 而非 long context</td>
          <td>Input token 量</td>
          <td>RAG + cache 可同時用</td>
      </tr>
  </tbody>
</table>
<h2 id="本地推論伺服器有沒有類似機制">本地推論伺服器有沒有類似機制</h2>
<p>Ollama / LM Studio / llama.cpp 自身的 prompt cache：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>機制</th>
          <th>範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>llama.cpp</td>
          <td><code>--prompt-cache</code> flag、persistent file</td>
          <td>重複跑同樣 prompt 時跳過 prefill</td>
      </tr>
      <tr>
          <td>Ollama</td>
          <td>內建 prefix cache、跨 request 共用</td>
          <td>同 server 跨 request</td>
      </tr>
      <tr>
          <td>LM Studio</td>
          <td>同 Ollama 級別、視版本</td>
          <td>同上</td>
      </tr>
      <tr>
          <td>vLLM</td>
          <td>強 prefix cache（PagedAttention 設計支援）</td>
          <td>高併發 production</td>
      </tr>
  </tbody>
</table>
<p>本地推論的 cache 主要靠 <a href="/blog/llm/knowledge-cards/prefix-cache/" data-link-title="Prefix Cache" data-link-desc="把多個請求共用的前綴 prompt 的 KV cache 重用、省下重複 prefill 算力的優化、production 多用戶服務的常見設計">prefix cache</a> 機制、跟雲端 API 的 prompt cache 商業 feature 同源、但定價 / TTL / 顯式 control 是雲端 API 才有的 product layer。</p>
<h2 id="何時不適合用-prompt-cache">何時不適合用 prompt cache</h2>
<ol>
<li><strong>每 request prefix 必變</strong>：streaming 任務、每 query 都帶 fresh 上下文</li>
<li><strong>Single-shot 對話</strong>：用完就丟、沒有重複使用、write cost 不回本</li>
<li><strong>Prefix &lt; 1K token</strong>：不到 minimum、cache 不生效</li>
<li><strong>Cost 不敏感場景</strong>：個人小流量、cache 設計 overhead 大於收益</li>
<li><strong>本地推論為主</strong>：本地多用 prefix cache、prompt cache 是雲端 API 概念</li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>「不變放 prefix、變動放後段」的設計原則</li>
<li>Cache breakpoint 分層（system / tool schema / skill / context）</li>
<li>Anti-pattern 分類（timestamp、user metadata、tool 順序）</li>
<li>Cache miss 診斷流程</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>各 vendor 的具體定價（write × / read × 折扣）</li>
<li>TTL（5min vs 1h）的可選性跟價格</li>
<li>Automatic vs explicit cache（OpenAI vs Anthropic 路線）</li>
<li>Breakpoint 上限數量</li>
<li>本地推論伺服器的 cache 功能（持續演化）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/agent-memory-architecture/" data-link-title="4.19 Agent memory 分層架構" data-link-desc="Agent 在 context window 之外管理長期狀態的設計：working / short-term / long-term episodic / semantic / procedural 五個層次、寫入時機、retrieval 設計、失敗模式">4.19 Agent memory 分層</a>、看 agent 如何在 context window 之外管理長期狀態。</p>
]]></content:encoded></item><item><title>4.19 Agent memory 分層架構</title><link>https://tarrragon.github.io/blog/llm/04-applications/agent-memory-architecture/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/agent-memory-architecture/</guid><description>&lt;p>LLM 本身無狀態 — 每次 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/forward-pass/" data-link-title="Forward Pass" data-link-desc="input 經過所有 layer 的計算、得到 output 的單向流程；推論跟訓練都會跑、訓練多一個反向階段">forward pass&lt;/a> 從零開始、唯一輸入是 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window&lt;/a>。但「agent」概念上有跨 session 狀態：使用者偏好、過去任務、累積知識、操作流程。Agent memory 是 harness 層的設計、把這些狀態持久化、按需 inject 到 working context。本章把 memory 分成五個層次、各層的寫入時機、retrieval 設計、失敗模式拆成可操作的工程實務。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>區分 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/agent-memory/" data-link-title="Agent Memory" data-link-desc="Agent 在 context window 之外管理長期狀態的設計、五個層次：working / short-term / long-term episodic / semantic / procedural">agent memory&lt;/a> 的五個層次（working / short-term / long-term episodic / semantic / procedural）。&lt;/li>
&lt;li>對自己 agent 場景判斷要哪幾層 memory、不要哪幾層。&lt;/li>
&lt;li>設計 long-term memory 的「何時寫」「何時讀」邏輯。&lt;/li>
&lt;li>認識 memory 的常見失敗模式（drift / PII / 污染）跟對應緩解。&lt;/li>
&lt;/ol>
&lt;h2 id="五個層次的責任劃分">五個層次的責任劃分&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">[Working memory]：當前 forward pass 的 context window
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> - 規模：模型 context（4K-1M token）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> - 範圍：當下這次推論的全部輸入
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> - 例：當下 user query + recent tool result + reasoning trace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> ↑ 從這層讀 / 寫到這層
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">[Short-term / session memory]：單一 session 的 scratchpad
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> - 規模：一輪對話到一天
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> - 範圍：跨多個 turn、但 session 結束就丟
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> - 例：本 session 算過的中間結果、tried strategies
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> ↑ session 結束時可選擇 persist 到 long-term
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">[Long-term episodic memory]：跨 session 的「事件」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> - 規模：永久（直到主動刪除）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> - 範圍：跨所有 session、按時間順序
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> - 例：「上週解過這個 race condition」「alice 上個月問過 X」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">[Long-term semantic memory]：跨 session 的「事實 / 知識」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> - 規模：永久
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> - 範圍：跨所有 session、按主題索引
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> - 例：「user 偏好 markdown 輸出」「專案用 React 18」「team 不用 Tailwind」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">[Long-term procedural memory]：跨 session 的「流程 / 技能」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> - 規模：永久
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> - 範圍：可重複使用的 known-good 程序
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> - 例：「跑測試前先 npm install」「commit 前要 lint」「deploy 前要 dry-run」&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跟人類認知科學的對應：working ≈ 短期工作記憶、episodic ≈ 「我昨天去哪裡了」、semantic ≈ 「巴黎是法國首都」、procedural ≈ 「騎腳踏車的肌肉記憶」。&lt;/p></description><content:encoded><![CDATA[<p>LLM 本身無狀態 — 每次 <a href="/blog/llm/knowledge-cards/forward-pass/" data-link-title="Forward Pass" data-link-desc="input 經過所有 layer 的計算、得到 output 的單向流程；推論跟訓練都會跑、訓練多一個反向階段">forward pass</a> 從零開始、唯一輸入是 <a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window</a>。但「agent」概念上有跨 session 狀態：使用者偏好、過去任務、累積知識、操作流程。Agent memory 是 harness 層的設計、把這些狀態持久化、按需 inject 到 working context。本章把 memory 分成五個層次、各層的寫入時機、retrieval 設計、失敗模式拆成可操作的工程實務。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>區分 <a href="/blog/llm/knowledge-cards/agent-memory/" data-link-title="Agent Memory" data-link-desc="Agent 在 context window 之外管理長期狀態的設計、五個層次：working / short-term / long-term episodic / semantic / procedural">agent memory</a> 的五個層次（working / short-term / long-term episodic / semantic / procedural）。</li>
<li>對自己 agent 場景判斷要哪幾層 memory、不要哪幾層。</li>
<li>設計 long-term memory 的「何時寫」「何時讀」邏輯。</li>
<li>認識 memory 的常見失敗模式（drift / PII / 污染）跟對應緩解。</li>
</ol>
<h2 id="五個層次的責任劃分">五個層次的責任劃分</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[Working memory]：當前 forward pass 的 context window
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   - 規模：模型 context（4K-1M token）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   - 範圍：當下這次推論的全部輸入
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - 例：當下 user query + recent tool result + reasoning trace
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">       ↑ 從這層讀 / 寫到這層
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">[Short-term / session memory]：單一 session 的 scratchpad
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 規模：一輪對話到一天
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 範圍：跨多個 turn、但 session 結束就丟
</span></span><span class="line"><span class="ln">11</span><span class="cl">   - 例：本 session 算過的中間結果、tried strategies
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">       ↑ session 結束時可選擇 persist 到 long-term
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">[Long-term episodic memory]：跨 session 的「事件」
</span></span><span class="line"><span class="ln">16</span><span class="cl">   - 規模：永久（直到主動刪除）
</span></span><span class="line"><span class="ln">17</span><span class="cl">   - 範圍：跨所有 session、按時間順序
</span></span><span class="line"><span class="ln">18</span><span class="cl">   - 例：「上週解過這個 race condition」「alice 上個月問過 X」
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">[Long-term semantic memory]：跨 session 的「事實 / 知識」
</span></span><span class="line"><span class="ln">21</span><span class="cl">   - 規模：永久
</span></span><span class="line"><span class="ln">22</span><span class="cl">   - 範圍：跨所有 session、按主題索引
</span></span><span class="line"><span class="ln">23</span><span class="cl">   - 例：「user 偏好 markdown 輸出」「專案用 React 18」「team 不用 Tailwind」
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">[Long-term procedural memory]：跨 session 的「流程 / 技能」
</span></span><span class="line"><span class="ln">26</span><span class="cl">   - 規模：永久
</span></span><span class="line"><span class="ln">27</span><span class="cl">   - 範圍：可重複使用的 known-good 程序
</span></span><span class="line"><span class="ln">28</span><span class="cl">   - 例：「跑測試前先 npm install」「commit 前要 lint」「deploy 前要 dry-run」</span></span></code></pre></div><p>跟人類認知科學的對應：working ≈ 短期工作記憶、episodic ≈ 「我昨天去哪裡了」、semantic ≈ 「巴黎是法國首都」、procedural ≈ 「騎腳踏車的肌肉記憶」。</p>
<h2 id="不是每個-agent-都要五個層次都用">不是每個 agent 都要五個層次都用</h2>
<p>選擇看用例：</p>
<table>
  <thead>
      <tr>
          <th>用例</th>
          <th>Working</th>
          <th>Session</th>
          <th>Episodic</th>
          <th>Semantic</th>
          <th>Procedural</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Autocomplete（單行補完）</td>
          <td>需要</td>
          <td>不需要</td>
          <td>不需要</td>
          <td>不需要</td>
          <td>不需要</td>
      </tr>
      <tr>
          <td>Single-turn Q&amp;A</td>
          <td>需要</td>
          <td>不需要</td>
          <td>不需要</td>
          <td>不需要</td>
          <td>不需要</td>
      </tr>
      <tr>
          <td>Chat IDE assistant（短對話）</td>
          <td>需要</td>
          <td>需要</td>
          <td>不需要</td>
          <td>不需要</td>
          <td>不需要</td>
      </tr>
      <tr>
          <td>Chat IDE assistant（長期使用）</td>
          <td>需要</td>
          <td>需要</td>
          <td>可選</td>
          <td>需要</td>
          <td>可選</td>
      </tr>
      <tr>
          <td>長期 coding agent（持續同 codebase）</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
      </tr>
      <tr>
          <td>Multi-session research agent</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
          <td>需要</td>
      </tr>
  </tbody>
</table>
<p>實務啟示：從「最少 memory」開始、有具體 trigger 才加。memory 不是越多越好、每加一層都增加複雜度跟失敗面。</p>
<h2 id="long-term-memory-的寫入時機">Long-term memory 的寫入時機</h2>
<p><strong>何時寫</strong>是設計核心、影響 memory 的品質跟成本。三種主流模式：</p>
<h3 id="1-每-turn-寫auto-write">1. 每 turn 寫（Auto-write）</h3>
<p>每個對話 turn 結束都寫一條 memory。實作簡單但 memory 變垃圾場 — 太多瑣碎內容、retrieval 時混淆 signal。</p>
<p><strong>適合</strong>：實驗階段、想看 memory 怎麼累積
<strong>不適合</strong>：production、長期使用</p>
<h3 id="2-任務結束寫task-end-write">2. 任務結束寫（Task-end write）</h3>
<p>每個明確「任務」（如「修完 bug」「寫完 feature」）結束時、寫一條 episodic / semantic memory 摘要。</p>
<p>實作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">任務開始 → working memory 進入「task mode」
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ 多 turn 累積 session scratchpad
</span></span><span class="line"><span class="ln">3</span><span class="cl">任務結束（user 說「好了」/ test 通過 / commit done）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ trigger memory write
</span></span><span class="line"><span class="ln">5</span><span class="cl">LLM call：「請從本 session 提取值得記得的 episodic / semantic / procedural memory」
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ↓ 結構化輸出
</span></span><span class="line"><span class="ln">7</span><span class="cl">寫進 long-term store</span></span></code></pre></div><p><strong>適合</strong>：production agent、明確任務邊界
<strong>不適合</strong>：開放式對話、無明確任務終點</p>
<h3 id="3-主動觸發寫reflection--consolidation">3. 主動觸發寫（Reflection / consolidation）</h3>
<p>定期（每 N turn / 每天）跑「memory consolidation」step、LLM 自己決定該寫什麼。借鑒人類睡眠時 memory consolidation 的研究。</p>
<p><strong>適合</strong>：長 running agent、有明確 idle 時間
<strong>不適合</strong>：低 cost 場景（consolidation 額外 LLM call 是常駐成本）</p>
<p>混用：production 多用「task-end write」為主 + 偶爾 reflection 做 consolidation。</p>
<h2 id="long-term-memory-的-retrieval">Long-term memory 的 retrieval</h2>
<p><strong>何時讀</strong>也是設計核心。三種主流模式：</p>
<h3 id="1-inject-on-startup">1. Inject-on-startup</h3>
<p>把 long-term memory 在 session / agent 啟動時一次塞進 system prompt。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">System prompt:
</span></span><span class="line"><span class="ln">2</span><span class="cl">  &#34;你是 coding assistant、user alice。
</span></span><span class="line"><span class="ln">3</span><span class="cl">   semantic memory: {markdown 偏好、React 18、Python 3.11、...}
</span></span><span class="line"><span class="ln">4</span><span class="cl">   procedural memory: {npm install before test、lint before commit、...}&#34;</span></span></code></pre></div><p><strong>適合</strong>：memory 量小（&lt; 1K token）、相對穩定
<strong>不適合</strong>：memory 多、變動快、retrieval 不準</p>
<h3 id="2-retrieval-on-demand">2. Retrieval-on-demand</h3>
<p>每次 user query 來、用 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">embedding similarity</a> 從 vector store retrieve 相關 memory、塞進 context。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">User query → embed → cosine similarity vs memory vectors → top-K → inject</span></span></code></pre></div><p><strong>適合</strong>：memory 量大、跨主題、需要動態
<strong>不適合</strong>：高頻 / 低 latency 要求（retrieval overhead）</p>
<h3 id="3-hybrid混合">3. Hybrid（混合）</h3>
<p>Procedural / semantic（穩定）→ inject-on-startup；episodic（動態）→ retrieval-on-demand。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Session 啟動：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  inject procedural + semantic（小、穩定）
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">每 user query：
</span></span><span class="line"><span class="ln">5</span><span class="cl">  retrieve top-K episodic（動態）+ inject</span></span></code></pre></div><p>實務 production 多採 hybrid。</p>
<h2 id="跟-rag-的邊界">跟 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> 的邊界</h2>
<p>Agent memory 跟 RAG 容易混淆、實際上是不同概念：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>RAG</th>
          <th>Long-term agent memory</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主要內容</td>
          <td>外部知識庫（docs、wiki、codebase）</td>
          <td>Agent 跟特定 user 的互動歷史</td>
      </tr>
      <tr>
          <td>Per-user？</td>
          <td>通常通用</td>
          <td>Per-user / per-session</td>
      </tr>
      <tr>
          <td>寫入時機</td>
          <td>Build time / ingestion pipeline</td>
          <td>Runtime（agent 自己決定何時寫）</td>
      </tr>
      <tr>
          <td>變動頻率</td>
          <td>較慢（doc 更新）</td>
          <td>快（每 session 都可能變）</td>
      </tr>
      <tr>
          <td>是否含「事件」</td>
          <td>否（純知識）</td>
          <td>Episodic memory 是事件</td>
      </tr>
  </tbody>
</table>
<p>但兩者實作層常共享：vector store / embedding model / retrieval logic 可重用。設計上：</p>
<ul>
<li><strong>如果讀者問「跟『過去聊過的事』有關」→ memory</strong></li>
<li><strong>如果讀者問「跟『某個固定知識』有關」→ RAG</strong></li>
<li><strong>同一個 query 兩者都要 → hybrid retrieval、結果合併</strong></li>
</ul>
<h2 id="失敗模式">失敗模式</h2>
<h3 id="1-memory-drift記憶過時">1. Memory drift（記憶過時）</h3>
<p>舊 memory 寫的內容不再正確、但仍被 retrieve、agent 用過時資訊。</p>
<p><strong>例</strong>：兩個月前寫 memory「user 偏好 React class component」、user 已換 hooks、agent 仍寫 class component。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Memory 加 timestamp、retrieval 時加 time decay weighting</li>
<li>定期 consolidation：LLM 跑一遍判斷哪些 memory 過時</li>
<li>Procedural / semantic memory 跑「validation step」：當前對話是否仍 align、不 align 就 mark stale</li>
</ul>
<h3 id="2-pii-寫入">2. PII 寫入</h3>
<p>User 不知情下、agent 把 PII（email、phone、社群 ID）寫進 long-term memory、跨 session retrieve 出來、可能洩漏。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Memory write 前過 PII detection（regex 或專門模型）</li>
<li>Memory store 加 encryption-at-rest</li>
<li>User 可看 / 編輯 / 刪除自己 memory（GDPR / 隱私法規要求）</li>
<li>跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端資料邊界</a> 結合判讀</li>
</ul>
<h3 id="3-context-污染">3. Context 污染</h3>
<p>不相關 memory 被 retrieve 進 working memory、模型把 irrelevant 內容當 signal、輸出飄。</p>
<p><strong>例</strong>：user 問 React 問題、retrieve 出兩個月前的 Vue 經驗、模型混淆。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Retrieval 加 similarity threshold（&lt; 0.7 不 inject）</li>
<li>Memory 加 metadata（topic / project / language）、retrieval 加 filter</li>
<li>Inject 後加 explicit framing：「以下是過去相關 memory、僅供參考、若跟當前問題不符請忽略」</li>
</ul>
<h3 id="4-memory-跟-hallucination-互相-boost">4. Memory 跟 hallucination 互相 boost</h3>
<p><a href="/blog/llm/knowledge-cards/hallucination/" data-link-title="Hallucination" data-link-desc="LLM 生成內容看起來合理但事實錯誤、引用不存在的來源、虛構不存在的 entity 的現象">Hallucination</a> 寫進 memory、變成「事實」、後續 retrieve 強化 hallucination、agent 越來越相信錯誤內容。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Memory write 前要求 LLM 標「不確定」flag、retrieval 時 deprioritize</li>
<li>定期 ground truth validation（如連結 memory 到實際檔案、檔案變了 memory 失效）</li>
<li>Critical memory 要 user 確認才寫入</li>
</ul>
<h3 id="5-跨-user-memory-污染">5. 跨 user memory 污染</h3>
<p>Production 多 user 場景、memory store 沒做 user isolation、A user 的 memory 流到 B user。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Memory store schema 強制 user_id 索引</li>
<li>Retrieval query 必加 user_id filter</li>
<li>跟 <a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">6.5 routing-to-production</a> 的多租戶 isolation 結合</li>
</ul>
<h2 id="主流實作">主流實作</h2>
<table>
  <thead>
      <tr>
          <th>工具 / framework</th>
          <th>特色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mem0</td>
          <td>開源、五層 memory framework、retrieval-on-demand</td>
      </tr>
      <tr>
          <td>Letta（前 MemGPT）</td>
          <td>LLM-managed memory hierarchy、自動 page in/out</td>
      </tr>
      <tr>
          <td>LangGraph memory</td>
          <td>LangChain 系、跟 graph workflow 整合</td>
      </tr>
      <tr>
          <td>Zep</td>
          <td>雲端 memory service、含 PII detection</td>
      </tr>
      <tr>
          <td>Self-implemented（DIY）</td>
          <td>多數 production 自寫、用 vector store + metadata</td>
      </tr>
  </tbody>
</table>
<p>判讀：用既有 framework vs 自己寫、取決於 memory 邏輯複雜度。簡單 case（per-user semantic preferences）用 DIY 即可；多層 memory + consolidation + GDPR 合規要 framework / SaaS。</p>
<h2 id="跟-coding-agent-的整合">跟 Coding agent 的整合</h2>
<p>Coding agent 場景的 memory 案例：</p>
<table>
  <thead>
      <tr>
          <th>Memory 類型</th>
          <th>內容例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Semantic</td>
          <td>「專案用 TypeScript strict mode」「team 不用 anonymous default export」</td>
      </tr>
      <tr>
          <td>Procedural</td>
          <td>「跑測試 = <code>npm test</code>」「commit 前 <code>npm run lint</code>」</td>
      </tr>
      <tr>
          <td>Episodic</td>
          <td>「上週解過 race condition 在 user_session.ts」「alice 的 retry 邏輯偏好」</td>
      </tr>
  </tbody>
</table>
<p>跟 <a href="/blog/llm/04-applications/coding-agent-harness/" data-link-title="4.17 Coding agent harness：scaffold / context engineering / subagent" data-link-desc="Coding agent 的內部設計：scaffold vs harness 分層、context budget 25% 規則、subagent 拓樸、跟 Claude Code / Cursor / Aider 的 mapping">4.17 coding agent harness</a> 的關係：</p>
<ul>
<li>Procedural memory 編進 <a href="/blog/llm/knowledge-cards/scaffold-vs-harness/" data-link-title="Scaffold vs Harness" data-link-desc="Coding agent 的兩個工程層次：scaffold 是建構時靜態結構、harness 是 runtime 的 tool dispatch &#43; context management &#43; safety">scaffold</a> 的 system prompt 或 skill registry</li>
<li>Semantic memory 可 inject-on-startup 或 retrieval-on-demand</li>
<li>Episodic memory 用 retrieval-on-demand、跟 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG</a> 共享 infrastructure</li>
</ul>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>五層 memory 分類（working / session / episodic / semantic / procedural）</li>
<li>「不是每個 agent 都要五層都用」的選擇框架</li>
<li>寫入時機的三種模式（auto / task-end / reflection）</li>
<li>Retrieval 的三種模式（inject / retrieval / hybrid）</li>
<li>五個失敗模式分類</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 framework（Mem0 / Letta / LangGraph）的 API</li>
<li>LLM-managed memory 的具體實作（如 MemGPT 風格的 paging）</li>
<li>Memory consolidation 的最佳實踐</li>
<li>整合 LLM 跟 vector store / DB 的最佳方式</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing 與 observability</a>、看 production debug 跟 cost 監控的工具層。</p>
]]></content:encoded></item><item><title>4.20 LLM tracing 與 observability</title><link>https://tarrragon.github.io/blog/llm/04-applications/llm-tracing-and-observability/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/llm-tracing-and-observability/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-tracing/" data-link-title="LLM Tracing" data-link-desc="把 LLM 應用的每次 LLM call / tool call / memory op 編成結構化 span、用 OpenTelemetry GenAI semantic conventions 標準化">LLM tracing&lt;/a> 把每次 LLM call / tool call / memory op / handoff 編成結構化 span、用 OpenTelemetry GenAI semantic conventions 標準化、是 production LLM 應用 debug / cost / quality 監控的事實標準。傳統 web app 的字串 logging 抓不到 LLM 應用的關鍵問題 — agent 為什麼選了那條路、reasoning trace 怎麼推導、tool call 為什麼 retry 三次、token 消耗為什麼比預期高 ×3。本章把 LLM tracing 的運作機制、OTel GenAI semconv、三大 use case（cost / latency / failure）跟 production eval 閉環拆成可操作的工程實務。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 LLM tracing 跟 traditional logging 的差異。&lt;/li>
&lt;li>用 OpenTelemetry GenAI semantic conventions 設計 span 結構。&lt;/li>
&lt;li>用 trace 做 cost / latency 監控跟 failure debug。&lt;/li>
&lt;li>把 production trace 餵回 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">LLM-as-judge&lt;/a> 做品質迴路。&lt;/li>
&lt;li>對自己應用判斷該用 self-host vs SaaS observability platform。&lt;/li>
&lt;/ol>
&lt;h2 id="traditional-logging-為什麼不夠">Traditional logging 為什麼不夠&lt;/h2>
&lt;p>LLM 應用的 debug 問題對傳統 logging 太抽象：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Logging 看到&lt;/th>
 &lt;th>真正需要的資訊&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Agent 為什麼選 tool A 不選 tool B&lt;/td>
 &lt;td>&lt;code>tool=A&lt;/code> 一行&lt;/td>
 &lt;td>完整 reasoning trace + 當下 context + tool list&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Token cost 為什麼高&lt;/td>
 &lt;td>&lt;code>tokens=15234&lt;/code>&lt;/td>
 &lt;td>Input / output / cached token 分項 + 每 turn 累積&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Why TTFT 5 秒&lt;/td>
 &lt;td>&lt;code>ttft=5012ms&lt;/code>&lt;/td>
 &lt;td>Prefill 跟 cache miss、prompt length、queue time&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tool 為什麼 retry 三次&lt;/td>
 &lt;td>&lt;code>tool error retry&lt;/code>&lt;/td>
 &lt;td>每次 error message + LLM 的判讀 + retry 策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Agent 為什麼 infinite loop&lt;/td>
 &lt;td>大量重複 log&lt;/td>
 &lt;td>每 iteration 的 context + 為什麼沒判 terminate&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>LLM tracing 用「結構化 span + parent-child 關係 + 標準化 attribute」直接編碼這些訊息。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/knowledge-cards/llm-tracing/" data-link-title="LLM Tracing" data-link-desc="把 LLM 應用的每次 LLM call / tool call / memory op 編成結構化 span、用 OpenTelemetry GenAI semantic conventions 標準化">LLM tracing</a> 把每次 LLM call / tool call / memory op / handoff 編成結構化 span、用 OpenTelemetry GenAI semantic conventions 標準化、是 production LLM 應用 debug / cost / quality 監控的事實標準。傳統 web app 的字串 logging 抓不到 LLM 應用的關鍵問題 — agent 為什麼選了那條路、reasoning trace 怎麼推導、tool call 為什麼 retry 三次、token 消耗為什麼比預期高 ×3。本章把 LLM tracing 的運作機制、OTel GenAI semconv、三大 use case（cost / latency / failure）跟 production eval 閉環拆成可操作的工程實務。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 LLM tracing 跟 traditional logging 的差異。</li>
<li>用 OpenTelemetry GenAI semantic conventions 設計 span 結構。</li>
<li>用 trace 做 cost / latency 監控跟 failure debug。</li>
<li>把 production trace 餵回 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">LLM-as-judge</a> 做品質迴路。</li>
<li>對自己應用判斷該用 self-host vs SaaS observability platform。</li>
</ol>
<h2 id="traditional-logging-為什麼不夠">Traditional logging 為什麼不夠</h2>
<p>LLM 應用的 debug 問題對傳統 logging 太抽象：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Logging 看到</th>
          <th>真正需要的資訊</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Agent 為什麼選 tool A 不選 tool B</td>
          <td><code>tool=A</code> 一行</td>
          <td>完整 reasoning trace + 當下 context + tool list</td>
      </tr>
      <tr>
          <td>Token cost 為什麼高</td>
          <td><code>tokens=15234</code></td>
          <td>Input / output / cached token 分項 + 每 turn 累積</td>
      </tr>
      <tr>
          <td>Why TTFT 5 秒</td>
          <td><code>ttft=5012ms</code></td>
          <td>Prefill 跟 cache miss、prompt length、queue time</td>
      </tr>
      <tr>
          <td>Tool 為什麼 retry 三次</td>
          <td><code>tool error retry</code></td>
          <td>每次 error message + LLM 的判讀 + retry 策略</td>
      </tr>
      <tr>
          <td>Agent 為什麼 infinite loop</td>
          <td>大量重複 log</td>
          <td>每 iteration 的 context + 為什麼沒判 terminate</td>
      </tr>
  </tbody>
</table>
<p>LLM tracing 用「結構化 span + parent-child 關係 + 標準化 attribute」直接編碼這些訊息。</p>
<h2 id="opentelemetry-genai-semantic-conventions">OpenTelemetry GenAI semantic conventions</h2>
<p>OTel GenAI semconv 是 2024-2025 標準化中的 trace schema。核心概念：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Trace（一次 user query 從進來到 response）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├── Span: gen_ai.agent.invocation（agent loop iteration 1）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  │     ├── Span: gen_ai.client.operation（LLM call 1）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  │     │     attrs: model, temperature, input_tokens, output_tokens, cache_read
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │     ├── Span: gen_ai.tool.execution（tool: read_file）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │     │     attrs: tool_name, input, output, duration
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │     └── Span: gen_ai.memory.read（retrieval）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │           attrs: query, top_k, similarity_scores
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├── Span: gen_ai.agent.invocation（iteration 2）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  │     └── ...
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └── Span: gen_ai.agent.terminate
</span></span><span class="line"><span class="ln">12</span><span class="cl">        attrs: reason, total_tokens, total_cost</span></span></code></pre></div><p>主要 attribute 分類：</p>
<table>
  <thead>
      <tr>
          <th>類別</th>
          <th>屬性 prefix</th>
          <th>典型內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Model</td>
          <td><code>gen_ai.request.*</code></td>
          <td>model, temperature, top_p, max_tokens, stream</td>
      </tr>
      <tr>
          <td>Usage</td>
          <td><code>gen_ai.usage.*</code></td>
          <td>input_tokens, output_tokens, cached_tokens</td>
      </tr>
      <tr>
          <td>Response</td>
          <td><code>gen_ai.response.*</code></td>
          <td>finish_reason, id</td>
      </tr>
      <tr>
          <td>Tool</td>
          <td><code>gen_ai.tool.*</code></td>
          <td>name, parameters, result</td>
      </tr>
      <tr>
          <td>Memory</td>
          <td><code>gen_ai.memory.*</code></td>
          <td>operation, store, query, hits</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td><code>gen_ai.cost.*</code></td>
          <td>usd, currency（vendor-specific）</td>
      </tr>
  </tbody>
</table>
<p>實作概要（Python 例）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">openinference.semconv.trace</span> <span class="kn">import</span> <span class="n">SpanAttributes</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">tracer</span> <span class="o">=</span> <span class="n">trace</span><span class="o">.</span><span class="n">get_tracer</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;gen_ai.client.operation&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="n">SpanAttributes</span><span class="o">.</span><span class="n">LLM_MODEL_NAME</span><span class="p">,</span> <span class="s2">&#34;claude-sonnet-4-6&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="n">SpanAttributes</span><span class="o">.</span><span class="n">LLM_TEMPERATURE</span><span class="p">,</span> <span class="mf">0.7</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">llm_client</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="o">=...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="n">SpanAttributes</span><span class="o">.</span><span class="n">LLM_TOKEN_COUNT_PROMPT</span><span class="p">,</span> <span class="n">response</span><span class="o">.</span><span class="n">usage</span><span class="o">.</span><span class="n">input_tokens</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="n">SpanAttributes</span><span class="o">.</span><span class="n">LLM_TOKEN_COUNT_COMPLETION</span><span class="p">,</span> <span class="n">response</span><span class="o">.</span><span class="n">usage</span><span class="o">.</span><span class="n">output_tokens</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;gen_ai.usage.cached_tokens&#34;</span><span class="p">,</span> <span class="n">response</span><span class="o">.</span><span class="n">usage</span><span class="o">.</span><span class="n">cache_read_tokens</span> <span class="ow">or</span> <span class="mi">0</span><span class="p">)</span></span></span></code></pre></div><p>實務上多用 framework auto-instrumentation（LangChain / LlamaIndex / Anthropic SDK 都有 OTel integration）、不必手寫 span。</p>
<h2 id="use-case-1cost-monitoring">Use case 1：Cost monitoring</h2>
<p>Trace 是 LLM 應用 cost 監控的核心 — token usage attribute 內建、不必另外算。</p>
<p>實作模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. Trace 端記錄 input_tokens / output_tokens / cached_tokens
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. Observability 平台用「per-model pricing table」算出 USD
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. Aggregate by：
</span></span><span class="line"><span class="ln">4</span><span class="cl">   - User（哪個 user 燒最多）
</span></span><span class="line"><span class="ln">5</span><span class="cl">   - Endpoint（哪條 API path 最貴）
</span></span><span class="line"><span class="ln">6</span><span class="cl">   - Feature（哪個 feature 最費 token）
</span></span><span class="line"><span class="ln">7</span><span class="cl">   - Time（哪天 spike）</span></span></code></pre></div><p>典型 dashboard 指標：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>直覺</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Total cost / day</td>
          <td>整體燒錢趨勢</td>
      </tr>
      <tr>
          <td>Cost per user</td>
          <td>找 power user 或 abuse</td>
      </tr>
      <tr>
          <td>Cost per request</td>
          <td>看單 request 平均 cost、設 alert</td>
      </tr>
      <tr>
          <td>Cached / total token ratio</td>
          <td><a href="/blog/llm/knowledge-cards/prompt-cache/" data-link-title="Prompt Cache" data-link-desc="重複出現的 prompt prefix 在推論伺服器或 LLM 服務端被 cache、後續 query 跳過 prefill、大幅降 cost 跟 TTFT">Prompt cache</a> 命中率</td>
      </tr>
      <tr>
          <td>Output / input token ratio</td>
          <td>輸出膨脹率、看 generation length 合理性</td>
      </tr>
  </tbody>
</table>
<h2 id="use-case-2latency--failure-debug">Use case 2：Latency / failure debug</h2>
<p>Trace 自然編碼 latency tree、能定位「哪個 span 卡」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">User query → response total: 5.2s
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── Agent iteration 1: 4.8s
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   ├── LLM call (claude): 4.2s     ← 主要時間在這
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   │   - prefill: 3.8s             ← prefill 太久、看 prompt 是否需要 cache
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   │   - generation: 0.4s
</span></span><span class="line"><span class="ln">6</span><span class="cl">│   ├── tool: read_file: 0.5s
</span></span><span class="line"><span class="ln">7</span><span class="cl">│   └── memory: retrieval: 0.1s
</span></span><span class="line"><span class="ln">8</span><span class="cl">└── Agent iteration 2: 0.4s</span></span></code></pre></div><p>從這 trace 看出「90% 時間在 prefill、開 prompt cache 可以救」、不必猜。</p>
<p>Failure debug：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">User query → response: ERROR
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── Agent iteration 1: success
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   └── LLM call: tool_call(run_bash, cmd=&#34;rm -rf /&#34;)
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── Agent iteration 2: failure
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   └── tool: run_bash: REJECTED by permission system
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── Agent fallback: error response
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">從 trace 看：tool call 被 permission 擋下、不是 LLM 自己亂、而是 user query 觸發危險 tool call、permission 正確擋下。</span></span></code></pre></div><p>對應 <a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">6.2 tool use 權限模型</a> 跟 <a href="/blog/llm/01-local-llm-services/hands-on/permission-boundary/" data-link-title="Hands-on：Ollama 改檔案 / 寫程式碼的權限邊界在哪" data-link-desc="四組對照實驗：Ollama 自己沒 FS / shell 權限、wrapper 才有；--dry-run / --confirm / --auto 三檔審查粒度的取捨">hands-on permission-boundary</a> 的判讀。</p>
<h2 id="use-case-3production-trace--eval-loop">Use case 3：Production trace → eval loop</h2>
<p>Production trace 是 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">LLM-as-judge</a> 的最佳資料來源：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Production users
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓ 產生 trace
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Trace storage（LangSmith / Phoenix / Langfuse）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓ filter（e.g. user thumbs-down 的 trace）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ↓ sample N 個
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">LLM-as-judge eval
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ↓ rubric scoring
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">找出系統性問題（哪類 query 品質差）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">10</span><span class="cl">改 system prompt / tool / agent loop
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">A/B test on production traces</span></span></code></pre></div><p>這是 <a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking</a> 提的「in-house benchmark」的具體 implementation — production trace 是最真實的 benchmark dataset。</p>
<h2 id="主流平台選型">主流平台選型</h2>
<table>
  <thead>
      <tr>
          <th>平台</th>
          <th>類型</th>
          <th>強項</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LangSmith</td>
          <td>SaaS（LangChain 系）</td>
          <td>Auto-instrumentation 強、UI 完整</td>
          <td>LangChain / LangGraph user</td>
      </tr>
      <tr>
          <td>Phoenix</td>
          <td>OSS + SaaS（Arize 系）</td>
          <td>OpenInference 標準、可 self-host</td>
          <td>想 self-host + OTel native</td>
      </tr>
      <tr>
          <td>Langfuse</td>
          <td>OSS + SaaS</td>
          <td>開源強、cost 監控好</td>
          <td>Cost / eval 中心、可 self-host</td>
      </tr>
      <tr>
          <td>Braintrust</td>
          <td>SaaS</td>
          <td>Eval + tracing 一體</td>
          <td>重 eval workflow 的 team</td>
      </tr>
      <tr>
          <td>Datadog APM</td>
          <td>SaaS</td>
          <td>跟 traditional APM 整合</td>
          <td>已用 Datadog、想統一監控</td>
      </tr>
      <tr>
          <td>Logfire</td>
          <td>SaaS（Pydantic）</td>
          <td>簡潔、Python 為主</td>
          <td>Python 為主、輕量</td>
      </tr>
      <tr>
          <td>Self-host OTel + Jaeger</td>
          <td>OSS</td>
          <td>完全 self-host、最便宜</td>
          <td>隱私敏感、cost 敏感、技術強</td>
      </tr>
  </tbody>
</table>
<p>判讀：</p>
<ol>
<li><strong>個人 / 小流量</strong>：SaaS 免費 tier（LangSmith / Langfuse / Phoenix）夠用</li>
<li><strong>隱私敏感（user data 不能離本機）</strong>：Self-host（Langfuse / Phoenix self-hosted、或 OTel + Jaeger）</li>
<li><strong>已有 observability stack</strong>：用 OTel + 現有 Datadog / Grafana、別再加一層</li>
<li><strong>重 eval</strong>：Braintrust / Langfuse 的 eval feature 強</li>
</ol>
<h2 id="跟-49-production-resource-的關係">跟 <a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 production resource</a> 的關係</h2>
<p>4.5 寫 production resource 的 6 個 dimension（concurrency / latency / cost / storage / observability / reliability）、其中 observability 是 4.5 點到、本章展開。讀者讀完 4.5 知道「需要 observability」、本章補「具體怎麼做」。</p>
<h2 id="設計失敗模式">設計失敗模式</h2>
<ol>
<li><strong>過度 instrument</strong>：每個 internal function 都加 span、trace overhead 大、實際 production noise 多</li>
</ol>
<p><strong>緩解</strong>：聚焦 LLM-related 跟跨 service 邊界、internal logic 不必 trace</p>
<ol start="2">
<li><strong>PII / sensitive data 寫進 span attribute</strong>：user prompt、API key、會被 SaaS 平台看到</li>
</ol>
<p><strong>緩解</strong>：Span attribute 過 PII filter、敏感資料 hash / masking、跟 <a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">6.4 跨雲端邊界</a> 結合</p>
<ol start="3">
<li><strong>不 sample</strong>：production 100% trace、storage / cost 爆</li>
</ol>
<p><strong>緩解</strong>：Production sample rate &lt; 10%、error / outlier 100% capture</p>
<ol start="4">
<li><strong>沒設 trace 保留期</strong>：trace 越累積越多、舊 trace 沒人看但仍付儲存</li>
</ol>
<p><strong>緩解</strong>：明確保留 policy（如 7-30 天 hot、之後 archive 或刪）</p>
<ol start="5">
<li><strong>Trace 不跟 metric 串</strong>：trace 是 sample、metric 是 aggregate、debug 要兩個一起看</li>
</ol>
<p><strong>緩解</strong>：cost / latency 也輸出 metric（Prometheus 等）、trace 補 specific instance debug</p>
<h2 id="何時不需要-tracing">何時不需要 tracing</h2>
<ol>
<li><strong>純 demo / 個人玩</strong>：log 字串夠用</li>
<li><strong>單一 LLM call、無 agent loop</strong>：簡單到 grep log 也能 debug</li>
<li><strong>隱私極敏感且不 self-host</strong>：trace 內容流向 SaaS 是邊界、評估 risk</li>
<li><strong>每 request 都 trace 的 overhead &gt; 收益</strong>：超低 latency 場景看是否 worth it</li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>LLM tracing 跟 traditional logging 的根本差異</li>
<li>結構化 span + parent-child 關係的 framing</li>
<li>Cost monitoring / latency debug / failure debug 三大 use case</li>
<li>Trace → eval 的閉環概念</li>
<li>5 個設計失敗模式</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>OTel GenAI semconv 的具體 attribute 名稱（仍在 stabilizing）</li>
<li>主流 SaaS 平台（每年 1-2 個新進入者）</li>
<li>Auto-instrumentation 的支援度（持續擴展）</li>
<li>跟具體 framework 的整合方式</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-judge 評估方法</a>、把 production trace 變成系統性 eval 的閉環。</p>
]]></content:encoded></item><item><title>4.21 LLM-as-Judge 評估方法</title><link>https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking-and-evaluation&lt;/a> 寫了 capability benchmark（MMLU、SWE-bench 等）跟 in-house benchmark 概念。但「自己工作流的真實案例該怎麼系統性 eval」這個操作層、4.14 點到沒展開。本章補上 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM-as-Judge&lt;/a> — production AI app 的事實標準 eval 方法、比 human eval 便宜 500-5000×、跟人類有 80%+ agreement、但要處理 bias。&lt;/p>
&lt;p>Judge 在 eval 系統中的定位：&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系&lt;/a> 把 eval 分三軸八象限、判斷哪個象限該用什麼工具——judge 的位置是 subjective 軸（沒 ground truth 的行為）、不是 objective 軸（有 ground truth 用 deterministic check 更便宜更準）。讀本章前先看 4.13 的軸誤選段、避開「全部 eval 都做成 judge」的常見反模式。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>區分 LLM-as-Judge、standard benchmark、human eval 三條 eval 路徑。&lt;/li>
&lt;li>設計可重現的 judge rubric（input / output / rubric / reasoning 四段）。&lt;/li>
&lt;li>用 pairwise vs direct scoring、知道何時用哪種。&lt;/li>
&lt;li>緩解三大 bias（position / verbosity / self-preference）。&lt;/li>
&lt;li>把 production &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">trace&lt;/a> 餵回 judge、形成自動 eval 閉環。&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼需要-llm-as-judge">為什麼需要 LLM-as-Judge&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14&lt;/a> 推「in-house benchmark 是 final test」、但操作層是個 gap：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Eval 痛點&lt;/th>
 &lt;th>LLM-as-Judge 解法&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Standard benchmark 跟自己 use case 不符&lt;/td>
 &lt;td>Judge 用自己 case 跑、rubric 自定義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Human eval 太貴 / 太慢&lt;/td>
 &lt;td>Judge 自動跑、$0.001-0.01 per item&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production trace 量大、人工看不完&lt;/td>
 &lt;td>Judge 跑 100% production trace 都可行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule-based eval 抓不到語意問題&lt;/td>
 &lt;td>Judge 能判斷「答案是否符合意圖、即使措辭不同」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Iteration 需要快速 feedback&lt;/td>
 &lt;td>Judge 幾分鐘跑完 100 items、prompt 改完馬上重測&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>主要 use case（重複 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM-as-Judge 卡片&lt;/a>）：in-house benchmark、production trace eval、A/B test、synthetic data quality。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 benchmarking-and-evaluation</a> 寫了 capability benchmark（MMLU、SWE-bench 等）跟 in-house benchmark 概念。但「自己工作流的真實案例該怎麼系統性 eval」這個操作層、4.14 點到沒展開。本章補上 <a href="/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM-as-Judge</a> — production AI app 的事實標準 eval 方法、比 human eval 便宜 500-5000×、跟人類有 80%+ agreement、但要處理 bias。</p>
<p>Judge 在 eval 系統中的定位：<a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系</a> 把 eval 分三軸八象限、判斷哪個象限該用什麼工具——judge 的位置是 subjective 軸（沒 ground truth 的行為）、不是 objective 軸（有 ground truth 用 deterministic check 更便宜更準）。讀本章前先看 4.13 的軸誤選段、避開「全部 eval 都做成 judge」的常見反模式。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>區分 LLM-as-Judge、standard benchmark、human eval 三條 eval 路徑。</li>
<li>設計可重現的 judge rubric（input / output / rubric / reasoning 四段）。</li>
<li>用 pairwise vs direct scoring、知道何時用哪種。</li>
<li>緩解三大 bias（position / verbosity / self-preference）。</li>
<li>把 production <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">trace</a> 餵回 judge、形成自動 eval 閉環。</li>
</ol>
<h2 id="為什麼需要-llm-as-judge">為什麼需要 LLM-as-Judge</h2>
<p><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14</a> 推「in-house benchmark 是 final test」、但操作層是個 gap：</p>
<table>
  <thead>
      <tr>
          <th>Eval 痛點</th>
          <th>LLM-as-Judge 解法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Standard benchmark 跟自己 use case 不符</td>
          <td>Judge 用自己 case 跑、rubric 自定義</td>
      </tr>
      <tr>
          <td>Human eval 太貴 / 太慢</td>
          <td>Judge 自動跑、$0.001-0.01 per item</td>
      </tr>
      <tr>
          <td>Production trace 量大、人工看不完</td>
          <td>Judge 跑 100% production trace 都可行</td>
      </tr>
      <tr>
          <td>Rule-based eval 抓不到語意問題</td>
          <td>Judge 能判斷「答案是否符合意圖、即使措辭不同」</td>
      </tr>
      <tr>
          <td>Iteration 需要快速 feedback</td>
          <td>Judge 幾分鐘跑完 100 items、prompt 改完馬上重測</td>
      </tr>
  </tbody>
</table>
<p>主要 use case（重複 <a href="/blog/llm/knowledge-cards/llm-as-judge/" data-link-title="LLM-as-Judge" data-link-desc="用 LLM 評估另一個 LLM 的輸出品質、production eval 的主流方法、500-5000× 成本降但有 bias 要處理">LLM-as-Judge 卡片</a>）：in-house benchmark、production trace eval、A/B test、synthetic data quality。</p>
<h2 id="judge-prompt-結構">Judge prompt 結構</h2>
<p>可重現的 judge 必須四段式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[Section 1: Task description]
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">你是 LLM 輸出品質評估員。要評估 coding assistant 對使用者請求的回答品質。
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">[Section 2: Input + Output to evaluate]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">User request: {input}
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Assistant response: {output}
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">[Section 3: Rubric（評分標準）]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">評分維度：
</span></span><span class="line"><span class="ln">10</span><span class="cl">1. Correctness（程式碼能否運作、邏輯是否正確）：1-5
</span></span><span class="line"><span class="ln">11</span><span class="cl">2. Style（是否符合 codebase convention）：1-5
</span></span><span class="line"><span class="ln">12</span><span class="cl">3. Completeness（是否完整解決 user request）：1-5
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">評分規則：
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 5：完美無瑕、可直接 merge
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 4：小修可用、整體正確
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 3：方向正確、需 substantial 修改
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 2：部分對、主要邏輯有錯
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 1：完全錯、誤導使用者
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">明確不加分：
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 冗長 / verbose（同樣正確的短答 = 長答）
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 道歉 / 開場白
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 「我希望這有幫助」這類禮貌話
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">[Section 4: Output format]
</span></span><span class="line"><span class="ln">27</span><span class="cl">請依下列 JSON 輸出：
</span></span><span class="line"><span class="ln">28</span><span class="cl">{
</span></span><span class="line"><span class="ln">29</span><span class="cl">  &#34;correctness&#34;: &lt;1-5&gt;,
</span></span><span class="line"><span class="ln">30</span><span class="cl">  &#34;style&#34;: &lt;1-5&gt;,
</span></span><span class="line"><span class="ln">31</span><span class="cl">  &#34;completeness&#34;: &lt;1-5&gt;,
</span></span><span class="line"><span class="ln">32</span><span class="cl">  &#34;reasoning&#34;: &#34;&lt;簡短解釋&gt;&#34;,
</span></span><span class="line"><span class="ln">33</span><span class="cl">  &#34;overall&#34;: &lt;1-5&gt;
</span></span><span class="line"><span class="ln">34</span><span class="cl">}</span></span></code></pre></div><p>關鍵設計原則：</p>
<ol>
<li><strong>Rubric 明確、可重現</strong>：用 1-5 scale + 每分明確定義、避免 judge 自由發揮</li>
<li><strong>明確列「不加分項」</strong>：vag rubric 容易讓 judge 加分長答 / 道歉 / 客套（verbosity bias）</li>
<li><strong>要求 reasoning</strong>：強迫 judge 寫評分理由、提升 calibration、後續可 debug</li>
<li><strong>Structured output</strong>：用 JSON / <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">structured output</a> 強制格式、後續可程式化處理</li>
</ol>
<h2 id="pairwise-vs-direct-scoring">Pairwise vs Direct scoring</h2>
<p>兩種主流評分方式：</p>
<h3 id="direct-scoring直接打分">Direct scoring（直接打分）</h3>
<p>給一個 (input, output)、judge 給絕對分數（1-5、1-10）。</p>
<p>優點：簡單、可看「絕對品質」隨時間改變
缺點：分數 calibration 不穩（不同 batch 跑、judge 可能 baseline drift）</p>
<h3 id="pairwise-comparison兩兩比較">Pairwise comparison（兩兩比較）</h3>
<p>給一個 input + 兩個 output（A、B）、judge 選哪個比較好。</p>
<p>優點：相對比較比絕對打分穩、適合 A/B testing
缺點：需要兩個 candidates、結果是「A &gt; B」不是「A 多好」</p>
<p>實務組合：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>適合方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Production quality monitoring</td>
          <td>Direct scoring（每個 trace 一個分數）</td>
      </tr>
      <tr>
          <td>Prompt / model A/B test</td>
          <td>Pairwise（A 跟 B 比）</td>
      </tr>
      <tr>
          <td>Fine-tune 前後比較</td>
          <td>Pairwise</td>
      </tr>
      <tr>
          <td>Regression detection</td>
          <td>Direct（跟 baseline 比較）</td>
      </tr>
      <tr>
          <td>Synthetic data filtering</td>
          <td>Direct（保留 ≥ 4 分）</td>
      </tr>
  </tbody>
</table>
<h2 id="三大-bias-跟緩解">三大 Bias 跟緩解</h2>
<h3 id="1-position-bias位置偏見">1. Position bias（位置偏見）</h3>
<p>Pairwise 比較時、judge 對「先出現」的 candidate 有偏好（通常偏 A）。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>換位置跑 2 次（A-B 跟 B-A）</li>
<li>只 count 兩次都偏 A 的為「prefer A」、不一致為「tie」</li>
<li>標準 LLM-as-Judge framework（如 MT-Bench）內建這做法</li>
</ul>
<h3 id="2-verbosity-bias冗長偏見">2. Verbosity bias（冗長偏見）</h3>
<p>Judge 傾向給「長答」高分、即使內容沒比「短答」更好。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>Rubric 明確寫「冗長不加分」「同樣正確的短答 = 長答」</li>
<li>長度 normalize：分數 = raw_score / log(length)</li>
<li>用 length-controlled benchmark（如 length-controlled AlpacaEval）</li>
</ul>
<h3 id="3-self-preference-bias自家偏好">3. Self-preference bias（自家偏好）</h3>
<p>Judge 偏好自家風格的答案（GPT 當 judge、偏好 GPT-style 輸出；Claude 當 judge、偏好 Claude-style）。</p>
<p><strong>緩解</strong>：</p>
<ul>
<li>用 3 個不同 family 的 judge model（如 Claude + GPT + Gemini）取多數</li>
<li>避免 judge 跟 test subject 同 model</li>
<li>用 reasoning model 當 judge（多家 reasoning model 共識更穩）</li>
</ul>
<h3 id="補充-biasformat-bias">補充 bias：Format bias</h3>
<p>Judge 對「有 markdown / 有 code block / 有結構」的答案偏好、即使內容沒比「純文字」更好。</p>
<p><strong>緩解</strong>：rubric 明確寫「格式不加分、看內容」。</p>
<h2 id="calibration校準">Calibration（校準）</h2>
<p>Judge 不該光信、要 calibrate：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 蒐集 100 個 (input, output) pair
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">2. Human eval（你自己或可信 human）打 ground truth 分數
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">3. Judge 跑同樣 100 個
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">4. 算 agreement rate：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   - Pairwise：judge 跟 human 同意比例（target &gt; 75%）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   - Direct scoring：Spearman correlation（target &gt; 0.7）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">5. 若 agreement 低：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - 改 rubric（更明確）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 換 judge model（更強）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 改 prompt（few-shot example）
</span></span><span class="line"><span class="ln">11</span><span class="cl">6. Calibrate 後的 judge 才能跑 production</span></span></code></pre></div><p>Calibration 是「judge 評什麼」跟「人類評什麼」對齊的步驟、跳過會讓 production eval 失準。</p>
<h2 id="跟-420-llm-tracing-的閉環">跟 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a> 的閉環</h2>
<p>Production trace + LLM-as-Judge 形成自動 eval pipeline：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Production users
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓ 產生 trace
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[LLM tracing 平台]（LangSmith / Phoenix / Langfuse / Braintrust）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓ filter：user thumbs-down、error、long latency 等 trace
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ↓ sample 100 個 / day
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">[LLM-as-Judge batch run]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ↓ rubric scoring
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">[Dashboard]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 哪類 query 品質下降
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 哪個 deployment version 品質差
</span></span><span class="line"><span class="ln">11</span><span class="cl">   - 哪個 user segment 體驗差
</span></span><span class="line"><span class="ln">12</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">13</span><span class="cl">觸發 alert / 改 prompt / 改 model / 回退
</span></span><span class="line"><span class="ln">14</span><span class="cl">   ↓ A/B test
</span></span><span class="line"><span class="ln">15</span><span class="cl">   ↓ Pairwise judge eval new vs old
</span></span><span class="line"><span class="ln">16</span><span class="cl">   ↓ Deploy 勝者</span></span></code></pre></div><p>這是 production LLM 應用 quality engineering 的標準閉環。</p>
<h2 id="judge-model-選型">Judge model 選型</h2>
<table>
  <thead>
      <tr>
          <th>Judge model 候選</th>
          <th>強項</th>
          <th>弱項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Sonnet / Opus</td>
          <td>reasoning 強、rubric 跟得緊</td>
          <td>Cost 中等</td>
      </tr>
      <tr>
          <td>GPT-5 / GPT-4o</td>
          <td>普及、tool-calling 強</td>
          <td>對自家 GPT 輸出有 self-preference</td>
      </tr>
      <tr>
          <td>Gemini Pro 2.5</td>
          <td>Long context 強、multi-modal</td>
          <td>rubric 跟得較鬆</td>
      </tr>
      <tr>
          <td>o1 / o3 / R1（reasoning model）</td>
          <td>推理能力強、判 nuanced case 穩</td>
          <td>Cost 高、latency 長</td>
      </tr>
      <tr>
          <td>本地 30B+ 模型（QwQ、DeepSeek-R1 distill）</td>
          <td>隱私強、cost 0</td>
          <td>能力上限低於雲端旗艦</td>
      </tr>
  </tbody>
</table>
<p>判讀：</p>
<ol>
<li><strong>大 stake / final QA</strong>：雲端旗艦 reasoning model</li>
<li><strong>大量 production trace eval</strong>：中等模型（GPT-4o / Sonnet）、cost / speed 平衡</li>
<li><strong>隱私敏感（user trace 不能送雲端）</strong>：本地 reasoning model（QwQ-32B / R1 distill）</li>
<li><strong>A/B test prompt 改進</strong>：用同個 judge 跑前後比對、保持 baseline</li>
</ol>
<h2 id="失敗模式">失敗模式</h2>
<ol>
<li><strong>Rubric 太 vague</strong>：judge 自由發揮、分數沒重複性</li>
</ol>
<p><strong>緩解</strong>：rubric 寫得像 unit test、每分有具體 criteria</p>
<ol start="2">
<li><strong>沒做 calibration</strong>：judge 跟 human agreement 沒驗、可能 systematically off</li>
</ol>
<p><strong>緩解</strong>：每次大改 rubric / 換 judge model 都重新 calibrate</p>
<ol start="3">
<li><strong>Sample 不代表 production</strong>：只 eval easy case、production 真實困難 case 沒覆蓋</li>
</ol>
<p><strong>緩解</strong>：用 stratified sampling（按 difficulty / user segment / feature 抽樣）</p>
<ol start="4">
<li><strong>Bias 沒緩解</strong>：position / verbosity / self-preference 直接 baked in</li>
</ol>
<p><strong>緩解</strong>：標準 framework（DeepEval / Inspect / Braintrust）內建 bias 緩解、用既有 framework 比 DIY 穩</p>
<ol start="5">
<li><strong>Judge cost 比預期高</strong>：production trace 全跑 judge、cost 爆</li>
</ol>
<p><strong>緩解</strong>：sample rate &lt; 10%、配合 <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">LLM tracing</a> 的 sampling</p>
<ol start="6">
<li><strong>Over-reliance on judge</strong>：忘記 judge 也會錯、把 judge 當絕對真理</li>
</ol>
<p><strong>緩解</strong>：高 stake 任務仍需 spot human review、judge 是 80% 解、不是 100%</p>
<h2 id="主流-framework">主流 framework</h2>
<table>
  <thead>
      <tr>
          <th>Framework</th>
          <th>特色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepEval</td>
          <td>OSS、Python、跟 pytest 整合</td>
      </tr>
      <tr>
          <td>Inspect（UK AI Safety）</td>
          <td>強 eval framework、reasoning model 友善</td>
      </tr>
      <tr>
          <td>Braintrust</td>
          <td>SaaS、eval + tracing 一體</td>
      </tr>
      <tr>
          <td>Langfuse evals</td>
          <td>OSS、跟 tracing 整合</td>
      </tr>
      <tr>
          <td>OpenAI evals</td>
          <td>OSS、Anthropic 也支援</td>
      </tr>
      <tr>
          <td>Patronus</td>
          <td>Production eval SaaS</td>
      </tr>
  </tbody>
</table>
<h2 id="何時不該用-llm-as-judge">何時不該用 LLM-as-Judge</h2>
<ol>
<li><strong>可機械驗證</strong>：unit test、exact match、output schema validation — 用 deterministic rule 比 judge 穩</li>
<li><strong>極小 dataset（&lt; 20 items）</strong>：直接 human eval、不必 judge</li>
<li><strong>判讀需要 domain expertise</strong>：醫療 / 法律 / 安全的 high-stake 判讀、judge 不該替代 expert</li>
<li><strong>Judge 能力 &lt; test subject</strong>：用 GPT-4o judge 評 o3 輸出、judge 看不懂 reasoning trace</li>
</ol>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>LLM-as-Judge 作為 production eval 主流方法的地位</li>
<li>四段式 judge prompt 結構（task / input-output / rubric / format）</li>
<li>Pairwise vs direct scoring 的取捨</li>
<li>三大 bias 分類跟緩解方法</li>
<li>Production trace → judge → action 的閉環</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>主流 framework（DeepEval / Inspect / Braintrust 等）</li>
<li>各 judge model 的具體能力（每代強模型）</li>
<li>Bias 的具體量化（人類 agreement 數字會隨時間 / 任務變）</li>
<li>新興 bias 跟緩解方法</li>
</ul>
<h2 id="下一步">下一步</h2>
<p>下一步：模組四到此覆蓋從基礎（4.0 prompt 技術光譜 / 4.1-4.2 RAG / 4.3 tool / 4.4 agent / 4.5 HITL）、協議與編排（4.6 protocols / 4.7 workflow / 4.8 multi-agent）、production 細節（4.9-4.12 resource / artifact / long-context / embedding）、到 eval 跟 production observability 閉環（4.13 eval 框架 / 4.14 benchmarking / 4.17-4.21 harness / caching / memory / tracing / judge）的完整應用層地圖。Hands-on 端到端案例見 <a href="/blog/llm/04-applications/hands-on/" data-link-title="4.x Hands-on：端到端案例" data-link-desc="把模組四的所有原理串成具體 case study：從 task decomposition、workflow 設計、eval 設計到 iteration loop">hands-on 子分類</a>。可進入 <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五</a> 看本地推論硬體、進入 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 看安全議題（特別是 <a href="/blog/llm/06-security/owasp-llm-top10-mapping/" data-link-title="6.6 OWASP LLM Top 10 對照圖" data-link-desc="把模組六的本地 dev 視角安全章節對照到 OWASP LLM Top 10 2025、補出個人 dev 場景跟企業合規溝通的共同詞彙">6.6 OWASP LLM Top 10 對照</a>、把 production eval 的安全議題對應到企業合規詞彙）、或回 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系</a> 看 judge 在 meta eval 框架中的定位。</p>
]]></content:encoded></item><item><title>4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀</title><link>https://tarrragon.github.io/blog/llm/04-applications/vector-storage-engineering/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/vector-storage-engineering/</guid><description>&lt;p>做完 RAG proof-of-concept 後最常見的問題是「現在的 in-memory 方案什麼時候該換成 vector database」。RAG pipeline 的儲存方案是&lt;strong>工程選擇、不是概念要件&lt;/strong>。&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理&lt;/a>定義的 retrieval + augmentation 二段式結構，跟 embedding 存在 pickle、flat file、SQLite、還是 Pinecone 無關 — 只要能「給一個 query vector，找到最相似的 chunk vectors」，retrieval 這一段就成立。&lt;/p>
&lt;p>本章整理 storage layer 的工程設計空間：什麼規模用什麼儲存、什麼訊號觸發升級、index 怎麼建怎麼更新、schema 怎麼設計、dependency chain 怎麼影響選型。全篇以一個約 2,700 篇 markdown（24K chunks）、Go 工具鏈的個人技術 blog 作為 running example（從 &lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &amp;#43; cosine retrieval &amp;#43; Ollama chat、validating 4.0 RAG 原理">pickle demo&lt;/a> 升級到 production 工具的過程）；Go-specific 的約束見「工程約束」段，Python 專案的路徑在各階段標示。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>本章涵蓋：&lt;/p>
&lt;ol>
&lt;li>RAG pipeline 的四個可替換層、判斷當前瓶頸落在哪一層。&lt;/li>
&lt;li>Corpus 規模跟使用模式對應的 storage backend 選擇。&lt;/li>
&lt;li>Index 的 build / update / rebuild 生命週期設計。&lt;/li>
&lt;li>ANN index 策略（HNSW / IVF / brute-force）的適用邊界。&lt;/li>
&lt;li>Storage 選型的 dependency 約束（語言生態、build chain、環境管理）。&lt;/li>
&lt;/ol>
&lt;h2 id="rag-pipeline-的四個可替換層">RAG pipeline 的四個可替換層&lt;/h2>
&lt;p>RAG 不是一個 monolithic 系統。從 query 進來到 augmented prompt 送進 LLM，經過四個獨立可替換的層：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>可替換選項範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Chunking strategy&lt;/td>
 &lt;td>把 corpus 切成 retrieval 單位&lt;/td>
 &lt;td>fixed-size / recursive / heading-aware / AST-based&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Embedding model&lt;/td>
 &lt;td>把 chunk text 轉成向量&lt;/td>
 &lt;td>nomic-embed-text / bge-large / jina-v3&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Storage backend&lt;/strong>&lt;/td>
 &lt;td>存向量 + metadata、支援相似度查詢&lt;/td>
 &lt;td>pickle / flat file / FAISS / SQLite-vec / Pinecone&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Retrieval algorithm&lt;/td>
 &lt;td>對 query vector 找 top-K 相似 chunk&lt;/td>
 &lt;td>brute-force cosine / HNSW / IVF / hybrid + rerank&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>四層各自演化、各自有不同的升級時機。Chunking 跟 embedding model 影響 retrieval &lt;strong>品質&lt;/strong>（找到的東西對不對）；storage backend 跟 retrieval algorithm 影響 retrieval &lt;strong>效能&lt;/strong>（找的速度跟規模上限）。&lt;/p></description><content:encoded><![CDATA[<p>做完 RAG proof-of-concept 後最常見的問題是「現在的 in-memory 方案什麼時候該換成 vector database」。RAG pipeline 的儲存方案是<strong>工程選擇、不是概念要件</strong>。<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a>定義的 retrieval + augmentation 二段式結構，跟 embedding 存在 pickle、flat file、SQLite、還是 Pinecone 無關 — 只要能「給一個 query vector，找到最相似的 chunk vectors」，retrieval 這一段就成立。</p>
<p>本章整理 storage layer 的工程設計空間：什麼規模用什麼儲存、什麼訊號觸發升級、index 怎麼建怎麼更新、schema 怎麼設計、dependency chain 怎麼影響選型。全篇以一個約 2,700 篇 markdown（24K chunks）、Go 工具鏈的個人技術 blog 作為 running example（從 <a href="/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &#43; cosine retrieval &#43; Ollama chat、validating 4.0 RAG 原理">pickle demo</a> 升級到 production 工具的過程）；Go-specific 的約束見「工程約束」段，Python 專案的路徑在各階段標示。</p>
<h2 id="本章目標">本章目標</h2>
<p>本章涵蓋：</p>
<ol>
<li>RAG pipeline 的四個可替換層、判斷當前瓶頸落在哪一層。</li>
<li>Corpus 規模跟使用模式對應的 storage backend 選擇。</li>
<li>Index 的 build / update / rebuild 生命週期設計。</li>
<li>ANN index 策略（HNSW / IVF / brute-force）的適用邊界。</li>
<li>Storage 選型的 dependency 約束（語言生態、build chain、環境管理）。</li>
</ol>
<h2 id="rag-pipeline-的四個可替換層">RAG pipeline 的四個可替換層</h2>
<p>RAG 不是一個 monolithic 系統。從 query 進來到 augmented prompt 送進 LLM，經過四個獨立可替換的層：</p>
<table>
  <thead>
      <tr>
          <th>層</th>
          <th>責任</th>
          <th>可替換選項範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chunking strategy</td>
          <td>把 corpus 切成 retrieval 單位</td>
          <td>fixed-size / recursive / heading-aware / AST-based</td>
      </tr>
      <tr>
          <td>Embedding model</td>
          <td>把 chunk text 轉成向量</td>
          <td>nomic-embed-text / bge-large / jina-v3</td>
      </tr>
      <tr>
          <td><strong>Storage backend</strong></td>
          <td>存向量 + metadata、支援相似度查詢</td>
          <td>pickle / flat file / FAISS / SQLite-vec / Pinecone</td>
      </tr>
      <tr>
          <td>Retrieval algorithm</td>
          <td>對 query vector 找 top-K 相似 chunk</td>
          <td>brute-force cosine / HNSW / IVF / hybrid + rerank</td>
      </tr>
  </tbody>
</table>
<p>四層各自演化、各自有不同的升級時機。Chunking 跟 embedding model 影響 retrieval <strong>品質</strong>（找到的東西對不對）；storage backend 跟 retrieval algorithm 影響 retrieval <strong>效能</strong>（找的速度跟規模上限）。</p>
<p>常見的認知混淆是把「RAG」跟「vector database」綁在一起。這個綁定在 production 規模可能合理（10M chunks 不用 vector DB 很難做），但在小規模場景會導致過度工程 — 1500 個 chunks 用 Pinecone 就像用 PostgreSQL 存 10 筆 config。</p>
<h2 id="storage-backend-的演化階梯">Storage backend 的演化階梯</h2>
<p>Storage backend 的選擇是<strong>規模驅動</strong>的工程決策。每個階段都能做 RAG，差別在效能、持久性、query 能力。以下規模閾值基於 768 維 embedding、單機常見配置的經驗判斷，切點依向量維度與硬體規格移動；實測數字（如 20 chunks/sec）另行標示：</p>
<h3 id="階段一in-memorypickle--python-list">階段一：In-memory（pickle / Python list）</h3>
<p>把所有 chunk embeddings 載入記憶體，brute-force 算 cosine similarity。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：&lt; 10K chunks
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：cosine 計算 &lt; 2ms（numpy BLAS、in-memory）；file-based 實作加 I/O 載入時間
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：pickle 檔、每次啟動重載
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：零 dependency、程式碼 &lt; 50 行、debug 容易
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：記憶體受限、無 metadata filter、無 incremental update</span></span></code></pre></div><p>本 blog 的 <a href="/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &#43; cosine retrieval &#43; Ollama chat、validating 4.0 RAG 原理">rag-demo</a> 就在這個階段：71 篇 markdown、463 chunks、pickle 儲存、22 秒索引、query &lt; 10ms。概念驗證完全夠用。</p>
<h3 id="階段二flat-filebinary-embedding-store">階段二：Flat file（binary embedding store）</h3>
<p>把 embeddings 存成 binary 格式（而非 Python pickle），配 JSON metadata index。跟階段一的差異是 <strong>language-agnostic persistence</strong> — 不綁定 Python 的 pickle 格式、Go / Rust / Node 都能讀。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：&lt; 10K chunks
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：cosine 計算 &lt; 2ms；加 file I/O 載入（70MB vectors ≈ 150ms Go / &lt; 50ms mmap）
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：binary file + metadata JSON、可 rebuild
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：跨語言、單檔案部署、不需要 DB server
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：brute-force O(n)、metadata filter 靠程式碼、schema 演化需 rebuild（換 embedding 模型要重建整個 index）、無 transaction 保護（binary 損毀靠 rebuild 復原）、每次 query 重載 file 是效能瓶頸</span></span></code></pre></div><p>Running example 的 blog 選了這個方案。驅動選擇的是<strong>工具鏈約束</strong>：該 blog 的核心工具是 Go（單 binary 分發的 lint / fmt 工具），用 pickle 就綁定 Python runtime、其他維護者 clone 後多一步環境設定（同規模下效能無差異）。Binary flat file 讓 Go 工具直接讀寫、維持單 binary 分發。Python 專案留在 pickle 完全合理，規模到 10K 再跳階段三 FAISS 更自然。</p>
<h3 id="階段三embedded-libraryfaiss--hnswlib--annoy">階段三：Embedded library（FAISS / HNSWLib / Annoy）</h3>
<p>引入 ANN（Approximate Nearest Neighbor）index，查詢從 O(n) 變成 O(log n)。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：10K - 100K chunks
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：&lt; 5ms（HNSW sublinear）
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：index 檔案、可 rebuild
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：不需要 server、嵌入應用 process
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：需要安裝 library（FAISS 有平台相依的 wheel）、index build 較慢</span></span></code></pre></div><p>升級訊號：brute-force latency 開始感覺到（&gt; 50ms）、或 corpus 大到記憶體載入太慢。1M chunks × 768 dim × 4 bytes = 3GB，載入開始有感。</p>
<h3 id="階段三piggyback-既有-dbpgvector--redis-vector">階段三½：Piggyback 既有 DB（pgvector / Redis vector）</h3>
<p>已有 PostgreSQL 或 Redis 的專案有一條跳板路徑：直接在既有 DB 加向量能力、不引入新 server。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：10K - 1M chunks（pgvector）、10K - 500K（Redis vector）
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：&lt; 10ms（HNSW、同 DB process）
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：DB 管理、有 transaction / WAL / backup
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：不增 server、SQL metadata filter 原生支援、既有維運流程直接沿用
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：DB 本身要夠大（向量索引佔額外記憶體）、效能跟 DB 負載共享</span></span></code></pre></div><p>升級訊號：已有 Postgres / Redis、需要 metadata filtering、但不想維運獨立 vector DB server。pgvector 讓「有 SQL 能力 + 有向量搜尋」在同一個 DB 完成；Redis vector（RediSearch）適合已有 Redis 且延遲敏感的場景。</p>
<p>這條路徑跟階段四的差異：階段四（Qdrant / Weaviate）是專用 vector DB、向量搜尋效能更高、但多一個 server 維運。Piggyback 路徑犧牲一些向量搜尋效能、換來零新增 server 的維運簡化。選擇取決於「向量搜尋是核心能力（階段四）、還是輔助功能（piggyback）」。</p>
<h3 id="階段四self-hosted-vector-databaseqdrant--weaviate--milvus">階段四：Self-hosted vector database（Qdrant / Weaviate / Milvus）</h3>
<p>獨立 server process，專精向量搜尋，支援 metadata filtering、incremental update、backup、replication。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：100K - 10M chunks
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：&lt; 10ms（HNSW + 網路 overhead）
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：server 管理、disk-based
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：metadata filter（SQL-like）、REST/gRPC API、可水平擴展
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：需要維運 server、佔用資源、增加系統複雜度</span></span></code></pre></div><p>升級訊號：需要 metadata filtering（「只搜 report/ 下的卡片」且頻率高）、需要多 process 並發 query、需要 incremental update 而非全量 rebuild。</p>
<p>典型場景是十人以上的團隊共用 RAG 知識庫：多人同時 query、文件隨 sprint 密集更新、需要按 project / team / access level 做 metadata filter。單人或小團隊的 side project 通常停在階段二或三就夠。回退路徑是「關掉 server、退回 embedded library」— 向量跟 metadata 仍在、只是失去 incremental update 跟 REST API。</p>
<h3 id="階段五hosted-saaspinecone--weaviate-cloud--qdrant-cloud">階段五：Hosted SaaS（Pinecone / Weaviate Cloud / Qdrant Cloud）</h3>
<p>由 vendor 管理的 vector database，免維運。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用規模：&gt; 10M chunks、或不想維運
</span></span><span class="line"><span class="ln">2</span><span class="cl">延遲：10-50ms（加上網路 round trip）
</span></span><span class="line"><span class="ln">3</span><span class="cl">持久性：vendor 管理
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：免維運、自動擴展、SLA
</span></span><span class="line"><span class="ln">5</span><span class="cl">限制：cost、vendor lock-in、資料離開本地</span></span></code></pre></div><p>升級訊號：corpus 超過單機記憶體（10M+ chunks 的 HNSW index 含 graph overhead 可達數十 GB）、或團隊沒有 infra 維運能力。</p>
<p>典型場景是跨國 SaaS 產品的 knowledge base：文件數百萬、多語言、需要 geo-distributed 部署。此規模下 self-hosted 的維運成本（on-call、capacity planning、backup）可能高於 SaaS 訂閱。風險是 vendor lock-in — 切換 vendor 要 re-index 全量資料、migration 成本跟 corpus 大小成正比。回退計畫是保留 ingest pipeline 的 vendor-agnostic 部分（chunking + embedding），只替換 storage layer。</p>
<h3 id="階梯的核心判讀">階梯的核心判讀</h3>
<p>每階段的升級都帶來新的 dependency 跟維護成本。判讀「該不該升級」看三個訊號：</p>
<ol>
<li><strong>目前這個階段有具體痛點嗎？</strong> 沒有就不升級。</li>
<li><strong>升級解的是效能瓶頸還是功能缺口？</strong> 效能瓶頸先量測再決定；功能缺口（如 metadata filter）看使用頻率。</li>
<li><strong>升級引入的 dependency 成本能接受嗎？</strong> 單人 blog 加一個 server process 的維護成本跟十人團隊不同。</li>
</ol>
<p>常見路徑速查：Python 小型 side project 留在 pickle（階段一），規模到 10K 再上 FAISS（階段三）；Go 專案跳階段二（flat file）避免 Python dependency；已有 Postgres 的專案直接評估 pgvector（階段三½）；已有 Docker 的團隊直接評估階段四（vector DB container）。</p>
<p>常見誤解：「FAISS 跟 Pinecone 選哪個」— 兩者差在規模量級（FAISS 是嵌入式 library、適合 &lt; 100K；Pinecone 是 hosted SaaS、適合 &gt; 10M 或免維運），不是同層級的互斥選項。</p>
<h3 id="同-corpus-實測比較">同 corpus 實測比較</h3>
<p>以下是同一個 corpus（24,216 chunks、768 維、nomic-embed-text）在四種 storage 方案的實測結果（2026-07 macOS Apple Silicon）：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>演化階段</th>
          <th>Ingest（純 storage）</th>
          <th>Query（median）</th>
          <th>Index 大小</th>
          <th>主要 dependency</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go + flat file</td>
          <td>階段二</td>
          <td>—</td>
          <td>151ms</td>
          <td>97.4 MB</td>
          <td>Go binary + Ollama</td>
      </tr>
      <tr>
          <td>Python sqlite-vec</td>
          <td>階段三½</td>
          <td>2.9s</td>
          <td>19ms</td>
          <td>75.3 MB</td>
          <td>Python + sqlite-vec</td>
      </tr>
      <tr>
          <td>Python FAISS flat</td>
          <td>階段三</td>
          <td>40ms</td>
          <td>1.8ms</td>
          <td>in-memory</td>
          <td>Python + faiss-cpu</td>
      </tr>
      <tr>
          <td>Python FAISS HNSW</td>
          <td>階段三</td>
          <td>23.3s</td>
          <td>0.5ms</td>
          <td>in-memory</td>
          <td>Python + faiss-cpu</td>
      </tr>
  </tbody>
</table>
<p>這張表揭露三個容易被理論估計遮蓋的事實：</p>
<p><strong>延遲的瓶頸在 I/O 和實作、不在演算法</strong>。Go flat file 的 151ms 裡，cosine 計算約 50ms、其餘約 100ms 是檔案載入（70MB vectors + 7MB metadata）。FAISS flat 用 numpy BLAS 做同樣的 brute-force cosine，純計算只要 1.8ms — 計算層差約 28 倍（Go pure loop vs BLAS 向量化指令），加上 I/O 差異後端到端差 84 倍。</p>
<p><strong>HNSW 的 query 加速在此規模 ROI 低，但原因要看對</strong>。FAISS HNSW query 0.5ms vs flat 1.8ms，每次查詢省 1.3ms；但 HNSW build 要 23.3s。如果每天查 100 次，要 179 天才回本 build 成本。在 10 萬+ chunks 規模這個比例會翻轉。</p>
<p><strong>sqlite-vec 的 19ms 是「DB overhead 換功能」的真實代價</strong>。比 FAISS flat 慢 10 倍，但多了 SQL metadata filter、transaction 保護、disk persistence — 不需要另起 server。這個 trade-off 在「需要 filter 但不想維運 server」的場景有意義。</p>
<h2 id="ann-index-策略">ANN index 策略</h2>
<p>Storage backend 到了階段三以上，需要選 ANN（Approximate Nearest Neighbor）index 策略。<a href="/blog/llm/knowledge-cards/vector-database/" data-link-title="Vector Database" data-link-desc="為高維向量 (embedding) 設計的儲存 &#43; 近似最近鄰 (ANN) 檢索系統：RAG 從 prototype 跨到 production 的關鍵元件">Vector database 卡</a>列了三種主流演算法，本段補充工程判讀。</p>
<h3 id="brute-forceexhaustive-search">Brute-force（exhaustive search）</h3>
<p>對 query vector 跟所有 stored vectors 算 cosine similarity，取 top-K。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">時間複雜度：O(n × d)（n = chunk 數、d = 向量維度）
</span></span><span class="line"><span class="ln">2</span><span class="cl">精確度：100%（exact nearest neighbor）
</span></span><span class="line"><span class="ln">3</span><span class="cl">記憶體：n × d × 4 bytes（float32）
</span></span><span class="line"><span class="ln">4</span><span class="cl">適用：&lt; 10K chunks</span></span></code></pre></div><p>1500 chunks × 768 dim 的 brute-force，現代 CPU 做一次 cosine similarity sweep 大約 1-5ms。在這個規模，HNSW 的建 index 時間（秒級）反而比它省下的查詢時間（毫秒級）長。</p>
<h3 id="hnswhierarchical-navigable-small-world">HNSW（Hierarchical Navigable Small World）</h3>
<p>建多層隨機圖，查詢時從稀疏高層往密集低層跳，sublinear 找到近似最近鄰。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">時間複雜度：O(log n × d)
</span></span><span class="line"><span class="ln">2</span><span class="cl">精確度：95-99%（approximate、可調 ef_search 參數換精度）
</span></span><span class="line"><span class="ln">3</span><span class="cl">記憶體：n × d × 4 bytes + graph overhead（通常 1.2-1.5x）
</span></span><span class="line"><span class="ln">4</span><span class="cl">Build 時間：O(n × log n)、比 brute-force 慢
</span></span><span class="line"><span class="ln">5</span><span class="cl">適用：10K - 10M chunks、記憶體充足</span></span></code></pre></div><p>HNSW 是目前 vector DB 的主流 index。工程取捨在兩個參數：<code>ef_construction</code>（build 精度、越高越慢但 graph 品質越好）跟 <code>ef_search</code>（query 精度、越高越慢但 recall 越高）。多數 vector DB 的預設值已經針對「recall &gt; 95%」調過。</p>
<h3 id="ivfinverted-file-index">IVF（Inverted File Index）</h3>
<p>先把向量 K-means 分群，query 時只搜最近的幾個群。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">時間複雜度：O(n/k × d)（k = 群數、nprobe = 搜幾個群）
</span></span><span class="line"><span class="ln">2</span><span class="cl">精確度：依 nprobe、通常 90-98%
</span></span><span class="line"><span class="ln">3</span><span class="cl">記憶體：可以 disk-based（比 HNSW 省）
</span></span><span class="line"><span class="ln">4</span><span class="cl">Build 時間：K-means 收斂需要時間
</span></span><span class="line"><span class="ln">5</span><span class="cl">適用：&gt; 1M chunks、記憶體受限、可接受較低 recall</span></span></code></pre></div><p>IVF 在超大規模（10M+）的 disk-based 場景有優勢，實務常配 product quantization（PQ）壓縮向量換記憶體。PQ / scalar quantization 跟 index 演算法（HNSW / IVF）正交 — 是記憶體受限時的壓縮手段，可疊加在任一 index 上。消費級場景通常不需要 quantization。</p>
<h3 id="判讀流程">判讀流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Corpus 規模？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── &lt; 10K chunks   → Brute-force（此規模無需再評估）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── 10K - 100K     → HNSW（如果記憶體夠）或 brute-force（如果 latency 可接受）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── 100K - 10M     → HNSW（主流）
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── &gt; 10M          → IVF 或 HNSW + sharding</span></span></code></pre></div><p>規模是第一軸。兩個修正軸在同規模下改變選擇：</p>
<ul>
<li><strong>Dependency constraint</strong>（見「工程約束」段）：規模小但工具鏈排除某些 storage（如 Go 專案排除 CGo dependency）→ 從可行選項中選。</li>
<li><strong>Metadata filter 需求</strong>：規模小但高頻需要按 section / tag 過濾 → 跳過 embedded library、直接評估 vector DB 或 code filter。</li>
</ul>
<p>一個常見的過度工程信號：corpus 只有幾千筆但花時間調 HNSW 的 <code>ef_construction</code>。實測數據（24K chunks）：FAISS HNSW query 0.5ms vs flat 1.8ms、每次省 1.3ms，但 HNSW build 要 23.3s。每天查 100 次要 179 天回本 build 成本（23.3s ÷ 0.13s/天）。此規模的 brute-force 絕對延遲已在感知閾值下，HNSW 的優化收益趨近零。</p>
<p>判讀流程之外還有一個容易忽略的變數：<strong>實作語言的計算效能差異</strong>。同一個 brute-force cosine，numpy BLAS 做 24K × 768 只要 1.8ms，Go pure cosine 做同樣運算約需 50-80ms（不含 I/O）。選 storage 方案時如果估「brute-force &lt; 10ms」、前提是用了向量化計算的 library；pure Go / pure Python loop 會慢一到兩個數量級。</p>
<h2 id="index-生命週期">Index 生命週期</h2>
<p>Index 的 build / update / rebuild 流程影響日常維護成本。</p>
<h3 id="full-rebuild">Full rebuild</h3>
<p>每次從 corpus 全量重建 index：walk 所有檔案 → chunk → embed → store。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用：corpus 小（&lt; 10K chunks）、更新頻率低（每週幾次）
</span></span><span class="line"><span class="ln">2</span><span class="cl">優點：邏輯最簡單、index 跟 corpus 保證一致
</span></span><span class="line"><span class="ln">3</span><span class="cl">成本：依 corpus 規模線性成長（本地 Ollama sequential embedding 約 100 chunks/sec、24K chunks ≈ 4 分鐘）</span></span></code></pre></div><p>Running example 的 blog 選 full rebuild：2,738 篇 markdown 產生 24K chunks，全量 ingest 在本地 Ollama 約 4 分鐘。每天變動 0-3 篇，rebuild 頻率跟 <code>git push</code> 對齊就夠。</p>
<h3 id="incremental-update">Incremental update</h3>
<p>只處理有變動的檔案：偵測 diff → 刪除舊 chunks → 重新 chunk + embed 變動檔 → 插入新 chunks。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">適用：corpus 大（&gt; 10K chunks）、更新頻繁
</span></span><span class="line"><span class="ln">2</span><span class="cl">優點：只處理 delta、省 embedding API cost
</span></span><span class="line"><span class="ln">3</span><span class="cl">複雜度：需要 chunk ID 穩定（file path + chunk offset）、刪除 orphan</span></span></code></pre></div><p>Incremental update 的工程難點是 <strong>chunk ID 穩定性</strong>。如果 chunking 策略對同一個檔案的切法會因為上游內容變動而改變（例如段落感知 chunking，加一段就改變後續所有 chunk 邊界），「只更新變動的 chunk」就需要 diff 整個 chunk 序列，邏輯接近全量重建。</p>
<p>判讀「該不該做 incremental」：</p>
<ul>
<li>Embedding 是 cost 瓶頸嗎？本地 Ollama 的 embedding 幾乎免費（約 50ms/chunk、sequential）；cloud API（OpenAI text-embedding-3-small 約 $0.02/1M tokens、Cohere 類似）按 token 計費、corpus 大時差異顯著。</li>
<li>全量 rebuild 的時間能接受嗎？1500 chunks 在本地約 60-90 秒可以接受；15 萬 chunks 約 2 小時可能不行。</li>
<li>能容忍短暫不一致嗎？Full rebuild 期間 index 可能是舊版；incremental update 隨改隨更新。</li>
</ul>
<h3 id="rebuild-trigger">Rebuild trigger</h3>
<p>不管 full 或 incremental，都要決定「什麼觸發 rebuild」：</p>
<table>
  <thead>
      <tr>
          <th>Trigger 類型</th>
          <th>做法</th>
          <th>適合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>手動</td>
          <td><code>blogsearch ingest</code> 手動跑</td>
          <td>個人工具</td>
      </tr>
      <tr>
          <td>Git hook</td>
          <td>pre-push 或 post-commit 自動 rebuild</td>
          <td>小團隊</td>
      </tr>
      <tr>
          <td>CI/CD</td>
          <td>push to main 後 CI job 跑 ingest</td>
          <td>多人協作</td>
      </tr>
      <tr>
          <td>File watcher</td>
          <td>inotify / fsevents 偵測 content/ 變動自動更新</td>
          <td>開發中即時回饋</td>
      </tr>
  </tbody>
</table>
<p>Trigger 跟團隊協作模式對齊：單人用手動；多人但 review cycle 長（每天幾次 push）用 Git hook 或 CI/CD；開發中密集寫作想即時看 retrieval 結果用 file watcher。Git hook 跟 CI/CD 的差異在 rebuild 跑在本地（hook）還是 server（CI）— 本地 rebuild 快（&lt; 2 分鐘）就用 hook、慢就推到 CI 避免 push 卡住。</p>
<p>本 blog 目前用手動 trigger — 維護者在寫新文章、需要查相關內容時跑 <code>blogsearch ingest</code>，日常使用頻率不高、不需要即時同步。</p>
<h2 id="schema-設計">Schema 設計</h2>
<p>每個 chunk 存的不只向量。至少有三類資料需要管理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">chunk = {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    vector:   float32[768],       // embedding
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    text:     string,             // 原始文字（generation 用）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    metadata: {                   // filtering + 溯源
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        source:    string,        // 來源檔案路徑
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        section:   string,        // 所屬 section（llm/ / backend/ / report/）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        title:     string,        // 文章標題
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        date:      string,        // 文章日期
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        tags:      []string,      // 文章 tags
</span></span><span class="line"><span class="ln">10</span><span class="cl">        chunk_idx: int,           // 該檔案內的第幾個 chunk
</span></span><span class="line"><span class="ln">11</span><span class="cl">    }
</span></span><span class="line"><span class="ln">12</span><span class="cl">}</span></span></code></pre></div><h3 id="metadata-filter-的設計取捨">Metadata filter 的設計取捨</h3>
<p>Metadata filter 是「在向量相似度之外加條件」：例如「只搜 report/ 下的卡片」「只搜 2026 年之後的文章」。</p>
<p>兩種實作路線：</p>
<p><strong>Code filter</strong>：先做 brute-force / ANN 取 top-N（N 大於最終需要的 K），再用程式碼 filter metadata，取 top-K。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">優點：不需要 DB、flat file 就能做
</span></span><span class="line"><span class="ln">2</span><span class="cl">限制：filter 比例高時（如 90% 被 filter 掉）需要取很大的 N
</span></span><span class="line"><span class="ln">3</span><span class="cl">適用：filter 條件少、filter 比例低（&lt; 50%）</span></span></code></pre></div><p><strong>DB filter</strong>：在 vector DB 的 query 語法中直接加 metadata condition（如 Qdrant 的 <code>must</code> filter）。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">優點：filter 在 index 層執行、效率高
</span></span><span class="line"><span class="ln">2</span><span class="cl">限制：需要 vector DB、schema 要先定好
</span></span><span class="line"><span class="ln">3</span><span class="cl">適用：filter 條件多、filter 比例高、query 頻繁</span></span></code></pre></div><p>本 blog 選 code filter：section 只有幾個值（llm / backend / report / work-log），filter 比例低，brute-force top-20 再 filter 到 top-5 就夠。</p>
<h3 id="hybrid-search-的-schema-考量">Hybrid search 的 schema 考量</h3>
<p><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a>介紹了 <a href="/blog/llm/knowledge-cards/hybrid-search/" data-link-title="Hybrid Search" data-link-desc="把字面 retrieval（BM25）跟語意 retrieval（embedding）的結果用 RRF 等方法合併、補單一路線的盲點">hybrid search</a>（BM25 關鍵字精確匹配 + embedding 語意相似度的加權合併），在 storage 層的 schema 影響是：需要同時存<strong>原始文字</strong>（給 BM25）跟<strong>向量</strong>（給 embedding search）。</p>
<ul>
<li>In-memory / flat file：BM25 自己實作（或用 library），原始文字本來就存了。</li>
<li>Vector DB：多數支援 hybrid search（Qdrant 有 full-text index、Weaviate 有 BM25 + vector 合併查詢）。</li>
<li>SQLite-vec + FTS5：SQLite 原生支援 full-text search（FTS5），配 sqlite-vec 可以在同一個 DB 做 hybrid search。</li>
</ul>
<p>判讀「要不要 hybrid」：先只用 embedding search，retrieval 品質不夠再加 BM25。多數場景 embedding-only 已經夠用；keyword 精確匹配需求高的場景（如搜特定 error message、RFC 編號）才需要 BM25 補。</p>
<h2 id="工程約束dependency-chain-與-build-system">工程約束：dependency chain 與 build system</h2>
<p>Storage 選型不只看功能跟效能，還受<strong>工程約束</strong>影響 — 包括 dependency chain 跟實作語言的計算效能。以下用 Go 專案示範這兩類 constraint 的思考方式；Python / Docker / 前端專案的 constraint 不同、結論見「不同專案的 constraint 不同」段。</p>
<h3 id="case-studygo-專案為什麼不選-sqlite-vec">Case study：Go 專案為什麼不選 SQLite-vec</h3>
<p>SQLite-vec 是 SQLite 的 C extension，提供向量搜尋能力。功能上完全符合需求。但在 Go 生態裡，CGo（Go 呼叫 C 程式碼的橋接機制）引入額外代價：</p>
<table>
  <thead>
      <tr>
          <th>SQLite Go binding</th>
          <th>能用 sqlite-vec？</th>
          <th>代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>modernc.org/sqlite</code>（純 Go）</td>
          <td>不能</td>
          <td>純 Go 重寫的 SQLite 不支援載入 C extension</td>
      </tr>
      <tr>
          <td><code>mattn/go-sqlite3</code>（CGo binding）</td>
          <td>能</td>
          <td>需要 C compiler、交叉編譯困難、build 時間增加</td>
      </tr>
  </tbody>
</table>
<p>選 <code>mattn/go-sqlite3</code> 意味著：</p>
<ul>
<li>其他維護者 clone 後需要裝 C compiler（macOS 要 Xcode CLI tools、Linux 要 gcc）</li>
<li>CI/CD 需要配 CGo 環境</li>
<li>單 binary 分發的優勢消失（動態連結 libc）</li>
</ul>
<p>這些代價在大團隊可能值得，但對一個個人 blog 的工具來說，dependency chain 的複雜度超過功能收益。</p>
<h3 id="判讀-dependency-約束的反射">判讀 dependency 約束的反射</h3>
<p>每個 storage 選項都帶一條 dependency chain。評估時要問：</p>
<ol>
<li><strong>新維護者 clone 後要裝什麼？</strong> pip install / go build / docker pull / apt install？</li>
<li><strong>CI 要加什麼？</strong> C compiler / Python runtime / Docker image？</li>
<li><strong>哪些平台要支援？</strong> macOS / Linux / Windows？交叉編譯需求？</li>
<li><strong>runtime dependency 還是 build-time dependency？</strong> Runtime（要 server 跑著）的維護成本遠高於 build-time（build 完就不需要了）。</li>
</ol>
<p>本 blog 的 constraint 是：Go 單 binary、clone 後 <code>go build</code> 即可、不需要外部 server。這個 constraint 排除了 CGo dependency 跟任何 server-based 方案，把選項收窄到 flat file。代價是 Go pure cosine + file I/O 讓 query 延遲（151ms）比 Python FAISS（1.8ms）慢 80 倍 — 對 CLI 工具可接受，對高頻 API server 則是致命瓶頸。選型時把 dependency chain 跟計算效能一起評估，避免「dependency 輕但效能差」或「效能好但 dependency 重」的單軸判斷。</p>
<h3 id="不同專案的-constraint-不同">不同專案的 constraint 不同</h3>
<p>這個 constraint 是本 blog 的特定情境。其他專案的 constraint 可能完全不同：</p>
<ul>
<li>Python 生態的專案：pip install 是標準流程，但 FAISS 的 CPU/GPU wheel 有平台相依（M1 Mac 需要 <code>faiss-cpu</code> 特定版本、glibc 版本影響 Linux wheel），不是完全零 constraint。</li>
<li>已有 Docker 的專案：加一個 Qdrant container 看似 <code>docker-compose.yml</code> 多三行，但要考慮 image 體積（數百 MB）、記憶體分配、冷啟動時間、以及 CI 環境是否支援 Docker-in-Docker。</li>
<li>前端專案：WebAssembly 版 HNSW 可行但受 bundle size 跟瀏覽器記憶體上限約束，跟 backend storage 的 constraint 型態完全不同。</li>
</ul>
<p>Storage 選型沒有「最佳方案」— 只有在特定 constraint 下的最適方案。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>RAG pipeline 的四層可替換結構。</li>
<li>Storage 升級的判讀訊號（規模驅動、痛點驅動、不是技術驅動）。</li>
<li>Index 生命週期的 full rebuild vs incremental update 取捨。</li>
<li>Dependency chain 作為選型約束的思考框架。</li>
<li>ANN 策略的複雜度分析（brute-force O(n) vs HNSW O(log n) vs IVF O(n/k)）。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 vector DB 的市場格局（Pinecone / Qdrant / Weaviate 的功能差異會持續變動）。</li>
<li>ANN library 的實作效能（新演算法可能比 HNSW 更好）。</li>
<li>語言生態的 binding 成熟度（Go 的 SQLite-vec 純 Go binding 可能出現）。</li>
<li>具體規模閾值（隨硬體進步、「brute-force 可行」的上限會提高）。</li>
</ul>
<h2 id="跟其他章節的關係">跟其他章節的關係</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>跟本章的分工</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a></td>
          <td>定義 retrieval + augmentation 本質、本章處理 storage layer</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/rag-retrieval-enhancements/" data-link-title="4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing" data-link-desc="Query 端增強（rewriting / expansion / HyDE）、multi-step iterative retrieval、retrieve 後的 context packing（dedup / ordering / summarization）、adaptive retrieval：vanilla RAG 不夠時的下一層工具箱">4.2 RAG 檢索增強</a></td>
          <td>處理 retrieval algorithm 層的增強、本章處理 storage 層</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 Embedding model</a></td>
          <td>處理向量怎麼生成（含實務選型 constraint 優先序）、本章處理向量怎麼存</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">4.10 衍生產物管理</a></td>
          <td>Index 是 derived artifact、不進 git、用 manifest 描述</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/vector-database/" data-link-title="Vector Database" data-link-desc="為高維向量 (embedding) 設計的儲存 &#43; 近似最近鄰 (ANN) 檢索系統：RAG 從 prototype 跨到 production 的關鍵元件">Vector database 卡</a></td>
          <td>概念定義與 ANN 演算法摘要、本章補工程判讀</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步">下一步</h2>
<p>本章整理的是跨場景的 storage 工程原則。Running example 的 blog 基於這些原則選了「Go + flat file + brute-force」方案，完整實作過程（選型→重寫→效能優化→四方案 benchmark→二次選型評估）見 <a href="/blog/llm/04-applications/hands-on/blog-vector-search/" data-link-title="Case Study：Blog 語意搜尋從 pickle 到 production" data-link-desc="為 CLI 或個人工具選 RAG storage backend、或原始選型理由被 benchmark 推翻但結論不變時，如何區分結論、理由與前提">Case Study：Blog 語意搜尋從 pickle 到 production</a>。</p>
<p>想看 retrieval 品質不夠時的增強手段（query rewriting / HyDE / multi-step），回到 <a href="/blog/llm/04-applications/rag-retrieval-enhancements/" data-link-title="4.2 RAG 檢索增強：query rewriting / HyDE / multi-step / context packing" data-link-desc="Query 端增強（rewriting / expansion / HyDE）、multi-step iterative retrieval、retrieve 後的 context packing（dedup / ordering / summarization）、adaptive retrieval：vanilla RAG 不夠時的下一層工具箱">4.2 RAG 檢索增強</a>。想看 embedding 模型怎麼選（含工程 constraint 如何先砍選項再比品質）、怎麼判讀 MTEB 分數，回到 <a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 Embedding model 內部</a>。</p>
]]></content:encoded></item><item><title>4.x Hands-on：端到端案例</title><link>https://tarrragon.github.io/blog/llm/04-applications/hands-on/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/hands-on/</guid><description>&lt;p>本子資料夾收錄把模組四原理串起來的端到端案例。跟前面 principle-first 章節的差別：principle 章節是「跨工具不變的原理」、hands-on 是「把這些原理放在同一個任務上、走一遍完整流程」。&lt;/p>
&lt;p>讀法建議：先讀 principle 章節建立心智模型、再進 hands-on 看「實際做的時候、原理怎麼落」。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>對應原理章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/" data-link-title="Case Study：customer support agent 從 task decomposition 到 eval" data-link-desc="把模組四原理串成端到端案例：observe → decompose → design workflow → instrument trace → design eval → iterate。每段標出引用哪章。">Customer support agent 從零到 eval&lt;/a>&lt;/td>
 &lt;td>Task decomposition → 設計 → trace → eval → iterate&lt;/td>
 &lt;td>4.0 prompt / 4.1 RAG / 4.3 tool / 4.4 agent / 4.5 HITL / 4.7 workflow / 4.13 eval / 4.20 trace&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/hands-on/blog-vector-search/" data-link-title="Case Study：Blog 語意搜尋從 pickle 到 production" data-link-desc="為 CLI 或個人工具選 RAG storage backend、或原始選型理由被 benchmark 推翻但結論不變時，如何區分結論、理由與前提">Blog 語意搜尋從 pickle 到 production&lt;/a>&lt;/td>
 &lt;td>Storage 選型 → 實作 → 效能優化 → 四方案 benchmark&lt;/td>
 &lt;td>4.1 RAG / 4.12 embedding / 4.14 benchmarking / 4.22 storage 工程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>本子資料夾收錄把模組四原理串起來的端到端案例。跟前面 principle-first 章節的差別：principle 章節是「跨工具不變的原理」、hands-on 是「把這些原理放在同一個任務上、走一遍完整流程」。</p>
<p>讀法建議：先讀 principle 章節建立心智模型、再進 hands-on 看「實際做的時候、原理怎麼落」。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>主題</th>
          <th>對應原理章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/hands-on/customer-support-case-study/" data-link-title="Case Study：customer support agent 從 task decomposition 到 eval" data-link-desc="把模組四原理串成端到端案例：observe → decompose → design workflow → instrument trace → design eval → iterate。每段標出引用哪章。">Customer support agent 從零到 eval</a></td>
          <td>Task decomposition → 設計 → trace → eval → iterate</td>
          <td>4.0 prompt / 4.1 RAG / 4.3 tool / 4.4 agent / 4.5 HITL / 4.7 workflow / 4.13 eval / 4.20 trace</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/hands-on/blog-vector-search/" data-link-title="Case Study：Blog 語意搜尋從 pickle 到 production" data-link-desc="為 CLI 或個人工具選 RAG storage backend、或原始選型理由被 benchmark 推翻但結論不變時，如何區分結論、理由與前提">Blog 語意搜尋從 pickle 到 production</a></td>
          <td>Storage 選型 → 實作 → 效能優化 → 四方案 benchmark</td>
          <td>4.1 RAG / 4.12 embedding / 4.14 benchmarking / 4.22 storage 工程</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>4.0 Prompt 技術光譜：手法分類、取捨、組合模式</title><link>https://tarrragon.github.io/blog/llm/04-applications/prompt-techniques-landscape/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/prompt-techniques-landscape/</guid><description>&lt;p>Prompt 技術不缺教學文章——但多數教學是「教你怎麼寫」、半年後模型換代、寫法跟著過時。本章不教「怎麼寫」、寫的是&lt;strong>這個技術 landscape 的結構&lt;/strong>：有哪些手法、每個解什麼問題、它們的 trade-off 在哪、什麼時候該組合、什麼時候不該。這些結構性問題跨模型世代不變。&lt;/p>
&lt;p>讀完本章後、看到任何新 prompt 技術都能放回正確座標、判斷「這是哪一軸的優化、跟我現在的問題對上嗎、能不能跟既有技術疊」——而不是每出一個新技術都從零學一次。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>把任何 prompt 技術放進三軸座標（context 提供 / 推理引導 / 角色與格式）。&lt;/li>
&lt;li>對單一技術評估四維 trade-off（accuracy、latency、cost、debuggability）。&lt;/li>
&lt;li>判斷何時 stack 技術、何時 stack 會互相抵消。&lt;/li>
&lt;li>區分 prompt 層解法 vs fine-tune / RAG / chaining 解法的邊界。&lt;/li>
&lt;li>看到「prompt 改了沒效」時、診斷是 systematic error 還是 random error。&lt;/li>
&lt;/ol>
&lt;h2 id="本章鎖定的是結構層不是寫法層">本章鎖定的是結構層、不是寫法層&lt;/h2>
&lt;p>Prompt 知識可以分兩層：&lt;strong>易變層&lt;/strong>是具體寫法（特定模型偏好哪種句型、特定任務最佳 step 切法）、&lt;strong>不變層&lt;/strong>是「有哪些技術可選、各解什麼問題、能不能組合」的結構。本章只寫不變層。&lt;/p>
&lt;p>易變層為什麼留給 case-by-case：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>跨模型差異&lt;/strong>：對 GPT-4 有效的寫法、對 Claude 可能反效果。模型 SFT 分佈不同、對 prompt 結構的偏好不同。&lt;/li>
&lt;li>&lt;strong>跨任務差異&lt;/strong>：對 summarization 有效的格式、對 classification 沒幫助。每個任務的最佳 prompt 形狀要實驗。&lt;/li>
&lt;/ul>
&lt;p>不變層的價值是：看到任何新 prompt 技術都能放回正確座標、判斷它解什麼問題、跟既有技術疊能不能。具體寫法（act as XYZ 怎麼設計、step 怎麼分）屬於客製工作、不在本章。&lt;/p>
&lt;h2 id="三軸分類">三軸分類&lt;/h2>
&lt;p>把 prompt 技術放到三軸座標、看到任何新手法都能定位：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>軸&lt;/th>
 &lt;th>解決什麼問題&lt;/th>
 &lt;th>代表技術&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Context 提供&lt;/td>
 &lt;td>模型「缺資料 / 缺對齊範例」&lt;/td>
 &lt;td>zero-shot、few-shot、retrieval-augmented&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推理引導&lt;/td>
 &lt;td>模型「直接答錯、需要 think」&lt;/td>
 &lt;td>chain-of-thought、decomposition、reflection&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>角色與格式&lt;/td>
 &lt;td>模型「不知該以什麼姿態回應」&lt;/td>
 &lt;td>role prompting、persona、output template&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個技術可能跨軸（如 few-shot CoT 同時 context + 推理）、但歸到主軸有助判讀「這技術在解哪一類問題」。看到新技術時、先問「它放哪一軸」、再看它跟既有技術的關係。&lt;/p>
&lt;h2 id="context-軸模型缺什麼資料">Context 軸：模型缺什麼資料&lt;/h2>
&lt;h3 id="zero-shot">Zero-shot&lt;/h3>
&lt;p>直接給任務、不給範例。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>適用&lt;/strong>：模型對任務分佈熟、輸出格式可預測。例：「將下列文字翻譯成英文」。&lt;/li>
&lt;li>&lt;strong>失效&lt;/strong>：任務邊界模糊、模型沒「對齊到你的標準」。例：「分類這個 review 是正向 / 中性 / 負向」——「中性」的邊界在不同產業差很多。&lt;/li>
&lt;/ul>
&lt;h3 id="few-shot">Few-shot&lt;/h3>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/few-shot-prompting/" data-link-title="Few-shot prompting" data-link-desc="在 prompt 內塞 input-output 範例對齊任務、不動模型權重的 in-context learning 技術">Few-shot prompting&lt;/a> 在 prompt 內塞幾個 input-output 範例、模型透過範例對齊任務。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>適用&lt;/strong>：任務有「我的標準跟模型預設不同」、但能舉幾個代表性例子。常見場景：分類、抽取、格式轉換、tone alignment。&lt;/li>
&lt;li>&lt;strong>核心收益&lt;/strong>：把「對齊任務」這件事從 fine-tune 移到 prompt——iteration 從幾天縮到幾分鐘、不動模型權重。&lt;/li>
&lt;li>&lt;strong>失效&lt;/strong>：範例選不好（cherry-picked、cover 不到 edge case）、範例太多撐爆 context、任務本質需要外部知識（這時該用 RAG 不是 few-shot）。&lt;/li>
&lt;/ul>
&lt;p>Few-shot 跟 fine-tune 是「對齊」這件事的兩個 endpoint。Trade-off：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Few-shot in prompt&lt;/th>
 &lt;th>Fine-tune&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Iteration&lt;/td>
 &lt;td>分鐘級、改 prompt 即可&lt;/td>
 &lt;td>天級、要 retrain&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>範例容量&lt;/td>
 &lt;td>受 context window 限制（10–50）&lt;/td>
 &lt;td>可以幾千幾萬、整個 dataset 都行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost&lt;/td>
 &lt;td>每次 inference 多付 token&lt;/td>
 &lt;td>一次性訓練 cost、之後 inference 不變&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模型遷移&lt;/td>
 &lt;td>跨模型即時換、prompt 直接搬&lt;/td>
 &lt;td>綁特定 base model、換模型要 retrain&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>知識更新&lt;/td>
 &lt;td>改 prompt 即可&lt;/td>
 &lt;td>要 retrain&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>實務啟示：先 few-shot、等到範例真的多到撐爆 context 又每天都用、再考慮 fine-tune。本指南對 fine-tune 的整體看法見 &lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4 訓練流程&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Prompt 技術不缺教學文章——但多數教學是「教你怎麼寫」、半年後模型換代、寫法跟著過時。本章不教「怎麼寫」、寫的是<strong>這個技術 landscape 的結構</strong>：有哪些手法、每個解什麼問題、它們的 trade-off 在哪、什麼時候該組合、什麼時候不該。這些結構性問題跨模型世代不變。</p>
<p>讀完本章後、看到任何新 prompt 技術都能放回正確座標、判斷「這是哪一軸的優化、跟我現在的問題對上嗎、能不能跟既有技術疊」——而不是每出一個新技術都從零學一次。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>把任何 prompt 技術放進三軸座標（context 提供 / 推理引導 / 角色與格式）。</li>
<li>對單一技術評估四維 trade-off（accuracy、latency、cost、debuggability）。</li>
<li>判斷何時 stack 技術、何時 stack 會互相抵消。</li>
<li>區分 prompt 層解法 vs fine-tune / RAG / chaining 解法的邊界。</li>
<li>看到「prompt 改了沒效」時、診斷是 systematic error 還是 random error。</li>
</ol>
<h2 id="本章鎖定的是結構層不是寫法層">本章鎖定的是結構層、不是寫法層</h2>
<p>Prompt 知識可以分兩層：<strong>易變層</strong>是具體寫法（特定模型偏好哪種句型、特定任務最佳 step 切法）、<strong>不變層</strong>是「有哪些技術可選、各解什麼問題、能不能組合」的結構。本章只寫不變層。</p>
<p>易變層為什麼留給 case-by-case：</p>
<ul>
<li><strong>跨模型差異</strong>：對 GPT-4 有效的寫法、對 Claude 可能反效果。模型 SFT 分佈不同、對 prompt 結構的偏好不同。</li>
<li><strong>跨任務差異</strong>：對 summarization 有效的格式、對 classification 沒幫助。每個任務的最佳 prompt 形狀要實驗。</li>
</ul>
<p>不變層的價值是：看到任何新 prompt 技術都能放回正確座標、判斷它解什麼問題、跟既有技術疊能不能。具體寫法（act as XYZ 怎麼設計、step 怎麼分）屬於客製工作、不在本章。</p>
<h2 id="三軸分類">三軸分類</h2>
<p>把 prompt 技術放到三軸座標、看到任何新手法都能定位：</p>
<table>
  <thead>
      <tr>
          <th>軸</th>
          <th>解決什麼問題</th>
          <th>代表技術</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Context 提供</td>
          <td>模型「缺資料 / 缺對齊範例」</td>
          <td>zero-shot、few-shot、retrieval-augmented</td>
      </tr>
      <tr>
          <td>推理引導</td>
          <td>模型「直接答錯、需要 think」</td>
          <td>chain-of-thought、decomposition、reflection</td>
      </tr>
      <tr>
          <td>角色與格式</td>
          <td>模型「不知該以什麼姿態回應」</td>
          <td>role prompting、persona、output template</td>
      </tr>
  </tbody>
</table>
<p>每個技術可能跨軸（如 few-shot CoT 同時 context + 推理）、但歸到主軸有助判讀「這技術在解哪一類問題」。看到新技術時、先問「它放哪一軸」、再看它跟既有技術的關係。</p>
<h2 id="context-軸模型缺什麼資料">Context 軸：模型缺什麼資料</h2>
<h3 id="zero-shot">Zero-shot</h3>
<p>直接給任務、不給範例。</p>
<ul>
<li><strong>適用</strong>：模型對任務分佈熟、輸出格式可預測。例：「將下列文字翻譯成英文」。</li>
<li><strong>失效</strong>：任務邊界模糊、模型沒「對齊到你的標準」。例：「分類這個 review 是正向 / 中性 / 負向」——「中性」的邊界在不同產業差很多。</li>
</ul>
<h3 id="few-shot">Few-shot</h3>
<p><a href="/blog/llm/knowledge-cards/few-shot-prompting/" data-link-title="Few-shot prompting" data-link-desc="在 prompt 內塞 input-output 範例對齊任務、不動模型權重的 in-context learning 技術">Few-shot prompting</a> 在 prompt 內塞幾個 input-output 範例、模型透過範例對齊任務。</p>
<ul>
<li><strong>適用</strong>：任務有「我的標準跟模型預設不同」、但能舉幾個代表性例子。常見場景：分類、抽取、格式轉換、tone alignment。</li>
<li><strong>核心收益</strong>：把「對齊任務」這件事從 fine-tune 移到 prompt——iteration 從幾天縮到幾分鐘、不動模型權重。</li>
<li><strong>失效</strong>：範例選不好（cherry-picked、cover 不到 edge case）、範例太多撐爆 context、任務本質需要外部知識（這時該用 RAG 不是 few-shot）。</li>
</ul>
<p>Few-shot 跟 fine-tune 是「對齊」這件事的兩個 endpoint。Trade-off：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Few-shot in prompt</th>
          <th>Fine-tune</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Iteration</td>
          <td>分鐘級、改 prompt 即可</td>
          <td>天級、要 retrain</td>
      </tr>
      <tr>
          <td>範例容量</td>
          <td>受 context window 限制（10–50）</td>
          <td>可以幾千幾萬、整個 dataset 都行</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>每次 inference 多付 token</td>
          <td>一次性訓練 cost、之後 inference 不變</td>
      </tr>
      <tr>
          <td>模型遷移</td>
          <td>跨模型即時換、prompt 直接搬</td>
          <td>綁特定 base model、換模型要 retrain</td>
      </tr>
      <tr>
          <td>知識更新</td>
          <td>改 prompt 即可</td>
          <td>要 retrain</td>
      </tr>
  </tbody>
</table>
<p>實務啟示：先 few-shot、等到範例真的多到撐爆 context 又每天都用、再考慮 fine-tune。本指南對 fine-tune 的整體看法見 <a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4 訓練流程</a>。</p>
<h3 id="retrieval-augmented-prompting">Retrieval-augmented prompting</h3>
<p>跟 few-shot 像、但範例不是寫死、是<strong>從一個範例庫即時 retrieve</strong>。技術上落在 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a>、但概念上是 few-shot 的延伸——把固定範例變成動態範例。</p>
<ul>
<li><strong>適用</strong>：範例庫大、每次任務最相關的範例不同。</li>
<li><strong>跟 RAG 知識檢索的差異</strong>：RAG 取「事實 / 文件」、retrieval-augmented prompting 取「相似任務的解答範例」。兩個目的不同、但 infra 共用。</li>
</ul>
<h2 id="推理軸模型該不該think">推理軸：模型該不該「think」</h2>
<h3 id="chain-of-thoughtcot">Chain-of-Thought（CoT）</h3>
<p><a href="/blog/llm/knowledge-cards/chain-of-thought/" data-link-title="Chain-of-Thought（CoT）" data-link-desc="讓 LLM 先輸出推理步驟再給最終答案的 prompting / 訓練方式、reasoning model 的基礎機制">Chain-of-Thought</a> 要求模型「show your work」、把推理步驟寫出來、再給最終答案。</p>
<ul>
<li><strong>適用</strong>：multi-step reasoning（數學、邏輯、複雜判斷）、模型直接答錯但 step-by-step 後對。</li>
<li><strong>失效在 reasoning model 出現後</strong>：<a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">reasoning model</a> 本身就在生成內部推理 trace、再外加 explicit CoT prompt 邊際收益遞減、部分模型可能反而干擾內部推理路徑。判讀訊號：模型卡片寫「reasoning model」、就不要再加 &ldquo;think step by step&rdquo;。</li>
<li><strong>失效在低能力模型</strong>：模型本身推理能力不足、CoT 變成「把錯誤推理寫得更詳細」、不會把答案變對。CoT 是「把潛在能力擠出來」、不是「給模型新能力」。</li>
</ul>
<h3 id="task-decomposition">Task decomposition</h3>
<p>把大任務拆成幾個明確子任務、prompt 內 enumerate 出來。</p>
<ul>
<li><strong>跟 CoT 的差異</strong>：CoT 是「過程要 explicit」、decomposition 是「子任務要 explicit」。CoT 在 single call 內展開、decomposition 可以單 call 也可以多 call。</li>
<li><strong>適用</strong>：任務有明顯的 phase（如「先抽要點、再寫 outline、再展開段落」）、不分階段就會走錯。</li>
<li><strong>跟 chaining 的邊界</strong>：decomposition 寫在 single prompt 裡是 prompt 技術；拆成多 call 是 <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> 的 pipeline 模式。判讀：每階段 output 要不要被審查 / 被 inject 不同 context → 要 → 走 chaining；不需要 → 留在 single prompt 內 decomposition。</li>
</ul>
<h3 id="reflection--self-critique">Reflection / self-critique</h3>
<p><a href="/blog/llm/knowledge-cards/reflection/" data-link-title="Reflection / Self-critique" data-link-desc="要求模型先輸出一版、再 critique 自己、再修改的 prompting / workflow 模式、有自身失敗模式">Reflection</a> 要求模型先輸出一版、再 critique 自己、再修改。</p>
<ul>
<li><strong>適用</strong>：模型有能力辨識「自己寫的不夠好」、critique 跟 generator 不會共用同樣 blind spot。</li>
<li><strong>失效</strong>：critique 跟 generator 是同個模型、訓練分佈中的盲點不會因為「再想一次」消失。判讀訊號：critique 每次都給很像的建議、或修完還是同一類錯——這是 systematic error、加 reflection 沒收益。</li>
<li><strong>完整失敗模式分析見</strong> <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> reflection 段。</li>
</ul>
<h2 id="角色與格式軸模型該以什麼姿態回應">角色與格式軸：模型該以什麼姿態回應</h2>
<h3 id="role-prompting">Role prompting</h3>
<p>&ldquo;Act as X&rdquo; 系列——指定模型扮演的角色或專業領域。</p>
<ul>
<li><strong>適用</strong>：通用模型在多種風格之間漂、加 role 把它鎖到特定分佈。例：「act as a senior backend engineer reviewing this PR」鎖技術深度。</li>
<li><strong>失效</strong>：role 跟任務無關（&ldquo;act as a wizard&rdquo; 做財務分析）、或 role 設定跟使用者實際需求衝突。Role 是調 tone / 深度 / 視角的工具、不會給模型新能力。</li>
<li><strong>常見過度迷信</strong>：&ldquo;you are the best in the world at this&rdquo; 這類自誇式 prompt 跨模型效果不穩定、難以可靠重現。不值得當核心策略。</li>
</ul>
<h3 id="output-template">Output template</h3>
<p>指定 output 格式（JSON schema、Markdown 結構、特定欄位）。</p>
<ul>
<li><strong>適用</strong>：output 要餵下游 deterministic 系統（API、DB、UI）、格式錯就整個流程斷。</li>
<li><strong>執行層次</strong>：純 prompt 指定（弱）→ few-shot 範例（中）→ structured output / constrained decoding（強、見 <a href="/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10 constrained decoding 內部</a>）。三者疊用最穩。</li>
<li><strong>失效</strong>：模板太緊、模型為了符合格式犧牲內容品質。Trade-off：嚴格 schema 換來下游穩定、但 prompt 的 expression 空間變小。</li>
</ul>
<h3 id="persona--system-prompt">Persona / system prompt</h3>
<p>跨 turn 持續性的角色與行為設定、放在 <a href="/blog/llm/knowledge-cards/system-prompt/" data-link-title="System Prompt" data-link-desc="LLM application 中由開發者預設、不直接顯示給使用者的指令層、定義模型的角色、行為規範、輸出格式">system prompt</a>。</p>
<ul>
<li><strong>跟 role prompting 的差異</strong>：role prompting 是 single call 的暫時角色、persona 是跨 turn 的長期人設。多數 chatbot 應用都在後台塞 persona。</li>
<li><strong>失效</strong>：persona 跟 user request 衝突時、模型在「跟 persona 一致」跟「滿足 user」之間擺盪、行為不穩。</li>
</ul>
<h2 id="四維-trade-off">四維 Trade-off</h2>
<p>每個 prompt 技術都可以用這四維評估：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>意義</th>
          <th>典型代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Accuracy</td>
          <td>任務完成品質</td>
          <td>—</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>從 request 到 final response 的時間</td>
          <td>Token 累積拉長生成時間</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>每次 inference 的 token 成本</td>
          <td>Token 累積放大成本</td>
      </tr>
      <tr>
          <td>Debuggability</td>
          <td>失敗時能不能定位是哪一步出問題</td>
          <td>Single 大 prompt 失敗難排查</td>
      </tr>
  </tbody>
</table>
<p>四維是 trade-off、不是「都拉到最高」。Few-shot 提高 accuracy 但加 cost 跟 latency；CoT 提高 accuracy 但顯著拉長 latency；reflection 進一步提高 accuracy 但 cost / latency 翻倍以上。</p>
<p>Latency 的展開：標準 LLM 生成的 latency 由 TTFT（首 token 時間）+ output token 數 × per-token latency 決定。Few-shot 加 input token、影響 TTFT 但不影響 per-token；CoT / reflection 加 output token、顯著拉長總生成時間。Reasoning model 例外——它的 thinking token 也算 output、顯著拉長 TTFT 跟總時間、加 explicit CoT 在 reasoning model 上是重複收費。</p>
<p>Debuggability 的展開：single 大 prompt 跑出錯時、要排查是 task 拆解錯、role 不對、few-shot 範例誤導、還是格式描述不清——所有問題混在一個 call 裡。Chaining / decomposition 把流程拆成多個獨立 step、每 step 有自己的 input / output trace、可以 isolate 故障點。Trade-off：chaining 加 latency / cost、但 debug 時間遠少。</p>
<p>設計時先問「我的 binding constraint 是哪個」：</p>
<ul>
<li>即時 chatbot → latency / cost 優先、accuracy 次要、避開 reflection</li>
<li>後台 batch（每晚跑、明早看）→ accuracy 優先、latency 不重要、reflection 可用</li>
<li>高代價任務（醫療、法律、財務）→ accuracy + debuggability 優先、cost 不在乎</li>
</ul>
<h2 id="組合stack-的兩個條件">組合：Stack 的兩個條件</h2>
<p>Stack 有效的必要條件是<strong>兩技術解不同軸的問題、且底層假設一致</strong>。兩條件都滿足才有疊加收益、任一失效就會抵消甚至反效果。</p>
<h3 id="有效的-stack-組合">有效的 stack 組合</h3>
<ul>
<li><strong>Few-shot + role</strong>：few-shot 解「任務對齊」、role 解「回應姿態」、兩軸不衝突。</li>
<li><strong>Few-shot + output template</strong>：few-shot 教任務、template 鎖格式、互補。</li>
<li><strong>CoT + decomposition</strong>：decomposition 拆 phase、CoT 展開每 phase 的推理、層級互補。</li>
</ul>
<h3 id="失效的-stack-組合同軸或假設衝突">失效的 stack 組合（同軸或假設衝突）</h3>
<ul>
<li><strong>CoT + reasoning model</strong>：reasoning model 內部已在做 chain-of-thought、外加 explicit CoT 邊際收益遞減、部分模型可能反而干擾內部推理路徑。判讀：模型卡片寫 reasoning、就不要再加 &ldquo;think step by step&rdquo;。</li>
<li><strong>Reflection + 低能力模型</strong>：reflection 需要 critique 能力、低能力模型 critique 不出有用建議、徒增 cost。</li>
<li><strong>多重 role 衝突</strong>：&ldquo;act as a creative writer AND a strict editor&rdquo;——指令互相牴觸、模型隨機選一邊。</li>
<li><strong>Few-shot 太多 + long context 任務</strong>：few-shot 撐爆 context、留給實際任務的空間不足、accuracy 反降。</li>
</ul>
<p>判讀 stack 是否有效的反射動作：問「兩個技術解的是不同問題嗎、它們有沒有共用底層假設」。</p>
<h2 id="跟相鄰技術的邊界">跟相鄰技術的邊界</h2>
<p>Prompt 技術不是萬能、有些問題該換層解：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>Prompt 層能解到哪</th>
          <th>該換的層</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模型不知道某個事實</td>
          <td>few-shot 塞少量、不夠</td>
          <td>RAG（<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1</a>）</td>
      </tr>
      <tr>
          <td>模型完全不會某個任務</td>
          <td>few-shot 撐不住、頻繁失敗</td>
          <td>Fine-tune（<a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4</a>）</td>
      </tr>
      <tr>
          <td>任務要多步、每步要不同 context</td>
          <td>single prompt 塞不下、邏輯混</td>
          <td>Chaining / workflow（<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a>）</td>
      </tr>
      <tr>
          <td>任務要外部資料 / API</td>
          <td>prompt 描述不出、需要實際呼叫</td>
          <td>Tool use（<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a>）</td>
      </tr>
      <tr>
          <td>任務要 LLM 自主推進</td>
          <td>prompt 無法表達「持續決定下一步」</td>
          <td>Agent（<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>）</td>
      </tr>
  </tbody>
</table>
<p>判讀訊號：prompt 改了五版、accuracy 還是不到 baseline、就該往這個表的右欄移、不是再改 prompt 第六版。</p>
<h2 id="失敗診斷prompt-改了沒效時">失敗診斷：Prompt 改了沒效時</h2>
<p>Prompt 修改沒效、定位是 systematic 還是 random error：</p>
<ul>
<li><strong>Random error</strong>：同 prompt 跑 N 次、output 不穩定、有時對有時錯。可以靠 reflection / 多採樣 / temperature 降低收斂——這條路 prompt 層有解。</li>
<li><strong>Systematic error</strong>：同 prompt 跑 N 次、output 一致地錯（或一致地朝某個方向偏）。reflection 沒用、prompt 改寫也救不回——這是模型能力 / 訓練分佈問題、要往 RAG / fine-tune / 換模型走、不是再改 prompt。</li>
</ul>
<p>判讀步驟：</p>
<ol>
<li>同 prompt 跑 5–10 次、看 output 分佈</li>
<li>若分佈寬：random error、prompt 層可解</li>
<li>若分佈窄但錯：systematic error、不要再 iterate prompt、換層</li>
</ol>
<p>這個判讀直接呼應 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">模組零 fuzzy engineering</a> 的「同 input → 分佈」假設——不看分佈、debug 就是瞎猜。</p>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>三軸分類（context / 推理 / 格式）。</li>
<li>四維 trade-off（accuracy / latency / cost / debuggability）。</li>
<li>Stack 有效 vs 抵消的判讀原則（不同軸 vs 同軸 / 底層假設）。</li>
<li>Prompt 層 vs 換層的邊界判讀。</li>
<li>Systematic vs random error 的診斷流程。</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>對特定模型有效的具體寫法（每個模型偏好的 prompt structure）。</li>
<li>角色 prompting 的有效程度（隨 model alignment 訓練成熟、role hack 的效果逐年降低）。</li>
<li>CoT 的必要性（reasoning model 普及後、explicit CoT 的場景縮小）。</li>
<li>Output format 強制手段（從 prompt-only 走向 structured output API、再走向 constrained decoding）。</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a>、把「prompt 層塞不下知識」這個邊界往外推、進入 LLM 跟外部資料互動的領域。Prompt 跟 fine-tune 的對齊取捨見 <a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4</a>、跟 chaining 的邊界完整討論見 <a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a>、跟 fuzzy engineering 典範的關係見 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a>。</p>
]]></content:encoded></item></channel></rss>