tool choice、function calling 与 parser#
上一节讲的是“怎样限制输出空间”,这一节讲的是“怎样把已经生成出来的结构解释成工具调用语义”。这两件事容易被混在一起,但它们不是同一个层次的问题。
这一节解决什么问题#
这一节主要回答三件事:
- tool choice 为什么最终还是要落到执行约束里;
- function calling parser 为什么不是 grammar backend;
- parser 在 streaming 和 non-streaming 场景里分别承担什么角色。
一张图先看 function calling 的两层关系#
flowchart LR
A["tool_choice / tools"] --> B["get_structure_constraint(...)"]
B --> C["json_schema / structural_tag constraint"]
C --> D["SamplingParams"]
D --> E["model output"]
E --> F["FunctionCallParser parse"]
F --> G["tool call objects"]这张图最重要的一点是:tool choice 先改写执行期约束,parser 再解释输出结果。这两件事都属于 function calling,但不发生在同一层。
parser 解决的不是“限制”,而是“解释”#
FunctionCallParser
的职责从类定义就写得很清楚:它处理的是 function / tool call 的解析,而不是 token 级约束本身。
它和 grammar backend 的边界可以先压成一句话:
- grammar backend 负责限制生成空间;
- parser 负责解释已经生成出来的结构。
如果把这两层混在一起,就会把“输出为什么没被限制住”和“输出已经限制住了但解释失败了”看成同一种问题。
tool_choice 怎样进入运行时#
FunctionCallParser.get_structure_constraint(...) 很关键,因为它说明 tool choice 不是“生成以后再决定怎么解释”,而是会先转成结构化约束,再进入采样参数:
if tool_choice == "required" or isinstance(tool_choice, ToolChoice):
json_schema = get_json_schema_constraint(...)
return ("json_schema", json_schema)这说明 tool choice 先改写的是执行期约束,然后 parser 才在输出侧继续解释结果。
这件事在 serving 层里的接法也很直接。OpenAIServingChat tool call constraint setup
会先把 parser 产出的约束编进请求,再由 streaming / non-streaming 路径继续消费。
为什么 parser 不能被省掉#
即使模型已经被约束在“像工具调用”的结构空间里,调用方依然不能直接拿原始输出当成工具调用对象。原因很简单:
- 不同模型输出 function call 的具体格式并不完全一样;
- streaming 场景里,调用信息可能是分段到达的;
- non-streaming 场景里,仍然需要把结果拆成 normal text 和 tool calls。
这正是 parser 存在的必要性:它把“看起来像工具调用的输出”变成真正可消费的调用对象。
streaming 和 non-streaming 为什么都需要 parser#
FunctionCallParser 不是只为非 streaming 服务。它的 streaming 价值在于:每次有新文本增量到来时,系统都可以尝试把其中已经成形的一部分解释成 tool call。
而在 non-streaming 场景里,parser 则更像最后一道解释层:把完整输出一次性拆成:
- normal text
- tool call list
所以 parser 的角色不是“有工具时顺便用一下”,而是 function calling 能否真正被工程系统消费的关键一环。
而在 streaming 路径里,OpenAIServingChat tool call streaming branch
还会继续决定:哪些增量文本已经足够被解释成 tool call,哪些还必须暂存在 parser 状态里等待下一段输出。
调试 function calling 时先看哪里#
如果你看到的现象是:
- 输出已经像函数调用,但 parser 没认出来;
- parser 认出来了,但 tool choice 行为和预期不符;
- streaming 下工具参数被拆得很奇怪;
更稳的顺序通常是:
- 先确认 tool choice 是否已经正确落到结构化约束;
- 再看 parser 是否和当前模型输出格式匹配;
- 最后再看 serving 层在 streaming 或 non-streaming 路径里怎样消费 parser 输出。
小结#
这一节真正要建立的是一个判断:
- tool choice 先改写执行期约束;
- parser 再解释输出结果;
- function calling 只有把“限制”和“解释”两层接起来,才算真正能用。
理解了这点,后面再看 Responses API 和 built-in tools 时,就不会把 parser 误看成一个可有可无的边角模块。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。