<?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>Structured-Output on Tarragon</title><link>https://tarrragon.github.io/blog/tags/structured-output/</link><description>Recent content in Structured-Output on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 14 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/structured-output/index.xml" rel="self" type="application/rss+xml"/><item><title>DSL（Domain-Specific Language）</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/dsl/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/dsl/</guid><description>&lt;p>DSL（Domain-Specific Language）的核心概念是「&lt;strong>為特定領域設計的小語言&lt;/strong>」。它不像通用程式語言要解所有問題，而是把某個領域的可用操作、資料形狀與限制收斂成小而可解析的語法，讓人類、LLM 與程式都能用同一種中介表示溝通。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>在 LLM 應用裡，DSL 常出現在自然語言與程式執行之間。模型把使用者意圖轉成 DSL，應用再 parse、validate、authorize、execute；這比直接讓模型輸出任意程式碼更容易控管，也比純自然語言更容易自動化。&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">使用者：「找出高優先、尚未處理的 billing ticket」
&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">LLM 輸出 DSL：ticket.where(category=&amp;#34;billing&amp;#34;, priority=&amp;#34;high&amp;#34;, status!=&amp;#34;done&amp;#34;)
&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">parser / validator / executor&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>看到「特定 query language」「workflow mini-language」「policy expression」「filter expression」「tool command language」就是 DSL 候選。例子包括搜尋篩選語法、監控告警規則、資料轉換 pipeline、客服工單查詢、CI workflow 條件式。&lt;/p>
&lt;p>DSL 的風險是語法看起來可控，但語意與權限仍然危險。模型生成的 DSL 要經過 parser 確認語法、validator 確認欄位與型別、authorization 確認可操作範圍、dry run 或 preview 確認副作用；不能因為輸出不是通用程式碼就直接執行。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>DSL 適合操作集合固定、需要高可控性、且自然語言到執行之間需要審計紀錄的場景。設計時先定義最小語法、失敗路由與不可表示狀態；需要讓 LLM 穩定產生 DSL 時，用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar&lt;/a> 或 JSON Schema 約束輸出。下一步路由是 &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/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">Sampling Constraint&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>DSL（Domain-Specific Language）的核心概念是「<strong>為特定領域設計的小語言</strong>」。它不像通用程式語言要解所有問題，而是把某個領域的可用操作、資料形狀與限制收斂成小而可解析的語法，讓人類、LLM 與程式都能用同一種中介表示溝通。</p>
<h2 id="概念位置">概念位置</h2>
<p>在 LLM 應用裡，DSL 常出現在自然語言與程式執行之間。模型把使用者意圖轉成 DSL，應用再 parse、validate、authorize、execute；這比直接讓模型輸出任意程式碼更容易控管，也比純自然語言更容易自動化。</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">使用者：「找出高優先、尚未處理的 billing ticket」
</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 輸出 DSL：ticket.where(category=&#34;billing&#34;, priority=&#34;high&#34;, status!=&#34;done&#34;)
</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">parser / validator / executor</span></span></code></pre></div><h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>看到「特定 query language」「workflow mini-language」「policy expression」「filter expression」「tool command language」就是 DSL 候選。例子包括搜尋篩選語法、監控告警規則、資料轉換 pipeline、客服工單查詢、CI workflow 條件式。</p>
<p>DSL 的風險是語法看起來可控，但語意與權限仍然危險。模型生成的 DSL 要經過 parser 確認語法、validator 確認欄位與型別、authorization 確認可操作範圍、dry run 或 preview 確認副作用；不能因為輸出不是通用程式碼就直接執行。</p>
<h2 id="設計責任">設計責任</h2>
<p>DSL 適合操作集合固定、需要高可控性、且自然語言到執行之間需要審計紀錄的場景。設計時先定義最小語法、失敗路由與不可表示狀態；需要讓 LLM 穩定產生 DSL 時，用 <a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar</a> 或 JSON Schema 約束輸出。下一步路由是 <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/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">Sampling Constraint</a>。</p>
]]></content:encoded></item><item><title>Grammar</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/</guid><description>&lt;p>Grammar（語法規則）的核心概念是「&lt;strong>用形式化規則描述哪些字串是合法輸出&lt;/strong>」。在 LLM structured output 裡，grammar 是 parser / decoder 可以執行的規則集合，用來判斷 JSON、SQL、DSL、表達式或自訂格式是否符合預期形狀——此處的 grammar 指形式語法，而非英文文法。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Grammar 位在格式定義層，常被 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a> 編譯成 token mask。它跟 schema 的差異在表達方式：schema 常描述資料結構與欄位限制，grammar 描述字串如何從符號規則生成；JSON Schema 適合物件欄位，grammar 適合自訂語言、查詢語法、括號結構與特定文字格式。&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">grammar 規則 → parser / decoder 編譯
&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">每個生成位置算出合法 token
&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">不合法 token 被 mask 掉&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>看到 &lt;code>expr: term (&amp;quot;+&amp;quot; term)*&lt;/code>、&lt;code>start: object&lt;/code>、&lt;code>&amp;lt;json&amp;gt; ::= ...&lt;/code> 這類規則就是 grammar。例子是讓模型只輸出簡化查詢語言：欄位只能是 &lt;code>status&lt;/code> / &lt;code>owner&lt;/code>，運算子只能是 &lt;code>=&lt;/code> / &lt;code>in&lt;/code>，字串必須加引號；grammar 可以把非法查詢擋在生成階段。&lt;/p>
&lt;p>Grammar 的邊界是語意與外部狀態。它可以限制語法合法，卻不能知道 &lt;code>owner = &amp;quot;alice&amp;quot;&lt;/code> 是否真有這個使用者，也不能判斷查詢是否符合權限；這些仍要交給 validator、authorization 與業務規則。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>需要自訂輸出格式時，先判斷格式是資料結構還是小語言：物件欄位優先用 JSON Schema，小語言或查詢語法才用 grammar。下一步路由是：需要語法表示法讀 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/bnf/" data-link-title="BNF（Backus-Naur Form）" data-link-desc="用遞迴產生式描述語法的經典記法，是 CFG、parser 與 grammar-constrained sampling 常見的基礎表示">BNF&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lark-grammar/" data-link-title="Lark Grammar" data-link-desc="Lark parser 使用的 EBNF-like grammar 格式，常被 structured output 工具拿來描述自訂輸出語法">Lark Grammar&lt;/a>；需要應用層自訂語言讀 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/dsl/" data-link-title="DSL（Domain-Specific Language）" data-link-desc="為特定業務或技術領域設計的小語言，在 LLM 應用中常作為可解析、可驗證、可執行的中介輸出">DSL&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Grammar（語法規則）的核心概念是「<strong>用形式化規則描述哪些字串是合法輸出</strong>」。在 LLM structured output 裡，grammar 是 parser / decoder 可以執行的規則集合，用來判斷 JSON、SQL、DSL、表達式或自訂格式是否符合預期形狀——此處的 grammar 指形式語法，而非英文文法。</p>
<h2 id="概念位置">概念位置</h2>
<p>Grammar 位在格式定義層，常被 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a> 編譯成 token mask。它跟 schema 的差異在表達方式：schema 常描述資料結構與欄位限制，grammar 描述字串如何從符號規則生成；JSON Schema 適合物件欄位，grammar 適合自訂語言、查詢語法、括號結構與特定文字格式。</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">grammar 規則 → parser / decoder 編譯
</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">每個生成位置算出合法 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">不合法 token 被 mask 掉</span></span></code></pre></div><h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>看到 <code>expr: term (&quot;+&quot; term)*</code>、<code>start: object</code>、<code>&lt;json&gt; ::= ...</code> 這類規則就是 grammar。例子是讓模型只輸出簡化查詢語言：欄位只能是 <code>status</code> / <code>owner</code>，運算子只能是 <code>=</code> / <code>in</code>，字串必須加引號；grammar 可以把非法查詢擋在生成階段。</p>
<p>Grammar 的邊界是語意與外部狀態。它可以限制語法合法，卻不能知道 <code>owner = &quot;alice&quot;</code> 是否真有這個使用者，也不能判斷查詢是否符合權限；這些仍要交給 validator、authorization 與業務規則。</p>
<h2 id="設計責任">設計責任</h2>
<p>需要自訂輸出格式時，先判斷格式是資料結構還是小語言：物件欄位優先用 JSON Schema，小語言或查詢語法才用 grammar。下一步路由是：需要語法表示法讀 <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>；需要應用層自訂語言讀 <a href="/blog/llm/knowledge-cards/dsl/" data-link-title="DSL（Domain-Specific Language）" data-link-desc="為特定業務或技術領域設計的小語言，在 LLM 應用中常作為可解析、可驗證、可執行的中介輸出">DSL</a>。</p>
]]></content:encoded></item><item><title>Structured Output</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/</guid><description>&lt;p>Structured output 的核心概念是「&lt;strong>讓 LLM 輸出符合可機器解析的固定形狀&lt;/strong>」。它解的是應用層 parser 能不能穩定消費模型輸出的問題：輸出要能被 JSON parser、schema validator、dispatcher、workflow engine 確定性處理，而不是靠人類讀自然語言再猜意圖。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Structured output 位在推論與應用交界，常見實作包含 JSON mode、JSON Schema、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar&lt;/a> 約束、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a> 與 logit mask。它跟 &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> 的差異在責任層：function calling 是模型訓練出的工具呼叫能力，structured output 是推論時讓輸出形狀穩定的約束。&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">推論約束：輸出必須符合 JSON / schema / grammar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">應用消費：parser 解析、validator 檢查、dispatcher 執行&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>看到「固定輸出 JSON」「把結果分類成 enum」「回傳符合 schema 的物件」「讓 parser 不再處理自由文字」就是 structured output 場景。例子是客服工單分類：模型輸出 &lt;code>{&amp;quot;category&amp;quot;:&amp;quot;billing&amp;quot;,&amp;quot;priority&amp;quot;:&amp;quot;high&amp;quot;}&lt;/code>，後端可以直接依欄位路由，而不是從一段自然語言裡抽關鍵字。&lt;/p>
&lt;p>Structured output 的成功訊號是合法率、schema 對位率與下游解析失敗率。JSON 合法率只代表文字可被 parser 讀，schema 對位率才代表欄位、型別、enum、required 都符合應用契約；兩者分開看，才能分辨是語法錯、schema 錯，還是模型語意判斷錯。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Structured output 適合「下游要自動執行」的輸出：tool 參數、分類、抽取、workflow 狀態、查詢條件。它的邊界是語意品質：grammar 可以保證格式合法，但不能保證模型填的值正確。下一步路由是：需要理解 token mask 機制讀 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">Constrained Decoding&lt;/a>；需要判斷它跟工具呼叫的分工讀 &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/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></description><content:encoded><![CDATA[<p>Structured output 的核心概念是「<strong>讓 LLM 輸出符合可機器解析的固定形狀</strong>」。它解的是應用層 parser 能不能穩定消費模型輸出的問題：輸出要能被 JSON parser、schema validator、dispatcher、workflow engine 確定性處理，而不是靠人類讀自然語言再猜意圖。</p>
<h2 id="概念位置">概念位置</h2>
<p>Structured output 位在推論與應用交界，常見實作包含 JSON mode、JSON Schema、<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/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a> 與 logit mask。它跟 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling</a> 的差異在責任層：function calling 是模型訓練出的工具呼叫能力，structured output 是推論時讓輸出形狀穩定的約束。</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">推論約束：輸出必須符合 JSON / schema / grammar
</span></span><span class="line"><span class="ln">3</span><span class="cl">應用消費：parser 解析、validator 檢查、dispatcher 執行</span></span></code></pre></div><h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>看到「固定輸出 JSON」「把結果分類成 enum」「回傳符合 schema 的物件」「讓 parser 不再處理自由文字」就是 structured output 場景。例子是客服工單分類：模型輸出 <code>{&quot;category&quot;:&quot;billing&quot;,&quot;priority&quot;:&quot;high&quot;}</code>，後端可以直接依欄位路由，而不是從一段自然語言裡抽關鍵字。</p>
<p>Structured output 的成功訊號是合法率、schema 對位率與下游解析失敗率。JSON 合法率只代表文字可被 parser 讀，schema 對位率才代表欄位、型別、enum、required 都符合應用契約；兩者分開看，才能分辨是語法錯、schema 錯，還是模型語意判斷錯。</p>
<h2 id="設計責任">設計責任</h2>
<p>Structured output 適合「下游要自動執行」的輸出：tool 參數、分類、抽取、workflow 狀態、查詢條件。它的邊界是語意品質：grammar 可以保證格式合法，但不能保證模型填的值正確。下一步路由是：需要理解 token mask 機制讀 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">Constrained Decoding</a>；需要判斷它跟工具呼叫的分工讀 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function Calling</a>；需要完整應用層組合讀 <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>Constrained Decoding</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/</guid><description>&lt;p>Constrained decoding（受限解碼）的核心概念是「&lt;strong>推論時用 grammar 動態算出每個位置的合法 token mask、把不合法 token 的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit&lt;/a> 設成 -∞、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax&lt;/a> 後機率為 0&lt;/strong>」。是 &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 協議，三者的層級差異與組合方式">structured output&lt;/a>（JSON mode / function calling 的合法性保證）背後的 sampling 機制。代表實作：XGrammar、outlines、lm-format-enforcer、guidance、SGLang。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>跟既有 sampling 概念的層次：&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">模型 forward pass → logits（每個 vocab token 一個分數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ apply temperature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ↓ apply grammar mask（constrained decoding） ← 本卡聚焦
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> - 算出當下位置的合法 token 集合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> - 不合法 token 的 logit 設 -∞
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ softmax → 機率分佈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> ↓ sampling（greedy / top-p / top-k）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓ next token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>主要 grammar 類型：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Grammar 類型&lt;/th>
 &lt;th>描述&lt;/th>
 &lt;th>用例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>JSON Schema&lt;/td>
 &lt;td>標準 JSON schema 定義合法 JSON 結構&lt;/td>
 &lt;td>Function calling、structured output&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Regex&lt;/td>
 &lt;td>Regular expression&lt;/td>
 &lt;td>受限文字格式（如 phone number、email）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CFG（Context-Free Grammar）&lt;/td>
 &lt;td>BNF 等 grammar 描述合法語法&lt;/td>
 &lt;td>Code generation、DSL、SQL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Choice list&lt;/td>
 &lt;td>一組固定字串選項&lt;/td>
 &lt;td>Classification、enum 輸出&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>主流實作對比：&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>&lt;strong>XGrammar&lt;/strong>&lt;/td>
 &lt;td>Pre-compile grammar → token mask cache、極快&lt;/td>
 &lt;td>vLLM / SGLang / TensorRT-LLM 預設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>outlines&lt;/td>
 &lt;td>Python lib、JSON schema / regex / CFG&lt;/td>
 &lt;td>用 Transformers / vLLM&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lm-format-enforcer&lt;/td>
 &lt;td>Lazy compile、適合動態 grammar&lt;/td>
 &lt;td>Hugging Face Transformers&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>guidance&lt;/td>
 &lt;td>Microsoft 系、API 較高階&lt;/td>
 &lt;td>自家 server&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>llama.cpp grammar&lt;/td>
 &lt;td>Built-in GBNF（GGML BNF）&lt;/td>
 &lt;td>llama.cpp 內建&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>讀 sampling / structured output / function calling 進階文件看到「constrained decoding」「grammar mask」「JSON schema enforcement」就是這 framing。寫 code 場景的判讀：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>何時值得用&lt;/strong>：需要 100% 合法 JSON / 特定格式、function calling spec 嚴格、structured output 不可有解析錯誤&lt;/li>
&lt;li>&lt;strong>不該用的情況&lt;/strong>：自由 / 創意輸出（會限制模型表達）、grammar 太嚴讓模型「該說的話說不出來」（如 enum 不含「unknown」、模型強制選錯）&lt;/li>
&lt;li>&lt;strong>跟 function calling 的關係&lt;/strong>：function calling 是「模型訓練 + structured output」、constrained decoding 是 sampling 層的工程實作、可獨立組合&lt;/li>
&lt;li>&lt;strong>加速 vs 拖慢&lt;/strong>：常見誤解是 grammar 拖慢 — 實測 XGrammar 等 pre-compiled 實作反而&lt;strong>加速&lt;/strong>生成（跳過 boilerplate token 直接生關鍵 token、節省 forward pass）&lt;/li>
&lt;li>&lt;strong>跟 &lt;a href="https://tarrragon.github.io/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 章節&lt;/a> 的關係&lt;/strong>：本卡是定義、章節是內部機制（token mask 計算、CFG 編譯、性能取捨）&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>Constrained decoding（受限解碼）的核心概念是「<strong>推論時用 grammar 動態算出每個位置的合法 token mask、把不合法 token 的 <a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit</a> 設成 -∞、<a href="/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax</a> 後機率為 0</strong>」。是 <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>（JSON mode / function calling 的合法性保證）背後的 sampling 機制。代表實作：XGrammar、outlines、lm-format-enforcer、guidance、SGLang。</p>
<h2 id="概念位置">概念位置</h2>
<p>跟既有 sampling 概念的層次：</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">模型 forward pass → logits（每個 vocab token 一個分數）
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ apply temperature
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ apply grammar mask（constrained decoding）  ← 本卡聚焦
</span></span><span class="line"><span class="ln">4</span><span class="cl">       - 算出當下位置的合法 token 集合
</span></span><span class="line"><span class="ln">5</span><span class="cl">       - 不合法 token 的 logit 設 -∞
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ↓ softmax → 機率分佈
</span></span><span class="line"><span class="ln">7</span><span class="cl">   ↓ sampling（greedy / top-p / top-k）
</span></span><span class="line"><span class="ln">8</span><span class="cl">   ↓ next token</span></span></code></pre></div><p>主要 grammar 類型：</p>
<table>
  <thead>
      <tr>
          <th>Grammar 類型</th>
          <th>描述</th>
          <th>用例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSON Schema</td>
          <td>標準 JSON schema 定義合法 JSON 結構</td>
          <td>Function calling、structured output</td>
      </tr>
      <tr>
          <td>Regex</td>
          <td>Regular expression</td>
          <td>受限文字格式（如 phone number、email）</td>
      </tr>
      <tr>
          <td>CFG（Context-Free Grammar）</td>
          <td>BNF 等 grammar 描述合法語法</td>
          <td>Code generation、DSL、SQL</td>
      </tr>
      <tr>
          <td>Choice list</td>
          <td>一組固定字串選項</td>
          <td>Classification、enum 輸出</td>
      </tr>
  </tbody>
</table>
<p>主流實作對比：</p>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>機制</th>
          <th>推論伺服器整合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>XGrammar</strong></td>
          <td>Pre-compile grammar → token mask cache、極快</td>
          <td>vLLM / SGLang / TensorRT-LLM 預設</td>
      </tr>
      <tr>
          <td>outlines</td>
          <td>Python lib、JSON schema / regex / CFG</td>
          <td>用 Transformers / vLLM</td>
      </tr>
      <tr>
          <td>lm-format-enforcer</td>
          <td>Lazy compile、適合動態 grammar</td>
          <td>Hugging Face Transformers</td>
      </tr>
      <tr>
          <td>guidance</td>
          <td>Microsoft 系、API 較高階</td>
          <td>自家 server</td>
      </tr>
      <tr>
          <td>llama.cpp grammar</td>
          <td>Built-in GBNF（GGML BNF）</td>
          <td>llama.cpp 內建</td>
      </tr>
  </tbody>
</table>
<h2 id="設計責任">設計責任</h2>
<p>讀 sampling / structured output / function calling 進階文件看到「constrained decoding」「grammar mask」「JSON schema enforcement」就是這 framing。寫 code 場景的判讀：</p>
<ol>
<li><strong>何時值得用</strong>：需要 100% 合法 JSON / 特定格式、function calling spec 嚴格、structured output 不可有解析錯誤</li>
<li><strong>不該用的情況</strong>：自由 / 創意輸出（會限制模型表達）、grammar 太嚴讓模型「該說的話說不出來」（如 enum 不含「unknown」、模型強制選錯）</li>
<li><strong>跟 function calling 的關係</strong>：function calling 是「模型訓練 + structured output」、constrained decoding 是 sampling 層的工程實作、可獨立組合</li>
<li><strong>加速 vs 拖慢</strong>：常見誤解是 grammar 拖慢 — 實測 XGrammar 等 pre-compiled 實作反而<strong>加速</strong>生成（跳過 boilerplate token 直接生關鍵 token、節省 forward pass）</li>
<li><strong>跟 <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> 的關係</strong>：本卡是定義、章節是內部機制（token mask 計算、CFG 編譯、性能取捨）</li>
</ol>
]]></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>3.10 Constrained decoding 內部：grammar mask 跟性能取捨</title><link>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/constrained-decoding-internals/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/constrained-decoding-internals/</guid><description>&lt;p>&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">3.5 sampling-and-decoding&lt;/a> 寫了 greedy / beam / top-p / top-k sampling、是「在合法輸出中選下一個 token」的基本機制。&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 application-protocols&lt;/a> 寫了 function calling / structured output 的應用層 — 但「為什麼 LLM 能保證輸出合法 JSON」這層原理在前兩章都沒展開。本章補 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a> 的內部機制：token mask 怎麼算、JSON schema / regex / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">CFG&lt;/a> 三種 grammar、為什麼 XGrammar 等實作反而加速生成。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋「grammar 強制」是在 sampling 階段哪一步做的。&lt;/li>
&lt;li>區分 JSON schema / regex / CFG 三種 grammar 的適用場景。&lt;/li>
&lt;li>看 XGrammar / outlines / llama.cpp grammar 等實作、能對應到本章 framing。&lt;/li>
&lt;li>判讀「constrained decoding 加速還是拖慢」的具體場景。&lt;/li>
&lt;/ol>
&lt;h2 id="sampling-階段的位置">Sampling 階段的位置&lt;/h2>
&lt;p>回顧 LLM 輸出流程（見 &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">3.5&lt;/a>）：&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">[forward pass] → logits（vocab_size 維、每個 token 一個實數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ apply temperature（logits / T）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ↓ apply constrained decoding（本章聚焦） ← grammar mask
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓ softmax → probability distribution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ↓ top-p / top-k / sampling
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ next token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Constrained decoding 在 softmax &lt;strong>之前&lt;/strong>插入 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">grammar mask&lt;/a>：&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">For each position：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 1. Grammar 算當前位置的「合法 token 集合」（vocab 子集）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> 2. 對不在合法集的 token、logit 設 -∞
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> 3. Softmax 後、不合法 token 機率為 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 4. Sampling 只可能選到合法 token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>關鍵理解：grammar 不改變模型本身、不改變 logits 數值（除了 mask 部分）、只是&lt;strong>限制 sampling 空間&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<p><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">3.5 sampling-and-decoding</a> 寫了 greedy / beam / top-p / top-k sampling、是「在合法輸出中選下一個 token」的基本機制。<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 application-protocols</a> 寫了 function calling / structured output 的應用層 — 但「為什麼 LLM 能保證輸出合法 JSON」這層原理在前兩章都沒展開。本章補 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a> 的內部機制：token mask 怎麼算、JSON schema / regex / <a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">CFG</a> 三種 grammar、為什麼 XGrammar 等實作反而加速生成。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋「grammar 強制」是在 sampling 階段哪一步做的。</li>
<li>區分 JSON schema / regex / CFG 三種 grammar 的適用場景。</li>
<li>看 XGrammar / outlines / llama.cpp grammar 等實作、能對應到本章 framing。</li>
<li>判讀「constrained decoding 加速還是拖慢」的具體場景。</li>
</ol>
<h2 id="sampling-階段的位置">Sampling 階段的位置</h2>
<p>回顧 LLM 輸出流程（見 <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">3.5</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">[forward pass] → logits（vocab_size 維、每個 token 一個實數）
</span></span><span class="line"><span class="ln">2</span><span class="cl">       ↓ apply temperature（logits / T）
</span></span><span class="line"><span class="ln">3</span><span class="cl">       ↓ apply constrained decoding（本章聚焦）  ← grammar mask
</span></span><span class="line"><span class="ln">4</span><span class="cl">       ↓ softmax → probability distribution
</span></span><span class="line"><span class="ln">5</span><span class="cl">       ↓ top-p / top-k / sampling
</span></span><span class="line"><span class="ln">6</span><span class="cl">       ↓ next token</span></span></code></pre></div><p>Constrained decoding 在 softmax <strong>之前</strong>插入 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">grammar mask</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">For each position：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  1. Grammar 算當前位置的「合法 token 集合」（vocab 子集）
</span></span><span class="line"><span class="ln">3</span><span class="cl">  2. 對不在合法集的 token、logit 設 -∞
</span></span><span class="line"><span class="ln">4</span><span class="cl">  3. Softmax 後、不合法 token 機率為 0
</span></span><span class="line"><span class="ln">5</span><span class="cl">  4. Sampling 只可能選到合法 token</span></span></code></pre></div><p>關鍵理解：grammar 不改變模型本身、不改變 logits 數值（除了 mask 部分）、只是<strong>限制 sampling 空間</strong>。</p>
<h2 id="三種主流-grammar">三種主流 grammar</h2>
<h3 id="json-schema">JSON Schema</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;object&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;age&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;integer&#34;</span><span class="p">,</span> <span class="nt">&#34;minimum&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nt">&#34;required&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>LLM 輸出必須是合法 JSON 且符合 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">當前已生：&#39;{&#34;name&#34;: &#34;alice&#34;, &#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ 算下一個合法 token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - 必須繼續產合法 JSON
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - schema 還沒填 age（optional）但 name 已填、所以 } 合法、&#34;age&#34; 也合法
</span></span><span class="line"><span class="ln">5</span><span class="cl">  - 不合法：&#39;{&#39; / &#39;]&#39; / 任意其他 key
</span></span><span class="line"><span class="ln">6</span><span class="cl">  ↓ Token mask 套用
</span></span><span class="line"><span class="ln">7</span><span class="cl">  → 模型只能選 } 或 &#34;age&#34;</span></span></code></pre></div><h3 id="regex">Regex</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">\d{3}-\d{4}-\d{4}  # 台灣 phone number 格式</span></span></code></pre></div><p>LLM 輸出必須符合 regex。實作：</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">當前已生：&#39;09&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ 算下一個合法 token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - regex 期望 \d 接下來
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - 合法 token：&#39;0&#39;-&#39;9&#39; 開頭的 token
</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">  ↓ Token mask</span></span></code></pre></div><h3 id="cfgcontext-free-grammar">CFG（Context-Free Grammar）</h3>
<p>用 <a href="/blog/llm/knowledge-cards/bnf/" data-link-title="BNF（Backus-Naur Form）" data-link-desc="用遞迴產生式描述語法的經典記法，是 CFG、parser 與 grammar-constrained sampling 常見的基礎表示">BNF</a> / EBNF 描述合法語法：</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">expr   ::= term (&#34;+&#34; term)*
</span></span><span class="line"><span class="ln">2</span><span class="cl">term   ::= number | &#34;(&#34; expr &#34;)&#34;
</span></span><span class="line"><span class="ln">3</span><span class="cl">number ::= [0-9]+</span></span></code></pre></div><p>LLM 輸出必須符合此 grammar。實作：</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">當前已生：&#39;(1+2&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ CFG 算當下合法 next token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - 已 match 部分 term + &#34;+&#34; + term
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - 合法：&#34;)&#34; 或 &#34;+&#34; 開始新 term
</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">  ↓ Token mask</span></span></code></pre></div><p>CFG 是最強表達力、但實作最複雜。SQL / 程式碼 generation 多用 CFG-based grammar。</p>
<h2 id="xgrammar-的-pre-compile-機制">XGrammar 的 pre-compile 機制</h2>
<p>XGrammar（Dong et al., 2024）是 2024-2025 主流的高效實作。核心優化：</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">Naive 實作（如 outlines 早期版）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  每次 sampling 都重算 grammar state
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  每個 token 都跑一次 grammar parse
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  → 開銷大、可能拖慢 generation
</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">XGrammar 優化：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  1. Pre-compile grammar → 確定性 DFA / push-down automaton
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  2. Cache 每個 grammar state 的「合法 token mask bitmap」
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  3. Sampling 時 O(1) 查表得到 mask
</span></span><span class="line"><span class="ln">10</span><span class="cl">  4. Mask 用 bitwise op 套用到 logits</span></span></code></pre></div><p>效果：grammar 套用 overhead 趨近 0、甚至<strong>因為跳過 boilerplate token 反而加速</strong>：</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">無 grammar 生 JSON：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  {     &#34; n a m e &#34;     : &#34; a l i c e &#34; ...
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ←     每個 token 都跑 forward pass    →
</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">有 grammar 生 JSON：
</span></span><span class="line"><span class="ln">6</span><span class="cl">  跳過固定 token（{ &#34; : 等）、直接生關鍵字串
</span></span><span class="line"><span class="ln">7</span><span class="cl">  forward pass 次數減少
</span></span><span class="line"><span class="ln">8</span><span class="cl">  → 實測加速 1.5-3×</span></span></code></pre></div><p>主流推論伺服器（vLLM、SGLang、TensorRT-LLM）2025 後預設用 XGrammar。</p>
<h2 id="性能取捨加速還是拖慢">性能取捨：加速還是拖慢</h2>
<p>常見誤解：「constrained decoding 拖慢生成」。實際看實作：</p>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>性能</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>XGrammar（vLLM 等預設）</td>
          <td><strong>加速 1.5-3×</strong>（跳過固定 token、forward pass 次數減）</td>
      </tr>
      <tr>
          <td>outlines（pre-compiled）</td>
          <td>略加速到中性</td>
      </tr>
      <tr>
          <td>outlines（lazy compile）</td>
          <td>略拖慢</td>
      </tr>
      <tr>
          <td>guidance（高階 API）</td>
          <td>中性到略拖慢</td>
      </tr>
      <tr>
          <td>llama.cpp grammar</td>
          <td>中性</td>
      </tr>
      <tr>
          <td>Lazy / naive 實作</td>
          <td>拖慢</td>
      </tr>
  </tbody>
</table>
<p>判讀：用主流推論伺服器（vLLM / SGLang）+ XGrammar 路線、constrained decoding 通常加速；自己寫 naive 實作可能拖慢。</p>
<h2 id="跟-function-calling-的關係">跟 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling</a> 的關係</h2>
<p>兩個概念可獨立、也可疊用：</p>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pure function calling（無 constrained decoding）</td>
          <td>靠模型訓練、不強制合法、可能有解析失敗</td>
      </tr>
      <tr>
          <td>Pure constrained decoding（無 function calling 訓練）</td>
          <td>推論時強制合法、但模型不一定知道「何時該呼叫工具」</td>
      </tr>
      <tr>
          <td>Function calling + constrained decoding</td>
          <td>訓練教模型何時呼叫、grammar 強制呼叫格式合法</td>
      </tr>
  </tbody>
</table>
<p>主流商業 API（Anthropic / OpenAI / Gemini）的 function calling 通常<strong>內部已用 constrained decoding</strong>、開發者無感。本地推論用 vLLM / SGLang + XGrammar 也是預設組合。</p>
<h2 id="失敗模式">失敗模式</h2>
<h3 id="1-grammar-太嚴讓模型該說的話說不出來">1. Grammar 太嚴讓模型「該說的話說不出來」</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">Schema 強制 type 是 enum [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;]
</span></span><span class="line"><span class="ln">2</span><span class="cl">但真實答案是「none of the above」
</span></span><span class="line"><span class="ln">3</span><span class="cl">→ 模型強制選 A/B/C、輸出語義錯誤</span></span></code></pre></div><p><strong>緩解</strong>：enum 加 fallback option（&ldquo;unknown&rdquo; / &ldquo;none&rdquo;）、schema 別過度約束</p>
<h3 id="2-cfg-太複雜編譯失敗--慢">2. CFG 太複雜、編譯失敗 / 慢</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">復雜 CFG（如完整 SQL grammar）pre-compile 數秒
</span></span><span class="line"><span class="ln">2</span><span class="cl">production cold start 多花這數秒</span></span></code></pre></div><p><strong>緩解</strong>：cache compiled grammar、用較簡單 grammar 版本（如「INSERT only」而非完整 SQL）</p>
<h3 id="3-grammar-跟-model-訓練分佈不符">3. Grammar 跟 model 訓練分佈不符</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">Schema 要求很罕見的 JSON 結構
</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">即使 grammar 強制合法、語義可能空洞</span></span></code></pre></div><p><strong>緩解</strong>：grammar 用模型訓練過的形態（function call spec、common JSON）、自定義 schema 加 few-shot example</p>
<h3 id="4-streaming-跟-grammar-衝突">4. Streaming 跟 grammar 衝突</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">Streaming 邊生邊輸出
</span></span><span class="line"><span class="ln">2</span><span class="cl">Grammar 中段 token 可能要 backtrack 修正
</span></span><span class="line"><span class="ln">3</span><span class="cl">streaming UX 跳字</span></span></code></pre></div><p><strong>緩解</strong>：用 incremental-parsing grammar（XGrammar 支援）、避免 backtrack 場景</p>
<h3 id="5-constrained-decoding-蓋過-function-calling-訓練">5. Constrained decoding 蓋過 function calling 訓練</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">模型訓練用 OpenAI function spec、應用強制套 Anthropic tools 的 grammar
</span></span><span class="line"><span class="ln">2</span><span class="cl">模型輸出「合法但語意空洞」（schema 對、欄位胡亂填）</span></span></code></pre></div><p><strong>緩解</strong>：grammar spec 跟模型訓練 spec 一致、別人工維護兩份不同 schema</p>
<h2 id="何時不該用-constrained-decoding">何時不該用 constrained decoding</h2>
<ol>
<li><strong>自由 / 創意輸出</strong>：寫作、brainstorming、grammar 限制模型表達</li>
<li><strong>可靠的 model + simple format</strong>：模型本身能穩定輸出 JSON、grammar overhead 不必要</li>
<li><strong>Grammar 太嚴有語義錯</strong>：見失敗模式 1</li>
<li><strong>Streaming + 複雜 grammar</strong>：streaming UX 受影響</li>
</ol>
<h2 id="主流實作詳細">主流實作詳細</h2>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>XGrammar</strong></td>
          <td>Production 高吞吐（vLLM / SGLang / TensorRT-LLM 預設）</td>
      </tr>
      <tr>
          <td><strong>outlines</strong></td>
          <td>Python script、開發 / 實驗、HF Transformers 用</td>
      </tr>
      <tr>
          <td><strong>lm-format-enforcer</strong></td>
          <td>動態 grammar、運行時切 schema</td>
      </tr>
      <tr>
          <td><strong>guidance</strong></td>
          <td>Microsoft 系、想要 high-level API</td>
      </tr>
      <tr>
          <td><strong>llama.cpp grammar</strong></td>
          <td>本地 GGUF 模型、GBNF 語法</td>
      </tr>
      <tr>
          <td><strong>OpenAI Structured Outputs</strong></td>
          <td>OpenAI API、JSON schema、開發者無感</td>
      </tr>
      <tr>
          <td><strong>Anthropic JSON mode</strong></td>
          <td>Anthropic API、簡化版</td>
      </tr>
  </tbody>
</table>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Constrained decoding 在 sampling 哪一步插入（softmax 之前）的 framing</li>
<li>三種 grammar 類型（JSON schema / regex / CFG）的分類</li>
<li>Token mask 機制（不合法 token logit 設 -∞）</li>
<li>「正確實作下加速、不是拖慢」的反直覺結論</li>
<li>5 大失敗模式分類</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>XGrammar / outlines 等實作的具體效能跟功能</li>
<li>主流推論伺服器的預設 grammar engine</li>
<li>JSON schema spec 標準化（新版會出）</li>
<li>Function calling + constrained decoding 是否會被 native multimodal 取代</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">3.11 想學更深</a>、整個模組三理論基礎走完。</p>
]]></content:encoded></item></channel></rss>