<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第七章 结构化生成与 API 表面 on Machine Learning 学习笔记</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/</link><description>Recent content in 第七章 结构化生成与 API 表面 on Machine Learning 学习笔记</description><generator>Hugo</generator><language>en</language><atom:link href="https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/index.xml" rel="self" type="application/rss+xml"/><item><title>7.1 response format 与 grammar constraints</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/response-format-and-grammar-constraints/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/response-format-and-grammar-constraints/</guid><description>&lt;h1 id="response-format-与-grammar-constraints"&gt;response format 与 grammar constraints&lt;a class="anchor" href="#response-format-%e4%b8%8e-grammar-constraints"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;到了这一章，问题已经不再是“请求怎样走”或者“执行层怎样选 token”，而是“这些 token 在生成时怎样被限制在某种结构空间里”。这就是 response format 和 grammar constraints 进入执行链的地方。&lt;/p&gt;
&lt;h2 id="这一节解决什么问题"&gt;这一节解决什么问题&lt;a class="anchor" href="#%e8%bf%99%e4%b8%80%e8%8a%82%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这一节主要回答三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;response_format&lt;/code&gt; 为什么最后会落到采样参数里；&lt;/li&gt;
&lt;li&gt;grammar constraint 为什么不是后处理，而是生成期约束；&lt;/li&gt;
&lt;li&gt;为什么结构化约束必须和执行层一起理解，而不能只当作 API 表面功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="response_format-真正落在哪"&gt;&lt;code&gt;response_format&lt;/code&gt; 真正落在哪&lt;a class="anchor" href="#response_format-%e7%9c%9f%e6%ad%a3%e8%90%bd%e5%9c%a8%e5%93%aa"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;从 &lt;a href="https://github.com/sgl-project/sglang/blob/1519acf37c23f2189adb93f57ca9cd2db1bebf18/python/sglang/srt/entrypoints/openai/protocol.py#L742-L820" title="python/sglang/srt/entrypoints/openai/protocol.py:L742-L820"&gt;&lt;code&gt;ChatCompletionRequest.to_sampling_params&lt;/code&gt;&lt;/a&gt;
 可以直接看出，&lt;code&gt;response_format&lt;/code&gt; 并不会停留在 OpenAI-compatible 请求表面，而是会被转换成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json_schema&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;structural_tag&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;或其他结构化约束字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，对 runtime 来说，它最后看到的不是 “response_format 是什么对象”，而是“这一轮 token selection 受什么结构约束”。&lt;/p&gt;
&lt;p&gt;如果把这条链先压成图，会更容易看清：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart LR
 A[&amp;#34;response_format&amp;#34;] --&amp;gt; B[&amp;#34;to_sampling_params(...)&amp;#34;]
 B --&amp;gt; C[&amp;#34;json_schema / structural_tag / regex / ebnf&amp;#34;]
 C --&amp;gt; D[&amp;#34;SamplingParams&amp;#34;]
 D --&amp;gt; E[&amp;#34;token selection&amp;#34;]&lt;/pre&gt;&lt;p&gt;图里最重要的一点是：&lt;code&gt;response_format&lt;/code&gt; 的终点不是“另一个响应对象”，而是执行层里的约束输入。&lt;/p&gt;
&lt;h2 id="为什么-grammar-constraint-不是后处理"&gt;为什么 grammar constraint 不是后处理&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88-grammar-constraint-%e4%b8%8d%e6%98%af%e5%90%8e%e5%a4%84%e7%90%86"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果结构化输出只是后处理，系统应该先自由生成，再在结果出来后检查格式。但当前设计不是这样：约束在 &lt;code&gt;to_sampling_params(...)&lt;/code&gt; 阶段就进入了执行层的参数集合。&lt;/p&gt;</description></item><item><title>7.2 tool choice、function calling 与 parser</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/tool-choice-function-calling-and-parser/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/tool-choice-function-calling-and-parser/</guid><description>&lt;h1 id="tool-choicefunction-calling-与-parser"&gt;tool choice、function calling 与 parser&lt;a class="anchor" href="#tool-choicefunction-calling-%e4%b8%8e-parser"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;上一节讲的是“怎样限制输出空间”，这一节讲的是“怎样把已经生成出来的结构解释成工具调用语义”。这两件事容易被混在一起，但它们不是同一个层次的问题。&lt;/p&gt;
&lt;h2 id="这一节解决什么问题"&gt;这一节解决什么问题&lt;a class="anchor" href="#%e8%bf%99%e4%b8%80%e8%8a%82%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这一节主要回答三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;tool choice 为什么最终还是要落到执行约束里；&lt;/li&gt;
&lt;li&gt;function calling parser 为什么不是 grammar backend；&lt;/li&gt;
&lt;li&gt;parser 在 streaming 和 non-streaming 场景里分别承担什么角色。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="一张图先看-function-calling-的两层关系"&gt;一张图先看 function calling 的两层关系&lt;a class="anchor" href="#%e4%b8%80%e5%bc%a0%e5%9b%be%e5%85%88%e7%9c%8b-function-calling-%e7%9a%84%e4%b8%a4%e5%b1%82%e5%85%b3%e7%b3%bb"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;flowchart LR
 A[&amp;#34;tool_choice / tools&amp;#34;] --&amp;gt; B[&amp;#34;get_structure_constraint(...)&amp;#34;]
 B --&amp;gt; C[&amp;#34;json_schema / structural_tag constraint&amp;#34;]
 C --&amp;gt; D[&amp;#34;SamplingParams&amp;#34;]
 D --&amp;gt; E[&amp;#34;model output&amp;#34;]
 E --&amp;gt; F[&amp;#34;FunctionCallParser parse&amp;#34;]
 F --&amp;gt; G[&amp;#34;tool call objects&amp;#34;]&lt;/pre&gt;&lt;p&gt;这张图最重要的一点是：tool choice 先改写执行期约束，parser 再解释输出结果。这两件事都属于 function calling，但不发生在同一层。&lt;/p&gt;
&lt;h2 id="parser-解决的不是限制而是解释"&gt;parser 解决的不是“限制”，而是“解释”&lt;a class="anchor" href="#parser-%e8%a7%a3%e5%86%b3%e7%9a%84%e4%b8%8d%e6%98%af%e9%99%90%e5%88%b6%e8%80%8c%e6%98%af%e8%a7%a3%e9%87%8a"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/sgl-project/sglang/blob/1519acf37c23f2189adb93f57ca9cd2db1bebf18/python/sglang/srt/function_call/function_call_parser.py#L39-L120" title="python/sglang/srt/function_call/function_call_parser.py:L39-L120"&gt;&lt;code&gt;FunctionCallParser&lt;/code&gt;&lt;/a&gt;
 的职责从类定义就写得很清楚：它处理的是 function / tool call 的解析，而不是 token 级约束本身。&lt;/p&gt;</description></item><item><title>7.3 Responses API 与 built-in tools</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/responses-api-and-built-in-tools/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part3-execution-core/structured-generation/responses-api-and-built-in-tools/</guid><description>&lt;h1 id="responses-api-与-built-in-tools"&gt;Responses API 与 built-in tools&lt;a class="anchor" href="#responses-api-%e4%b8%8e-built-in-tools"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;前两节已经把结构化约束和 function calling parser 讲清楚了，但如果只停在这里，这一章仍然缺一块很重要的表面：当响应不再只是一次性文本，而是一个可以被追踪、检索、取消，甚至带着工具上下文继续推进的运行中实体时，系统怎样管理它。&lt;/p&gt;
&lt;h2 id="这一节解决什么问题"&gt;这一节解决什么问题&lt;a class="anchor" href="#%e8%bf%99%e4%b8%80%e8%8a%82%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这一节主要回答三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Responses API 为什么不只是“另一种返回格式”；&lt;/li&gt;
&lt;li&gt;background request、retrieve、cancel 为什么会让 response 长成一个实体；&lt;/li&gt;
&lt;li&gt;built-in tools 为什么在这个 surface 里更显眼。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="responses-api-改写的不是字段而是工作流"&gt;Responses API 改写的不是字段，而是工作流&lt;a class="anchor" href="#responses-api-%e6%94%b9%e5%86%99%e7%9a%84%e4%b8%8d%e6%98%af%e5%ad%97%e6%ae%b5%e8%80%8c%e6%98%af%e5%b7%a5%e4%bd%9c%e6%b5%81"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/sgl-project/sglang/blob/1519acf37c23f2189adb93f57ca9cd2db1bebf18/python/sglang/srt/entrypoints/openai/serving_responses.py#L162-L340" title="python/sglang/srt/entrypoints/openai/serving_responses.py:L162-L340"&gt;&lt;code&gt;OpenAIServingResponses.create_responses&lt;/code&gt;&lt;/a&gt;
 最值得注意的不是参数多，而是它把 response 变成了一个之后还能被继续操作的对象。&lt;/p&gt;
&lt;p&gt;一旦进入 background 模式，请求就不再只是“一次 request-response”，而会长出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request_id&lt;/code&gt; / &lt;code&gt;response_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;response store&lt;/li&gt;
&lt;li&gt;background task lifecycle&lt;/li&gt;
&lt;li&gt;retrieve / cancel 语义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明 Responses API 改写的不是字段，而是工作流。&lt;/p&gt;
&lt;p&gt;这条工作流如果先压成一张图，会更容易看清：&lt;/p&gt;
&lt;pre class="mermaid"&gt;flowchart LR
 A[&amp;#34;create_responses(...)&amp;#34;] --&amp;gt; B[&amp;#34;foreground response&amp;#34;]
 A --&amp;gt; C[&amp;#34;background task + response_store&amp;#34;]
 C --&amp;gt; D[&amp;#34;retrieve_responses()&amp;#34;]
 C --&amp;gt; E[&amp;#34;cancel_responses()&amp;#34;]&lt;/pre&gt;&lt;p&gt;图里最重要的一点是：一旦进入 background 模式，response 就不再只是当前连接里的一次性结果，而会继续活在系统里。&lt;/p&gt;</description></item></channel></rss>