[integrations][openai] Apply OpenAI native structured output - #919
[integrations][openai] Apply OpenAI native structured output#919weiqingy wants to merge 1 commit into
Conversation
Override the schema-carrying chat on the OpenAI completions connection to request the provider's native structured output when the caller supplies a schema the provider can honor natively. Native applies only when the schema is a POJO class (Java) or a BaseModel subclass (Python) and the effective model supports it. Capability is a connection-side predicate over the effective model, since the OpenAI json schema mode is model dependent: per the provider's structured output guide it is supported on gpt-4o-mini and the gpt-4o snapshots from 2024-08-06 onward, while gpt-4-turbo and gpt-3.5-turbo have json mode only. The predicate matches gpt-4o-mini by prefix (the whole family postdates the cutoff) and the capable gpt-4o snapshots exactly, so a pre-cutoff snapshot such as gpt-4o-2024-05-13 correctly falls back to the prompt path rather than failing at the provider. A RowTypeInfo schema stays on the prompt path. No path passes a schema on the chat call yet, so behavior is unchanged; native output is exercised by direct callers and the unit tests.
da21d08 to
265bc35
Compare
|
Hi @wenjin272, could you please help review this PR when you get a chance? It applies OpenAI's native structured output on top of #843, and it's now rebased onto main since that merged. Thanks! |
|
One thing worth calling out explicitly, since it is deliberately left out of this PR.
I left it unfixed here on purpose. The connection never receives the strategy, so it cannot tell an explicit Nothing dispatches on the strategy today, so this is dormant on Happy to pull the fix into this PR instead if you would rather it not wait for #912. It would change the two fallback tests, which currently assert the silent degrade. |
| return false; | ||
| } | ||
| return effectiveModel.startsWith(NATIVE_STRUCTURED_OUTPUT_FAMILY_PREFIX) | ||
| || NATIVE_STRUCTURED_OUTPUT_MODELS.contains(effectiveModel); |
There was a problem hiding this comment.
The capability predicate appears to be inaccurate in both directions.
Using startsWith("gpt-4o-mini") also classifies models such as gpt-4o-mini-audio-preview and gpt-4o-mini-realtime-preview as capable, although these variants do not support Structured Outputs. Conversely, models such as gpt-4.1, gpt-5, o3, and o4-mini do support Structured Outputs but currently return false.
Once #912 uses this predicate for AUTO, the false positives may produce provider errors, while the false negatives will unnecessarily fall back to prompting. Could we use stricter alias/snapshot matching, include the currently supported model families, and add tests covering both cases?
References:
| // Native structured output applies only for a POJO Class schema on a model the provider | ||
| // documents as capable; a RowTypeInfo (wrapped in OutputSchema) or an incapable model keeps | ||
| // the prompt-engineering fallback. | ||
| if (outputSchema instanceof Class && supportsNativeStructuredOutput(modelName)) { |
There was a problem hiding this comment.
I agree that the final fix belongs in #912, where AUTO can be distinguished from explicit NATIVE. Could we add a clear TODO referencing #912 at this capability check, noting that NATIVE must bypass the connection-side fallback or fail explicitly? This should help prevent the dormant issue from being missed when the strategy wiring is implemented.
Linked issue: #280
Purpose of change
Applies OpenAI's native structured output on top of the structured-output foundation from #843, which has now merged. This PR is rebased onto main and is a single commit.
The OpenAI completions connection overrides the schema-carrying
chatand requestsresponse_formatjson_schema strict when native applies, and declares the capability predicate over the effective model.Native applies only when the schema is a POJO class (Java) or a
BaseModelsubclass (Python) and the effective model supports it. Per OpenAI's structured-output guide, json_schema is supported ongpt-4o-miniand thegpt-4osnapshots from2024-08-06onward, whilegpt-4-turboandgpt-3.5-turbohave JSON mode only. Because "and later" is a snapshot cutoff rather than a name prefix, the predicate matchesgpt-4o-miniby prefix (its whole family post-dates the cutoff) and the capablegpt-4osnapshots exactly, so a pre-cutoff snapshot such asgpt-4o-2024-05-13falls back to the prompt path instead of failing at the provider. ARowTypeInfoschema stays on the prompt path.#843 added
_reject_unsupported_output_schemaso that a connection with no native translation turns a schema it could only drop into an error. OpenAI has that translation now, so it no longer calls the guard, and the connection walk intest_output_schema_param_declared.pyexempts connections that translate a schema natively. The exemption keys off the override ofsupports_native_structured_output, which is how a connection reports that capability in the first place. The other seven connections stay held to the rejection contract.No path passes a schema on the chat call yet, so behavior is unchanged; native output is exercised by direct callers and the unit tests.
Tests
Java: the request carries
response_formatjson_schema strict when a capable model and a POJO schema are supplied; the capability predicate is model-dependent (an incapable and a pre-cutoff same-family snapshot both resolve to not-capable); null schema and non-POJO schema keep the prompt path.Python: the same coverage over the
BaseModelpath, including the pre-cutoff snapshot regression test and policy behavior.The connection walk still fails if a connection without a native translation stops rejecting a schema, and its existing non-empty assertion now runs on the filtered list, so an exemption that ever swallowed the whole walk would fail rather than pass silently.
Java OpenAI module tests and the Python chat model suites pass after the rebase; ruff and Spotless are clean.
API
Touches the OpenAI connection: overrides the schema-carrying
chatand declares the capability predicate. No new user-facing surface beyond the foundation in #843.Documentation
doc-neededdoc-not-neededdoc-included