@tanstack/openai-base: strict structured output skips $defs/definitions, so recursive zod schemas are rejected
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
Issue này chưa được đánh giá.
Mô tả
TanStack AI version
@tanstack/[email protected] (same code on main; we hit it on 1.0.0 with @tanstack/[email protected])
Framework/Library version
Node 24, zod 4.6.7
Describe the bug and the steps to reproduce it
coerceStrictSchema (behind makeStructuredOutputCompatible / makeStructuredOutputCompatibleWithMap) applies the strict Structured Outputs rules to properties, items and anyOf. It never goes into $defs or definitions. Every object schema in a definition pool reaches the API unchanged: optional properties are left out of required and are not widened to null.
zod 4 puts every recursive schema into a definition pool and points to it with $ref. A recursive outputSchema is common in practice, for example a filter tree where and/or hold more filters. With such a schema, strict response_format is built from a half-converted schema. OpenAI and Azure OpenAI reject it, because strict mode requires required to list every key in properties.
This differs from #1213. That issue covers tools, which fall back to strict: false when they meet $ref/$defs. Structured output has no such fallback. The request is sent strict and fails.
Repro (no API key needed; it shows the schema that goes on the wire):
import { makeStructuredOutputCompatible } from "@tanstack/openai-base";
import { z } from "zod";
const node = z.object({
label: z.string(),
note: z.string().optional(),
get children() { return z.array(node); },
});
const json = z.toJSONSchema(z.object({ root: node }), { target: "draft-7" });
console.log(JSON.stringify(makeStructuredOutputCompatible(json, json.required), null, 2));
Output (trimmed):
{
"type": "object",
"properties": { "root": { "$ref": "#/definitions/__schema0" } },
"required": ["root"],
"additionalProperties": false,
"definitions": {
"__schema0": {
"type": "object",
"properties": {
"label": { "type": "string" },
"note": { "type": "string" },
"children": { "type": "array", "items": { "$ref": "#/definitions/__schema0" } }
},
"required": ["label", "children"],
"additionalProperties": false
}
}
}
The top level is converted. Inside __schema0, note is still optional and not nullable.
Expected: each schema in $defs/definitions goes through the same strict conversion. In this example that means "note": { "type": ["string", "null"] } and "required": ["label", "note", "children"].
Fix we run as a pnpm patch (on the built dist/esm/utils/schema-converter.js, at the end of coerceStrictSchema, just before the return):
for (const defsKey of ["$defs", "definitions"]) {
if (!isSchemaObject(result[defsKey])) continue;
result[defsKey] = Object.fromEntries(Object.entries(result[defsKey]).map(([name, def]) => {
if (!isSchemaObject(def)) return [name, def];
const nested = coerceStrictSchema(def, def.required || []);
hasUntrackableAnyOfWidening ||= nested.nullWideningMap !== undefined || nested.hasUntrackableAnyOfWidening;
return [name, nested.schema];
}));
}
Open point: the null-widening map is keyed by position in the tree, so it cannot follow a $ref. With the patch, a field the conversion widened to null inside a definition comes back as null, not as an omitted key. A schema that uses .optional() there then fails validation on null. We work around this with .nullish() inside recursive schemas. A full fix would carry a map per definition and resolve it at each $ref.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
The snippet above runs as-is with @tanstack/openai-base and zod installed.
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
- Ngôn ngữ chính
- TypeScript
- Star
- 3.3k
- Fork
- 378
- Merge trung bình
- 1 ngày 3 giờ
- Pull request đã merge (30 ngày)
- 253
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Có thể đã có người làm @tombeckenham đã nhận 9 ngày trước. Đang mởhas-pr waiting-on: maintainer
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
TanStack/ai#1916 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
feat(ai-anthropic): support native on-demand compactionCó thể đã có người làm @tombeckenham đã nhận hôm nay. Đang mởwaiting-on: maintainer
TanStack/ai#2081 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Approval resume validates null-widened history args, so approved tools fail with repairToolInput: falseCó thể đã có người làm @tombeckenham đã nhận hôm nay. Đang mởwaiting-on: maintainer
TanStack/ai#2073 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-sandbox: encrypt snapshot blobs at rest with an encrypting blob store wrapperCó thể đã có người làm @AlemTuzlak đã nhận 1 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#2062 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Sandbox takeover: the saved transcript loses everything the first host delivered before the restartCó thể đã có người làm @tombeckenham đã nhận 1 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#2061 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
binding:python bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
coollabsio/orbit#72 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug needs-triage potential-regression
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
aws/aws-sdk-js-v3#8408 ·
Maintainer thường phản hồi trong vòng 1 ngày