快速结论:使用 vLLM 的 Muse tool parser 时,若工具参数声明为 Optional[str](即 ["string", "null"] 或 anyOf 写法),解析器会把原本的字符串值当成 JSON 解码,导致 "10001" 变成数字 10001,客户端拿到的类型与 schema 不符。优先排查工具参数 schema 是否含可空字符串写法,并升级到包含修复的提交。
适用环境:Issue 已确认的环境为 vLLM main(fa3b9d137,从源码树在 CPU 上导入)、torch 2.15.0+cpu、transformers 5.20.2、pydantic 2.14.1、Python 3.12.6;使用 --tool-call-parser muse。GPU/CUDA 环境未在 Issue 中给出。
最快修复方案:升级到包含 #64518 的版本(该 PR 已在 Python 与 Rust 两侧修复),升级后在 Issue 给出的复现脚本中,三种 schema 均输出 "10001"。
注意事项:Issue 作者最初提出的 coerce_to_schema_type 方案并非最终采纳的修复,不要照搬;Rust 侧的验证并非由复现脚本实际执行,仅在 #64518 的 diff 中阅读确认;#64568 尝试修改同一处 Rust 检查,与 main 产生冲突,合入时需注意。
问题场景
在 vLLM 中使用 --tool-call-parser muse 解析 ATEM 格式的工具调用时,若被调用函数的某个参数 schema 声明为可空字符串({"type": ["string", "null"]} 或 pydantic 为 str | None 生成的 anyOf 写法),模型输出的原始文本 10001 会被 JSON 解码,客户端最终收到 {"zip": 10001}。而普通 {"type": "string"} 参数会正确保留为 {"zip": "10001"}。
报错原文
Muse tool parser JSON-decodes Optional[str] parameters, so "10001" becomes 10001
{'type': 'string'} {"zip": "10001"}
{'type': ['string', 'null']} {"zip": 10001}
{'anyOf': [{'type': 'string'}, {'type': 'null'}]} {"zip": 10001}
原因分析
Muse 解析器在 vllm/tool_parsers/muse_tool_parser.py 的 _string_parameters 中判断字符串参数时,只匹配 schema.get("type") == "string",因此漏掉了数组形式的可空类型 ["string", "null"] 以及 pydantic 为 str | None 生成的 anyOf 结构。这类参数未进入“保留原始文本”的路径,而是走了通用 JSON 解码,于是纯数字字符串被转换成了整数。
Issue 作者验证:把类型匹配改为通过 extract_types_from_schema 识别后,三种 schema 都能输出 "10001";恢复原第 336 行的判断后,整数又回来了。最终维护者说明,这与 #64517 同一根因,由 #64539 引入共享的 is_string_schema() 辅助函数统一处理 anyOf/oneOf/数组形式的可空字符串,并在 Muse、MiniCPM5、Poolside V1 解析器中复用,随后 #64518 合入,同时修复 Python 与 Rust 实现。
环境排查
- 确认当前 vLLM 版本或提交号是否包含 #64518;Issue 复现所用为 main fa3b9d137,验证修复所用提交为 1fc9d00。
- 确认 Python 3.12.6、torch 2.15.0+cpu、transformers 5.20.2、pydantic 2.14.1 等依赖与当前环境一致,便于复现。
- 确认启动参数使用
--tool-call-parser muse。 - 检查工具参数 schema 是否为
{"type": ["string", "null"]}、anyOf或oneOf形式的可空字符串。 - 若涉及 Rust 解析器(
rust/src/parser/src/unified/muse.rs),需单独确认该侧是否包含修复。
解决步骤
- 先定位工具定义中声明为
Optional[str](可空字符串)的参数,确认其 schema 形态。 - 将 vLLM 升级到包含 #64518 的版本或提交;该修复在 Muse 解析器中通过统一的字符串 schema 判断,使可空字符串参数保留原始文本。
- 使用 Issue 中的复现脚本分别测试
{"type": "string"}、{"type": ["string", "null"]}、{"anyOf": [{"type": "string"}, {"type": "null"}]}三种 schema,核对输出。 - 如继续保留自定义补丁,不要直接照搬
coerce_to_schema_type的推测方案,应以 #64518 的实际实现为准,否则可能改变["string", "integer"]等类型组合与 null 的处理行为。
验证方法
运行 Issue 中的 repro.py,在含修复的提交(如 1fc9d00)上,三种 schema 应全部输出 {"zip": "10001"},即字符串保持不变;若输出 {"zip": 10001},说明修复未生效或未正确加载。Rust 解析器一侧建议另行用实际调用验证,因为 Issue 中未实际运行。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


