[BUG] Task output schema in the prompt marks Optional fields as required and strips null, so the model cannot express “not applicable”

这个报错通常出现在 CrewAI 使用 output_pydantic 定义任务输出、并在 prompt 内嵌 JSON Schema 时:prompt 里的 schema 把 Pydantic 模型中的 Optional 字段强行标成 required 且去掉 null ,导致模型无法表达“不适用

快速结论:这个报错通常出现在 CrewAI 使用 output_pydantic 定义任务输出、并在 prompt 内嵌 JSON Schema 时:prompt 里的 schema 把 Pydantic 模型中的 Optional 字段强行标成 required 且去掉 null,导致模型无法表达“不适用”,只能编造非空值。优先排查 prompt 内 schema 与 provider 侧响应 schema 是否针对 optional / nullable 字段给出了互相矛盾的约束。

适用环境:Issue 中已确认的环境为 CrewAI(复现基于 crewai==1.15.10,后续评论基于 crewai==1.15.16 轮子验证),使用 Pydantic 模型 + Task(output_pydantic=...);报告者所在路径为 LiteLLM(经 InternalInstructor 构建响应 schema)。Issue 未提供操作系统、Python、CUDA、显卡信息,故不补写。

最快修复方案:暂无确认的一步修复方案。Issue 中提出的候选修复是给 generate_model_description 传入已存在的 strip_null_types=False(代码树中 utilities/agent_utils.py 对 tool schema 已有先例),但这是提案,尚未在 Issue 中确认为已合并或已发布的修复。

注意事项:即使启用 strip_null_types=Falseensure_all_properties_required 仍会让 prompt 里的 required 比 Pydantic 原生 schema 更宽,“必填但可为 null”是预期编码,不要用 required 结构相等来判断是否修好。此外评论指出该缺陷至少存在于三种配置(LiteLLM 路径、原生 OpenAI 路径、agent 带 tools 时 provider 侧模型被丢弃的单契约路径),仅在 LiteLLM 路径上两个 schema 才真正互相矛盾;评论区还提到 force_additional_properties_false 会把 dict[str, str] 变成只允许 {} 的对象,属于同一家族但 nullability 修复无法覆盖。

问题场景

在使用 CrewAI 构建任务时,通过 Task(..., output_pydantic=Plan) 指定 Pydantic 输出模型,并由 build_task_prompt_with_schematask.output_pydantic 的 JSON Schema 嵌入 prompt。当输出模型里含有 Optional[str] = Field(default=None) 这类可选字段时,模型收到的 prompt schema 与 provider 侧(例如 LiteLLM / instructor)按同一 Pydantic 模型生成的响应 schema 不一致,导致模型被逼着为每个可选字段填非空值。报告者的实际影响是:规划器把不相关的参数拼接进同一个必填字符串字段、丢掉了本该输出的步骤,个别运行甚至把 JSON 字符串一路写到输出 token 上限,并把困惑直接写进 payload。

报错原文

[BUG] Task output schema in the prompt marks Optional fields as required and strips null, so the model cannot express "not applicable"

pydantic  required: ['name']
pydantic  note    : {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, ...}

in-prompt required: ['name', 'note']
in-prompt note    : {"default": null, "type": "string", ...}

"value": "... Wait, the schema requires step_description, element_description, value, k"

原因分析

最可能的原因是 prompt 内嵌 schema 的生成链路对 Pydantic schema 做了面向 OpenAI strict function-calling 的“清洗”,但该调用点并不是 function-calling schema,而是 prompt 中的一段文字,strict 约束本不适用。具体地:generate_model_description 默认 strip_null_types=True,会把每个 anyOf 中的 null 去掉;同时 ensure_all_properties_required 会把所有属性加入 required。两者叠加后,Optional[str] = None 字段在 prompt 里被描述成“必填、非空字符串”,而字段自身的 "type": "string" 旁边又标注 "default": null,内部自相矛盾。

评论补充指出:在两个契约同时存在的 LiteLLM 路径上,instructor 按模型直接生成的 schema 仍允许 null 且不把 note 放进 required,于是同一请求里出现两种相反的约束;而在原生 OpenAI 路径上,provider 侧 schema 也由同一默认值生成并带 strict: True,等于强制了“必填非空字符串”,逃生通道被关闭。失败之所以静默,是因为两个契约的交集恰好是“非空字符串”,而编造出来的非空字符串本身就是合法的 Optional[str],校验无从拦截。

环境排查

  • CrewAI 版本:Issue 复现基于 crewai==1.15.10,评论基于 crewai==1.15.16 验证缺陷仍存在;报告者指出 crewai==1.8.1 时 prompt 中仍为 anyOf: [{"type": "string"}, {"type": "null"}],具体回归版本未二分定位。
  • LLM 提供方路径:确认是 LiteLLM(经 InternalInstructor)还是原生 OpenAI(Responses / chat completions)、Gemini 等,不同路径下 provider 侧 schema 行为不同。
  • agent 是否配置了 tools:评论指出 effective_response_model = None if self.original_tools else self.response_model,带 tools 时 provider 侧模型会被丢弃,只剩被清洗过的 prompt schema 这一个契约。
  • Pydantic 模型定义:确认可选字段是否使用 Optional[...] = NoneX | None = None,以便对比 model_json_schema() 与 prompt 内 schema 的 requiredanyOf
  • Issue 未提供操作系统、Python 版本、CUDA、显卡等信息,这些项目无法作为排查依据。

解决步骤

  1. 先用 Issue 提供的复现脚本确认现象:定义含 Optional[str] 字段的嵌套模型,分别打印 Plan.model_json_schema()generate_model_description(Plan)["json_schema"]["schema"],对比 Steprequirednote 属性。pip install crewai==1.15.10 为本 Issue 复现所用版本。
  2. 若需要看到 prompt 实际下发内容,调用 build_task_prompt_with_schema(task, "") 检查嵌入的 schema。
  3. 确认自身所处的 LLM 路径与是否带 tools,判断是“两个契约矛盾”还是“只剩一个被清洗后的契约”。
  4. 可优先尝试的候选修复:在 prompt 内嵌 schema 的生成调用点传入已有参数 strip_null_types=False,即 schema_dict = generate_model_description(task.output_pydantic, strip_null_types=False)。该写法在同代码树的 utilities/agent_utils.py 对 tool schema 已有先例,但 Issue 未确认它已被官方采纳发布,属于提案,需自测。
  5. 注意不要同时改动 ensure_all_properties_required:评论指出它与 _common_strict_pipeline 共用,影响 OpenAI / Anthropic / Bedrock 的 strict sanitizer,改动面更大;“必填 + 可为 null”是 strict 模式下的标准写法,保留 required 更宽是预期行为。
  6. 若受 force_additional_properties_false 影响,需额外检查 dict[str, str] 等字段:它会被改写成只接受 {} 的对象,这属于同一家族的另一问题,不在 nullability 修复范围内。

验证方法

用同一复现脚本重新对比:note 在 prompt 内 schema 中应恢复为 anyOf: [{"type": "string"}, {"type": "null"}]。同时不能只看 required 是否与 Pydantic 原生一致——评论明确给出 prompt, post-#6775 状态下 required 仍为 ['name','note'],因此验证应着眼于“该字段是否允许为 null、note 键是否必须出现”这一可接受值集合,而不是 required 结构相等。行为层面,观察原先被迫编造非空值的可选字段是否可以不填或填 null,以及输出是否不再出现把无关参数拼接、把 JSON 值写到 token 上限的情况。

参考来源

crewAIInc/crewAI #6774

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 22694

发表回复

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