执行摘要
- 一句话:修复 Harmony 严格工具调用语法,兼容真实渲染输出
- 推荐动作:值得精读,尤其是
vllm/parser/harmony.py 中如何通过枚举约束变体与 OrFormat 组合来对齐模型真实渲染,以及测试中如何用 render_conversation 生成真实输出 token 来做回归验证。对工具调用解析相关维护者具有参考价值。
功能与动机
PR body 明确指出这是对 #51020 的重做,目的是修复在维持 BFCL 性能时被发现的严格工具调用语法 bug,并通过放宽约束来提升解析鲁棒性;同时将测试更新为使用真实 Harmony 渲染输出,而非手写样本,以减少测试与实际行为之间的偏差。
实现拆解
- 统一约束写法:在
vllm/parser/harmony.py 中新增常量 _JSON_CONSTRAINS = [" json", " <|constrain|>json"],取代原先硬编码的两种写法,作为后续标签生成的唯一约束来源。
- 重写函数调用模板:将
_FUNCTION_CALL_BEGINS 精简为两条带前导空格的通用模板 " to=functions.{name}{channel}{constrain}<|message|>" 与 "{channel} to=functions.{name}{constrain}<|message|>",随后在 get_harmony_structural_tag 中通过三层循环(工具 × 模板 × 约束 × channel)生成全部 TagFormat,确保解析器同时接受 Harmony 渲染产生的两种格式。
- 放宽 final 通道与结构化输出:
get_harmony_structural_tag 中 tool_choice == "auto" 分支改为对 _JSON_CONSTRAINS + [""] 全部生成标签;_adjust_output_format 中当 final_content 为 JSONSchemaFormat 时,用 OrFormat 包装两种约束写法,使结构化输出也能被两种渲染风格接受。
- 测试改造:
tests/parser/test_harmony.py 中 assistant() 增加 content_type 参数,get_model_output_tokens() 改为接受只包含 response_messages 的调用,并在内部固定使用 Message.from_role_and_content(Role.USER, "x") 作为 prompt,再通过 render_conversation_for_completion 与 render_conversation 生成真实模型输出 token;新增 get_model_output_str() 辅助获取渲染字符串;各测试用例从手写 prompt 迁移到真实渲染输出。
- 验证配套:运行
pytest tests/parser/test_harmony.py 得到 64 passed;BFCL 实测准确率 multi_turn_base 54.50%、multi_turn_long_context 33.50%、multi_turn_miss_func 44.50%、multi_turn_miss_param 45.00%。
关键文件:
vllm/parser/harmony.py(模块 解析器;类别 source;类型 core-logic;符号 get_harmony_structural_tag, _adjust_output_format, _assemble_tag): 核心解析器逻辑修改:统一 JSON 约束写法、放宽函数调用与 final 通道的语法标签,使 Harmony 解析器能够接受 GPT-OSS 真实渲染输出,是本次修复的关键。
tests/parser/test_harmony.py(模块 测试;类别 test;类型 test-coverage;符号 assistant, get_model_output_tokens, get_model_output_str): 测试配套改造:从手写 prompt 迁移到真实渲染输出,新增辅助函数,确保解析器修改被真实场景覆盖,是验证修复有效性的关键。
关键符号:get_harmony_structural_tag, _adjust_output_format, _assemble_tag, assistant, get_model_output_tokens, get_model_output_str
关键源码片段
vllm/parser/harmony.py
核心解析器逻辑修改:统一 JSON 约束写法、放宽函数调用与 final 通道的语法标签,使 Harmony 解析器能够接受 GPT-OSS 真实渲染输出,是本次修复的关键。
# vllm/parser/harmony.py
# 统一两种 JSON 约束写法:Harmony 渲染可能输出 " json",也可能输出 " <|constrain|>json"
_JSON_CONSTRAINS = [" json", " <|constrain|>json"]
# Harmony 的 stop token(<|return|>、<|call|>)在 xgrammar 约束下被表示为 "",
# 因为 xgrammar 不允许在约束中使用 stop token,否则会导致生成错误或无限循环
_END_TAG = ["<|end|>", ""]
_FINAL_BEGIN = "<|channel|>final{constrain}<|message|>"
# 工具调用可以出现在 commentary / analysis / final 三个 channel 中
_TOOL_CALL_CHANNELS = [
"<|channel|>commentary",
"<|channel|>analysis",
"<|channel|>final",
]
# 模板带前导空格,以匹配 Harmony 渲染的 " to=functions..." 风格
_FUNCTION_CALL_BEGINS = [
" to=functions.{name}{channel}{constrain}<|message|>",
"{channel} to=functions.{name}{constrain}<|message|>",
]
@register_vllm_structural_tag("harmony")
def get_harmony_structural_tag(tools, builtin_tools, tool_choice, reasoning):
# Harmony 始终启用 reasoning,忽略该参数
del reasoning
if builtin_tools:
# 内置工具走宽松 fallback:任意文本直到 <|start|> 或 <|end|>
tags = [
TagFormat(
begin="to=",
content=AnyTextFormat(excludes=["<|start|>"]),
end=_END_TAG,
)
]
else:
# 普通函数:对每个工具、每种 channel、每种约束写法都生成一个 TagFormat,
# 让解析器同时接受两种渲染风格,修复之前严格语法拒绝真实输出的问题
tags = [
TagFormat(
begin=pattern.format(
name=tool.function.name,
channel=channel,
constrain=constrain,
),
content=JSONSchemaFormat(
json_schema=get_function_parameters(tool.function)
),
end=_END_TAG,
)
for tool in tools
for pattern in _FUNCTION_CALL_BEGINS
for constrain in _JSON_CONSTRAINS
for channel in _TOOL_CALL_CHANNELS
]
if tool_choice == "auto":
# auto 模式允许 final 通道在两种约束或完全不带约束之间选择
tags.extend(
TagFormat(
begin=_FINAL_BEGIN.format(constrain=constrain),
content=_ANY_CONTENT,
end=_END_TAG,
)
for constrain in _JSON_CONSTRAINS + [""]
)
return _assemble_tag(
allow_analysis=True,
allow_commentary=True,
content=OrFormat(elements=tags),
)
评论区精华
本次 PR 的 review 讨论很少:唯一一条有效评论是维护者 bbrowning 的 Looks good - thanks!,并直接批准合并;claude[bot] 提示由于 PR 来自 fork,自动审查被禁用,可由维护者手动执行 @claude review。没有遗留的代码评审意见或争议点。
- 整体审查与审批 (other): PR 被批准并合并,无遗留问题。
风险与影响
- 风险:
- 解析约束放宽:
vllm/parser/harmony.py 中函数调用与 final 通道的语法从固定单一写法扩展为两种约束写法,理论上可能让部分不符合预期的渲染被解析为合法工具调用,存在误解析风险;但 xgrammar 仍会对 JSON 内容本身做结构校验,风险可控。
- 测试覆盖局限:测试仅覆盖
tests/parser/test_harmony.py 的单元场景,未包含端到端工具调用集成测试,放宽后的语法对完整推理链路的影响尚未被充分验证。
- 模型范围风险:改动基于 GPT-OSS 的 Harmony 渲染验证,其他使用 Harmony 格式的模型是否同样受益或出现回归,需要额外评估。
- 影响:影响范围集中在使用 Harmony 渲染格式的模型(如 GPT-OSS)的工具调用解析路径。修复后严格工具调用语法不再拒绝真实渲染输出,可提升 BFCL 等 benchmark 的准确率,并避免因解析失败导致的会话中断;测试基础设施的改进(真实渲染驱动测试)也为后续模型解析器提供更可靠的回归保障。
- 风险标记:解析约束放宽, 缺少端到端集成测试, 仅 GPT-OSS 验证
关联脉络
- PR #51020 旧版严格工具调用语法修复(本 PR 为其重做): PR body 明确说明这是对 #51020 的重做,修复其遗留的约束过严问题。
- PR #48922 Guard tool call argument JSON parsing in chat message postprocessing: 同属工具调用解析链路,体现 vLLM 对模型返回工具调用鲁棒性的持续演进方向。
参与讨论