[api][integrations] Support models' native structured output (foundation + OpenAI)#843
[api][integrations] Support models' native structured output (foundation + OpenAI)#843weiqingy wants to merge 3 commits into
Conversation
…ion + OpenAI) Add the foundation for using a model provider's native structured-output capability at the chat-model connection layer, plus the OpenAI implementation, in both Java and Python. Previously output_schema was honored only by prompt-engineering the request and parsing the response text. The request's output schema is carried to the connection through a reserved key in the existing modelParams/kwargs map, so the abstract chat() signature is unchanged. Each connection declares a boolean native-structured-output capability (default false). A connection applies the native API only when a schema is present, no tools are bound on the call, the schema is a POJO (Java) / BaseModel (Python) rather than a RowTypeInfo, and the setup is same-language. The reserved key is always removed before the SDK call so it cannot leak into a provider request. The prompt-engineered path is retained as the fallback and is unaffected: in the ReAct loop tools are always bound, so the native path stays dormant there. OpenAI applies response_format json_schema with strict validation. Other connections only strip the reserved key; their native paths and the ReActAgent final-output wiring follow in later changes.
c312e35 to
e2388dd
Compare
|
The failing check was unrelated to this PR — it was a transient CI infra failure:
Re-triggered CI (empty commit) to clear it — all checks are green now. |
|
Hi @weiqingy, thanks for the PR. Overall, the approach looks good. One thought on the strip: right now every non-native connection has to pop the key itself (the 4 non-OpenAI Python ones do it purely to avoid leaking; the Java ones don't strip at all and are only safe incidentally). Since both languages already funnel through # Python
if not connection.supports_native_structured_output:
merged_kwargs.pop(STRUCTURED_OUTPUT_SCHEMA_KEY, None)// Java
if (!connection.supportsNativeStructuredOutput()) {
params.remove(BaseChatModelConnection.STRUCTURED_OUTPUT_SCHEMA_KEY);
}That removes the 4 leak-only pops on the Python side, closes the Java asymmetry, and gives the flag a real caller on both. Native connections would still pop themselves (to discard when tools are bound). Not blocking. |
… model setup Strip the reserved structured-output schema key in BaseChatModelSetup.chat() for connections that do not support native structured output, instead of each non-native connection popping it. Both languages funnel through the setup before connection.chat(), so this gives supportsNativeStructuredOutput a real caller on both sides, removes the leak-only pops from the non-native Python connections, and closes the Java asymmetry where non-native connections never stripped the key. Native connections keep the key and pop it themselves to build the provider's native parameter.
|
Thanks for the review, @GreatEugenius — this is a nice simplification. Done in f35e432. |
|
LGTM. The centralized strip in |
|
Thanks, @GreatEugenius! @wenjin272, could you take a look when you get a chance? |
Linked issue: #280
Purpose of change
Today
output_schemais honored only by prompt-engineering and parsing the response text; no integration uses a provider's native structured-output API. This PR adds the foundation for native structured output at the chat-model connection layer, plus the OpenAI implementation, in Java and Python. It's the first in a small stack under #280 (Azure/Ollama, Anthropic, DashScope follow; ReActAgent final-output wiring is a separate follow-up).How it works:
__structured_output_schema__) in the existingmodelParams/kwargsmap, so the abstractchat()signature is unchanged.supportsNativeStructuredOutput()/supports_native_structured_output), defaultfalse.BaseModel(notRowTypeInfo), and the setup is same-language. The key is always stripped before the SDK call so it can't leak into a request.OpenAI applies
response_formatjson_schema strict. Other connections only strip the reserved key for now. The same-language guard avoids marshaling a schema object across the Pemja bridge, where native structured output can't work anyway (a JavaClassis not a PythonBaseModel).Tests
Unit tests with the SDK mocked (no network): native applied with schema and no tools (Java + Python); not applied when tools are bound or for
RowTypeInfo; the reserved key never reaches a provider SDK; the same-language threading guard; and existing ReActAgent prompt-path tests remain green.API
Yes — additive only.
BaseChatModelConnectiongains a public reserved-key constant and aprotectedcapability method (defaultfalse); no existing signatures change.Documentation
doc-neededdoc-not-neededdoc-included