<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Constrained Decoding on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/constrained-decoding/</link>
    <description>KbWen is a practical technology blog about AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Mon, 12 Oct 2026 10:15:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/constrained-decoding/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How constrained decoding works for JSON</title>
      <link>https://www.kbwen.com/constrained-decoding-valid-json/</link>
      <pubDate>Mon, 12 Oct 2026 10:15:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/constrained-decoding-valid-json/</guid>
      <description>Let&amp;#39;s see how a JSON schema, or a plain JSON mode, changes what a model is able to write.</description>
      <content:encoded><![CDATA[<p>Ask an inference engine like vLLM or SGLang for structured output, either a JSON schema or a plain JSON mode, and a technique called constrained decoding does the work. The more familiar way to get JSON out of a model is to ask for it in the prompt, parse whatever comes back, and try again when parsing fails. Constrained decoding works earlier than that, while the tokens are still being picked.</p>
<h2 id="what-happens-at-each-step">What happens at each step</h2>
<p>At every step a model gives each token in its vocabulary a score, called a logit, and the decoder turns those scores into probabilities and <a href="/llm-temperature-top-k-top-p/">samples one token</a>. Nothing in that step checks for JSON. When the prompt is the only thing asking for JSON, a trailing comma or a sentence of prose can still come out. A constrained decoder adds one thing before the sampling. It holds a grammar, either a general one for JSON or one compiled from your schema, and it keeps track of where the output currently is inside that grammar. After <code>{&quot;name&quot;:</code>, for example, a value has to come next, and a closing brace is not allowed yet.</p>
<p>From that position the decoder marks every token that could legally come next, and it sets the logit of every other token to negative infinity. The softmax turns negative infinity into a probability of exactly zero, and a token with zero probability is never sampled, however high the model had scored it. The tokens that are still legal keep their scores untouched.</p>
<p>Once a token has been sampled, it is fed back into the grammar, the position moves forward past it, and the next step gets a fresh mask. Tokens are usually a few characters long, while the grammar is written one character at a time. A token passes only if all of its characters fit where the output is now, and <code>true</code> can go through as a single token where a value is expected.</p>
<p>This is the approach <a href="https://github.com/mlc-ai/xgrammar">xgrammar</a> takes. Its README currently lists the library as &ldquo;the default structured generation backend for most LLM inference engines&rdquo;, vLLM, SGLang and TensorRT-LLM among them, and describes the result as &ldquo;100% structural correctness of the output&rdquo;. That holds for a reply that finishes; one cut off by the token limit still stops partway through an object.</p>
<h2 id="next-running-it-on-twelve-tokens">Next, running it on twelve tokens</h2>
<p>Here is xgrammar with its built-in JSON grammar and a vocabulary of twelve tokens, writing <code>{&quot;name&quot;: 42}</code> one token at a time:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">xgrammar</span> <span class="k">as</span> <span class="nn">xgr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># A deliberately tiny vocabulary, so the whole mask is printable.</span>
</span></span><span class="line"><span class="cl"><span class="n">vocab</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;{&#39;</span><span class="p">,</span> <span class="s1">&#39;}&#39;</span><span class="p">,</span> <span class="s1">&#39;&#34;&#39;</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="p">,</span> <span class="s1">&#39;age&#39;</span><span class="p">,</span> <span class="s1">&#39;:&#39;</span><span class="p">,</span> <span class="s1">&#39;,&#39;</span><span class="p">,</span> <span class="s1">&#39;42&#39;</span><span class="p">,</span> <span class="s1">&#39;true&#39;</span><span class="p">,</span> <span class="s1">&#39; &#39;</span><span class="p">,</span> <span class="s1">&#39;hello&#39;</span><span class="p">,</span> <span class="s1">&#39;xyz&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">info</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">TokenizerInfo</span><span class="p">(</span><span class="n">vocab</span><span class="p">,</span> <span class="n">vocab_size</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">vocab</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">compiler</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">GrammarCompiler</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">matcher</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">GrammarMatcher</span><span class="p">(</span><span class="n">compiler</span><span class="o">.</span><span class="n">compile_grammar</span><span class="p">(</span><span class="n">xgr</span><span class="o">.</span><span class="n">Grammar</span><span class="o">.</span><span class="n">builtin_json_grammar</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bitmask</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">allocate_token_bitmask</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">vocab</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">allowed_now</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">matcher</span><span class="o">.</span><span class="n">fill_next_token_bitmask</span><span class="p">(</span><span class="n">bitmask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">logits</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">zeros</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">vocab</span><span class="p">))</span>          <span class="c1"># the model&#39;s scores; all equal here</span>
</span></span><span class="line"><span class="cl">    <span class="n">xgr</span><span class="o">.</span><span class="n">apply_token_bitmask_inplace</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">bitmask</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">survivors</span> <span class="o">=</span> <span class="p">[</span><span class="n">vocab</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">vocab</span><span class="p">))</span> <span class="k">if</span> <span class="n">logits</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="nb">float</span><span class="p">(</span><span class="s1">&#39;-inf&#39;</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">survivors</span><span class="p">,</span> <span class="n">logits</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">survivors</span><span class="p">,</span> <span class="n">logits</span> <span class="o">=</span> <span class="n">allowed_now</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;start:  &#34;</span><span class="p">,</span> <span class="n">survivors</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;logits: &#34;</span><span class="p">,</span> <span class="n">logits</span><span class="o">.</span><span class="n">tolist</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">&#39;{&#39;</span><span class="p">,</span> <span class="s1">&#39;&#34;&#39;</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="p">,</span> <span class="s1">&#39;&#34;&#39;</span><span class="p">,</span> <span class="s1">&#39;:&#39;</span><span class="p">,</span> <span class="s1">&#39;42&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">matcher</span><span class="o">.</span><span class="n">accept_string</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;after </span><span class="si">{</span><span class="n">token</span><span class="si">!r}</span><span class="s2">:&#34;</span><span class="p">,</span> <span class="n">allowed_now</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">start:   [&#39;{&#39;]
</span></span><span class="line"><span class="cl">logits:  [0.0, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf]
</span></span><span class="line"><span class="cl">after &#39;{&#39;: [&#39;}&#39;, &#39;&#34;&#39;, &#39; &#39;]
</span></span><span class="line"><span class="cl">after &#39;&#34;&#39;: [&#39;{&#39;, &#39;}&#39;, &#39;&#34;&#39;, &#39;name&#39;, &#39;age&#39;, &#39;:&#39;, &#39;,&#39;, &#39;42&#39;, &#39;true&#39;, &#39; &#39;, &#39;hello&#39;, &#39;xyz&#39;]
</span></span><span class="line"><span class="cl">after &#39;name&#39;: [&#39;{&#39;, &#39;}&#39;, &#39;&#34;&#39;, &#39;name&#39;, &#39;age&#39;, &#39;:&#39;, &#39;,&#39;, &#39;42&#39;, &#39;true&#39;, &#39; &#39;, &#39;hello&#39;, &#39;xyz&#39;]
</span></span><span class="line"><span class="cl">after &#39;&#34;&#39;: [&#39;:&#39;, &#39; &#39;]
</span></span><span class="line"><span class="cl">after &#39;:&#39;: [&#39;{&#39;, &#39;&#34;&#39;, &#39;42&#39;, &#39;true&#39;, &#39; &#39;]
</span></span><span class="line"><span class="cl">after &#39;42&#39;: [&#39;}&#39;, &#39;,&#39;, &#39;42&#39;, &#39; &#39;]
</span></span></code></pre></div><p>At the start only <code>{</code> is left, and the logits line shows how: one <code>0.0</code> and negative infinity for all the other choices, although every score started out equal, which means the brace there comes from the grammar alone.</p>
<h2 id="what-about-inside-a-string">What about inside a string?</h2>
<p>But look at the two lines after the opening quote of the key. Every token in the vocabulary is allowed again, including <code>{</code>, <code>:</code> and <code>xyz</code> (a little counterintuitive at first), because inside a string they are only characters, and all the grammar checks there is that the string stays a valid string.</p>
<p>Running the same twelve tokens against a one-field schema, an object with a required string <code>name</code>, leaves <code>name</code> as the only token allowed after <code>{&quot;</code>, and after the colon <code>42</code> is no longer allowed, only a quote or a space. The run above used the general JSON grammar, and there even the key was free. Once the opening quote of the value arrives, though, everything is allowed again.</p>
<p>So among the tokens that survive, the choice still comes from the model&rsquo;s own scores, which the mask leaves untouched. When those scores favour a wrong name or a made-up number, nothing in the grammar stops them. The JSON still parses, but from its shape alone you can&rsquo;t tell whether the values in it can be trusted.</p>
<p>This was put together from the xgrammar README and the two runs above, the general one and the schema one, both done with Python 3.12, xgrammar 0.2.8 and CPU-only torch. Every score in them was equal. They show the mask itself; how much the mask changes what a real model would have gone on to write is a separate question. Models and inference engines move fast, and each engine handles this a little differently, so the details here may date quickly; take it as one simple look at how it works.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>JSON schema 怎麼限制 LLM 的輸出</title>
      <link>https://www.kbwen.com/structured-output-token-mask/</link>
      <pubDate>Mon, 12 Oct 2026 10:10:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/structured-output-token-mask/</guid>
      <description>我們用 xgrammar 看看給了 JSON schema 之後，模型會受到甚麼影響。</description>
      <content:encoded><![CDATA[<p>用 API 呼叫模型的時候，常常會希望它回傳 JSON，我們就可以讓程式直接讀取、操作。只在 prompt 裡寫「請用 JSON 格式回答」，大部分時候是可以的，但偶爾前面會多一句說明、少了一個括號或是多了個符號，就會解析失敗。所以現在很多 API 和推論引擎都有 JSON mode 或結構化輸出（structured output）的選項，讓回來的內容通常照著格式處理。</p>
<p>模型產生文字的方式，是每一步替詞彙表裡所有的 token 計算分數，再從裡面挑出下一個 token。constrained decoding 則是在選擇挑取之前先把「選了就會讓格式壞掉」的 token 分數改成負無限大，這些 token 就不可能被選到，模型就被限縮在剩下的選項中挑選。</p>
<p>參考之前寫的相關文章：<a href="/llm-predicts-next-token/">大語言模型 LLM：其實做的事情比你想像中更單純</a></p>
<h2 id="剩下幾個-token">剩下幾個 token</h2>
<p><a href="https://github.com/mlc-ai/xgrammar">xgrammar</a> 是專門做這件事的開源函式庫，而且可以拿 tokenizer 來跑，不需要載入模型，因為要劃掉哪些 token 只跟 schema、詞彙表和目前進度到哪裡有關，跟模型的權重無關(感覺很反直覺)。下面用 Qwen2.5-0.5B-Instruct 的 tokenizer 來測試。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">xgrammar</span> <span class="k">as</span> <span class="nn">xgr</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">transformers</span> <span class="kn">import</span> <span class="n">AutoTokenizer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tok</span> <span class="o">=</span> <span class="n">AutoTokenizer</span><span class="o">.</span><span class="n">from_pretrained</span><span class="p">(</span><span class="s2">&#34;Qwen/Qwen2.5-0.5B-Instruct&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">info</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">TokenizerInfo</span><span class="o">.</span><span class="n">from_huggingface</span><span class="p">(</span><span class="n">tok</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">schema</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#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="cl">    <span class="s2">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">},</span> <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;integer&#34;</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;required&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;age&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;additionalProperties&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">compiled</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">GrammarCompiler</span><span class="p">(</span><span class="n">info</span><span class="p">)</span><span class="o">.</span><span class="n">compile_json_schema</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">schema</span><span class="p">),</span> <span class="n">any_whitespace</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">matcher</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">GrammarMatcher</span><span class="p">(</span><span class="n">compiled</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">bitmask</span> <span class="o">=</span> <span class="n">xgr</span><span class="o">.</span><span class="n">allocate_token_bitmask</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">info</span><span class="o">.</span><span class="n">vocab_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;vocab_size&#34;</span><span class="p">,</span> <span class="n">info</span><span class="o">.</span><span class="n">vocab_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tok</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;{&#34;name&#34;: &#34;Ada&#34;, &#34;age&#34;: 36}&#39;</span><span class="p">,</span> <span class="n">add_special_tokens</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">matcher</span><span class="o">.</span><span class="n">fill_next_token_bitmask</span><span class="p">(</span><span class="n">bitmask</span><span class="p">)</span>          <span class="c1"># 算出還能用的 token</span>
</span></span><span class="line"><span class="cl">    <span class="n">logits</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">vocab_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">xgr</span><span class="o">.</span><span class="n">apply_token_bitmask_inplace</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">bitmask</span><span class="p">)</span>  <span class="c1"># 不能用的分數改成 -inf</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">tok</span><span class="o">.</span><span class="n">decode</span><span class="p">([</span><span class="n">t</span><span class="p">])</span><span class="si">!r:</span><span class="s2">8</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="nb">int</span><span class="p">(</span><span class="n">torch</span><span class="o">.</span><span class="n">isfinite</span><span class="p">(</span><span class="n">logits</span><span class="p">)</span><span class="o">.</span><span class="n">sum</span><span class="p">())</span><span class="si">:</span><span class="s2">&gt;7</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">matcher</span><span class="o">.</span><span class="n">accept_token</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span></span></code></pre></div><p>環境是 Python 3.12.13、xgrammar 0.2.8、torch 2.14.1、transformers 5.19.0。左邊是送進去的 token，右邊是當下還可以使用的 token 數量：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">vocab_size 151665
</span></span><span class="line"><span class="cl">&#39;{&#34;&#39;           2
</span></span><span class="line"><span class="cl">&#39;name&#39;         4
</span></span><span class="line"><span class="cl">&#39;&#34;:&#39;           2
</span></span><span class="line"><span class="cl">&#39; &#34;&#39;         121
</span></span><span class="line"><span class="cl">&#39;Ada&#39;     147057
</span></span><span class="line"><span class="cl">&#39;&#34;,&#39;      147057
</span></span><span class="line"><span class="cl">&#39; &#34;&#39;           2
</span></span><span class="line"><span class="cl">&#39;age&#39;          3
</span></span><span class="line"><span class="cl">&#39;&#34;:&#39;           2
</span></span><span class="line"><span class="cl">&#39; &#39;            2
</span></span><span class="line"><span class="cl">&#39;3&#39;           11
</span></span><span class="line"><span class="cl">&#39;6&#39;           11
</span></span><span class="line"><span class="cl">&#39;}&#39;           11
</span></span></code></pre></div><p>一開始只能從兩個 token 裡選，<code>{</code> 或是 <code>{&quot;</code>，詞彙表裡其他選擇都被無視，因為 JSON 物件只能用左大括號開頭。接下來是 key，xgrammar 會照 schema 裡欄位寫的順序產生，我們看到第一個是 <code>name</code>，所以剩下的只有 <code>n</code>、<code>na</code>、<code>nam</code>、<code>name</code> 這四種組合；拼寫處理完之後是 <code>&quot;</code> 或 <code>&quot;:</code>，此時又回到兩個選擇。可以看到在這一階段，模型其實沒有什麼需要決定的，也沒有甚麼可選的，因為不管怎麼走，最後都會是 <code>{&quot;name&quot;:</code>。</p>
<p>但接下來到了 name 的值，可選的數字跳到 147,057 個，瞬間變多了，也就代表詞彙表裡大部分的 token 都可以用。而被劃掉的四千多個裡面，其中多數是含有換行或 tab 的 token。也就是說，這裡要填 <code>Ada</code>、<code>Bob</code> 還是一整句中文，都是模型自行挑選的，也不會被劃掉。後面 age 的位置，能用的只剩 0 到 9 的數字和負號，但到底要寫 36 還是 360，對規則來說都一樣。所以仔細看看可以發現，schema 負責規範的是格式，括號、引號、欄位名稱和型別。</p>
<h2 id="schema-和-prompt">schema 和 prompt</h2>
<p>那模型怎麼知道 <code>name</code> 要填名字、<code>age</code> 要填年紀？從剛剛過程來看，處理的都是劃掉不合格式的 token，並沒有告訴模型每個欄位是什麼意思。<a href="https://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md">llama.cpp 的 grammar 文件</a>寫到，JSON schema 只用來限制輸出，不會放進 prompt，模型本身看不到 schema；但工具呼叫（tool calling）則是例外，schema 會被放進 prompt。</p>
<p>因此在用這類功能的時候，還是記得要在 prompt 裡把每個欄位的意思寫清楚，像是 <code>age</code> 是實際年齡、單位是年，不然模型也會照格式輸出整數，只是這個數字你也不知道可不可信。至於其他服務會不會另外把 schema 放進 prompt，當成一併處理的資訊，就要看各自的文件以及其發展。</p>
<p>不過還是說明現今的AI模型發展很快，每家廠商的做法和服務都不一樣，可能這個資訊很快就會過時了，就當作一個簡易分享囉。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
