Skip to content

Fix malformed json_schema response_format envelope (#229)#252

Closed
vincenzolegrottaglie wants to merge 1 commit into
WordPress:trunkfrom
vincenzolegrottaglie:fix/openai-compatible-json-schema-envelope
Closed

Fix malformed json_schema response_format envelope (#229)#252
vincenzolegrottaglie wants to merge 1 commit into
WordPress:trunkfrom
vincenzolegrottaglie:fix/openai-compatible-json-schema-envelope

Conversation

@vincenzolegrottaglie

Copy link
Copy Markdown

Fixes #229.

Problem

AbstractOpenAiCompatibleTextGenerationModel::prepareResponseFormatParam() emitted the bare output schema directly under json_schema:

['type' => 'json_schema', 'json_schema' => $outputSchema]

The OpenAI Structured Outputs spec requires json_schema to be an object carrying the schema under a schema key plus a required name. As a result, any provider extending this base class fails on structured-output requests (asJsonResponse($schema)) against a compliant endpoint with:

400 missing_required_parameter: response_format.json_schema.name

Plain text generation is unaffected (the broken path is gated behind the application/json output MIME type). Permissive self-hosted servers may silently accept the malformed payload, which is why this only surfaces on strict endpoints (OpenAI, OpenRouter→Azure, etc.).

Fix

Wrap the schema in the spec-compliant envelope:

['type' => 'json_schema', 'json_schema' => ['name' => 'response', 'schema' => $outputSchema]]

strict is intentionally left unset. Enabling it imposes extra schema constraints (every object needs additionalProperties: false, all properties listed in required) that arbitrary caller schemas rarely satisfy — that would trade one 400 for another. Per the spec, only name is required.

Verification

  • Unit: updates the two existing assertions and adds a regression test (testPrepareResponseFormatParamSchemaEnvelopeHasName) asserting the envelope shape — name present, schema wrapped under schema, strict absent, and the raw schema no longer emitted directly under json_schema.
  • Live, end-to-end against the OpenAI Chat Completions API: the previous payload returns the exact 400 missing_required_parameter: response_format.json_schema.name; the payload produced by the patched method returns 200 with valid JSON content.
  • composer lint clean (phpcs PSR-12 + phpstan max); composer test:unit green.

AbstractOpenAiCompatibleTextGenerationModel::prepareResponseFormatParam()
emitted the bare output schema directly under `json_schema`. The OpenAI
Structured Outputs spec requires `json_schema` to be an object carrying the
schema under a `schema` key plus a required `name`, so compliant endpoints
rejected structured-output requests with:

    400 missing_required_parameter: response_format.json_schema.name

Wrap the schema as `{ name: 'response', schema: <schema> }`. `strict` is left
unset on purpose: enabling it would impose extra schema constraints (every
object needs `additionalProperties: false`, all properties in `required`) that
arbitrary caller schemas rarely satisfy, which would trade one 400 for another.
Per the spec only `name` is required.

Verified live against the OpenAI Chat Completions API: the previous payload
returns 400 with the exact error above, the fixed payload returns 200.

Updates the two existing assertions and adds a regression test asserting the
envelope shape (name present, schema wrapped, strict absent, raw schema no
longer emitted directly under json_schema).
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: legrottaglie <vincenzolegro@git.wordpress.org>
Co-authored-by: benridane <presents111@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecov Bot commented Jun 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.13%. Comparing base (99a65e8) to head (cd48bdc).

Additional details and impacted files
@@            Coverage Diff            @@
##              trunk     #252   +/-   ##
=========================================
  Coverage     88.12%   88.13%           
  Complexity     1213     1213           
=========================================
  Files            60       60           
  Lines          3934     3937    +3     
=========================================
+ Hits           3467     3470    +3     
  Misses          467      467           
Flag Coverage Δ
unit 88.13% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vincenzolegrottaglie

Copy link
Copy Markdown
Author

Closing as a duplicate of #251, which predates this PR and already covers the same fix (and additionally passes through pre-wrapped json_schema envelopes). Deferring to that one. Apologies for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAI-compatible providers fail with 400 on structured output (missing json_schema envelope)

2 participants