fix(providers): preserve Responses schema strict false#10139
fix(providers): preserve Responses schema strict false#10139VectorPeak wants to merge 3 commits into
Conversation
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #10139 +/- ##
==========================================
- Coverage 80.73% 80.72% -0.01%
==========================================
Files 929 929
Lines 76701 76703 +2
Branches 25154 25156 +2
==========================================
- Hits 61923 61919 -4
- Misses 14778 14784 +6
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
👍 All Clear
Reviewed changes to Azure/OpenAI response formatting that now honor an explicitly provided strict: false in json_schema configs, along with corresponding tests. The updates only affect request construction and do not introduce new tools, execution sinks, or untrusted input paths. No LLM security vulnerabilities were identified in this PR.
Minimum severity threshold: 🟡 Medium | To re-scan after changes, comment @promptfoo-scanner
Learn more
mldangelo-oai
left a comment
There was a problem hiding this comment.
Release audit on 6d7ebb4: ran the focused Responses/schema suites (50 tests) and reviewed request construction. Explicit strict false is preserved while unset/true behavior remains intact; no release-blocking findings.
Add two tests to the OpenAI Responses provider suite pinning the strict resolution rungs left unexercised by promptfoo#10139: - default to true when no strict key is present anywhere, locking in the backward-compat behavior the PR promises - honor an explicit strict: false supplied via the top-level response_format shape, covering the middle responseFormat.strict rung Mutation testing confirms the gap: before this change, both flipping the `?? true` default to false and deleting it outright survived all 1197 tests in test/providers/openai/. Each mutant is now killed by exactly one of the new tests. Test-only; no production code changed.
mldangelo
left a comment
There was a problem hiding this comment.
Thanks for this — tightly scoped fix, and it's correct. Verified it's safe to land.
Heads-up: I pushed a commit to your branch
So you're not surprised by a commit you didn't write: I added two tests to test/providers/openai/responses/responseFormat.test.ts. Test-only, no production code touched. The reason was that mutation testing showed the OpenAI half of your change wasn't pinned by anything:
- Delete
?? truefromsrc/providers/openai/responses.ts:942→Test Files 34 passed (34) / Tests 1197 passed (1197). Survived. - Change it to
?? false→ same, 1197 passed. Survived. - The identical
?? falsemutation on the Azure line (src/providers/azure/responses.ts:187) →Tests 1 failed | 534 passed, failing attest/providers/azure/responses.test.ts:157:Killed.- "strict": true, + "strict": false,
Your Azure test pinned the default; nothing pinned it on OpenAI. Separately, deleting the middle rung ?? responseFormat.strict from both files left all 1732 tests green — that shape was unexercised everywhere. The pushed tests cover both rungs, and each one fails against its corresponding mutant.
What's good
The three-rung resolution json_schema.strict ?? strict ?? true is the right shape, and it matches what src/providers/azure/foundry-agent.ts:396 already does (from #8059) — so this aligns the Responses providers with an existing precedent instead of inventing a fourth convention. Backward compat genuinely holds: a config with no strict key anywhere still emits text.format.strict: true. And you caught both clones, which is the easy thing to miss here.
Remaining items
1. xai:responses still hardcodes strict: true — P2
src/providers/xai/responses.ts:124, inside buildTextFormat, is the same block reading the same config shape:
const schema = responseFormat.schema || responseFormat.json_schema?.schema;
const schemaName = responseFormat.json_schema?.name || responseFormat.name || 'response_schema';
return { format: { type: 'json_schema', name: schemaName, schema, strict: true } };After this PR, strict: false is honored on openai:responses and azure:responses and silently ignored on xai:responses — same docs page, same config shape, divergent behavior. Was leaving xAI out deliberate (e.g. their API rejects non-strict json_schema)? If so, a note in site/docs/providers/xai.md would save someone a debugging session. If not, it's the same one-liner.
2. Docs — P3
site/docs/providers/openai.md:1529-1547 shows the flattened Responses response_format with no strict key at all, while the Chat Completions example directly above (line 1524) does show "strict": true. Nothing in site/docs states the Responses default or that it's now configurable.
3. Optional: one helper instead of four copies
buildTextFormat at src/providers/xai/responses.ts:107 is already the extracted form of the block you edited. Lifting it into src/providers/responses/ (all three files already import from there) drops ~50 duplicated lines and fixes xAI for free. Totally fine as a follow-up.
Nit if you're in there anyway: the two tests you added are near-verbatim copies of the tests immediately above them — it.each([{ strict: true }, { strict: false }]) would avoid a 7th copy of the mockApiResponse blob.
Nothing blocking. Thanks again.
The xAI Responses provider hardcoded `strict: true` when building the json_schema text format, so an explicit `strict: false` was silently upgraded — the same defect this PR fixes for OpenAI, in a sibling provider that shares the response-format shape. Resolve strict the same way: `json_schema.strict ?? strict ?? true`, preserving the true-by-default behavior when neither key is present. Adds coverage for both rungs on the xAI path, and documents the resolution order in the OpenAI provider docs.
Preserve explicit
response_format.json_schema.strict: falsewhen building OpenAI and Azure Responses API request bodies.What Problem This Solves
OpenAI Responses and Azure Responses both accepted
response_formatobjects that includejson_schema.strict, but their request builders always emittedtext.format.strict: truefor JSON Schema formats.That meant an explicit user configuration like
strict: falsewas indistinguishable from an omitted value by the time promptfoo sent the request. This matters because provider APIs treat strict mode as a real schema-enforcement switch, and strict mode only supports a subset of JSON Schema.Change
The Responses request builders now preserve the configured strict value using the same fallback shape already used by the Azure Foundry Agent provider:
The default remains
truewhen no strict value is provided. The change only affects OpenAI Responses and Azure Responses JSON Schema request body construction.Evidence
Before this change, both request builders hardcoded
strict: true, sostrict: falsecould not reachtext.format.Added focused regression coverage for both provider paths:
body.text.format.strict === falsebody.text.format.strict === falsePossible call chain / impact
Sibling surface checked: Azure Foundry Agent already preserves
strict: false; this PR aligns the Responses providers with that behavior without changingjson_object, plain text, tools, or schema file loading.Validation
npm cinpx @biomejs/biome check --write src/providers/openai/responses.ts src/providers/azure/responses.ts test/providers/openai/responses/responseFormat.test.ts test/providers/azure/responses.test.tsnpx vitest run test/providers/openai/responses/responseFormat.test.ts test/providers/azure/responses.test.ts— 2 files, 50 tests passednpx vitest run test/providers/openai/responses test/providers/azure/responses.test.ts— 15 files, 237 tests passedgit diff --checknpm run tsc