get_json_schema drops `items` / `enum` when a Union contains `list[…]`, `dict[…]` or `Literal[…]`

当你在 Transformers 里用 get_json_schema 把带类型注解的 Python 函数转成工具 schema、且某个参数的类型注解是 Union( | )并包含 list[...] 、 dict[...] 、 tuple[...] 或 Literal[...] 时,生成的 JSO

快速结论:当你在 Transformers 里用 get_json_schema 把带类型注解的 Python 函数转成工具 schema、且某个参数的类型注解是 Union(|)并包含 list[...]、dict[...]、tuple[...] 或 Literal[...] 时,生成的 JSON Schema 会丢掉 items、enum、additionalProperties、prefixItems 等关键字段,只剩一个不完整甚至非法的 type 数组。优先排查 Union 分支对子类型的合并逻辑。

适用环境:Issue 已确认:transformers 5.18.0.dev0(main @ 27166ea),也在 5.17.0 上复现;平台 macOS-15.3.1-arm64;Python 3.12.13;huggingface_hub 1.33.0;safetensors 0.8.0;accelerate 1.15.0;PyTorch 2.14.0(无加速器);未使用分布式或并行。

最快修复方案:暂无确认的一步修复方案。Issue 中提到的修复是修改 src/transformers/utils/chat_template_utils.py 的 _parse_type_hint Union 分支:把「每个子类型都有字符串 "type"」的合并条件收紧为「子类型恰好只有 {"type": ...}」,并补上去重,让带额外键的子类型落到已有的 anyOf 分支;但该 PR 是否已合并、对应正式版本,Issue 中未给出结论,不要直接照搬。

注意事项:该修复在本地测试通过(tests/utils/test_chat_template_utils.py、tests/utils/test_chat_parsing.py),但属提交者自述,未在本 Issue 元数据中标注为已合并。此外,response_parser._schema_types 已会递归处理 anyOf,因此改为 anyOf 输出不会影响仓库内的消费方;但如果你有下游代码在解析这个扁平化后的 type 数组,改动后会看到结构变化,需要自行适配。

问题场景

用户在 Transformers 中调用 transformers.utils.get_json_schema,把一个带类型注解和 docstring 的 Python 函数转换成工具调用的 JSON Schema(例如用于 chat templates / tool schemas 场景)。当某个参数的类型注解是 Union,且 Union 成员中含有 list[str]、dict[str, int]、Literal["a", "b"] 这类带附加约束的类型时,生成的 schema 会丢失这些附加信息。

问题并非在所有 Union 场景下出现:同样的注解在「Union 中只有一个非 None 成员」(如 list[str] | None)或与 Any 组合时都能正确保留,说明这是 Union 合并分支的行为不一致,而不是注解本身不被支持。

报错原文

get_json_schema drops `items` / `enum` when a Union contains `list[...]`, `dict[...]` or `Literal[...]`

Output on `main`:
{
  "query": {"type": ["array", "string"], "description": "A single query or a list of queries"},
  "unit": {"type": ["integer", "string"], "description": "Either a named unit or a raw integer code"}
}

- `list[str] | list[int]` -> `{"type": ["array", "array"]}` (no `items`, and duplicate entries in a `type` array are not valid JSON Schema)
- `Literal["a", "b"] | Literal["c"]` -> `{"type": ["string", "string"]}` (both enums lost)
- `dict[str, int] | list[int]` -> `{"type": ["array", "object"]}`

原因分析

最可能的原因在 src/transformers/utils/chat_template_utils.py 的 _parse_type_hint 函数。其 Union 分支中有一个「基础类型 Union」的快捷合并逻辑,判断条件是每个子类型都存在字符串形式的 "type":

elif all("type" in subtype and isinstance(subtype["type"], str) for subtype in subtypes):
    return_dict = {"type": sorted([subtype["type"] for subtype in subtypes])}

问题在于 list / dict / tuple / Literal 生成的 schema 同样带有字符串 "type"(分别是 "array"、"object"、"string"),因此它们也会命中这个分支。该分支只提取 "type" 并排序,items、additionalProperties、prefixItems、enum 等其他键全部被丢弃;同时它也不做去重,所以会出现 ["array", "array"] 这种在 JSON Schema 中不合法的重复 type 数组。

社区在评论中复现了该问题,并确认根因就是这一行:捷径只检查每个子类型是否有字符串 "type",导致 list[...] 和 Literal 被塌缩成类型字符串。修复方向是仅在子类型恰好为 {"type": ...} 时才做合并,并去重,其余情况交给已有的 anyOf 分支处理。

环境排查

  • 确认 transformers 版本:Issue 在 5.18.0.dev0(main @ 27166ea)复现,也在 5.17.0 复现;不要假设其他版本行为一致。
  • 确认 Python 版本:Issue 为 3.12.13;Union 语法 | 需要 Python 3.10+。
  • 确认 huggingface_hub 1.33.0、safetensors 0.8.0、accelerate 1.15.0,但 Issue 未显示这些包与 schema 生成逻辑直接相关,仅作环境对照。
  • 确认 PyTorch 版本与是否使用加速器:Issue 为 PyTorch 2.14.0、无加速器;该问题属纯 Python 类型解析,是否使用 CUDA/GPU 预计不影响结果。
  • 确认是否使用分布式或并行:Issue 为否。
  • 确认 chat_template_utils.py 中 _parse_type_hint 的 Union 分支源码是否仍为 "type" in subtype 形式(而非 subtype.keys() == {"type"}),以判断修复是否已进你的版本。

解决步骤

  1. 先用 Issue 中的最小复现脚本确认症状,观察输出里 query 是否缺少 items、unit 是否缺少 enum。
  2. 定位 src/transformers/utils/chat_template_utils.py 的 _parse_type_hint Union 分支,检查那一行条件是否为 all("type" in subtype and isinstance(subtype["type"], str) for subtype in subtypes)。
  3. 可优先尝试的修复方向(Issue 中提出、本地测试通过,但未确认已合并):把合并条件改为仅在子类型恰好只有 "type" 键时才走扁平化,即 subtype.keys() == {"type"},并对合并结果去重;这样 list[...]、dict[...]、tuple[...]、Literal[...] 会落到已有的 anyOf 分支,保留 items / enum 等键。
  4. 如上一步不可行,先不要修改或硬编码输出:在等待上游修复期间,避免在工具函数参数注解中对含附加约束的类型使用 Union,或改为不含 Union 的写法以绕过该分支。
  5. 如果你要自行验证修复,需按 Issue 提到的方式补充回归测试(tests/utils/test_chat_template_utils.py、tests/utils/test_chat_parsing.py);这些测试是否为最终合入版本,Issue 未说明。

验证方法

重新运行 Issue 中的复现脚本,检查 get_json_schema(search)["function"]["parameters"]["properties"] 的输出。修复后 query 应为 anyOf 结构并保留 {"type": "array", "items": {"type": "string"}},unit 应保留 {"type": "string", "enum": ["celsius", "fahrenheit"]}。可再补测 list[str] | list[int](不应出现重复的 "array")、Literal["a", "b"] | Literal["c"](不应丢失 enum)、dict[str, int] | list[int](应保留 additionalProperties 等键)等组合。若生成的 schema 需被下游消费,再用实际的 chat template / 工具调用流程跑一遍,确认解析端能正确处理 anyOf。

参考来源

huggingface/transformers #49122

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 26116

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注