🔴 Required Information
Describe the Bug:
LlmAgent.outputSchema(...) does not reach OpenAI-compatible models as a strict JSON Schema when the request is handled by ADK Java's native chat-completions connector.
ADK's request pipeline uses two different schema representations:
Basic.processRequest(...) attaches the agent's typed output schema through LlmRequest.Builder.outputSchema(...) for models that support output schemas alongside tools.
LlmRequest.Builder.outputSchema(...) stores that schema in GenerateContentConfig.responseSchema and sets responseMimeType = "application/json".
ChatCompletionsRequest.fromLlmRequest(...) never reads GenerateContentConfig.responseSchema.
- It creates
response_format.type = "json_schema" only when the separate raw responseJsonSchema field is present.
- Because only
responseMimeType = "application/json" survives, the connector silently falls back to generic JSON-object mode.
The model therefore receives:
{
"response_format": {
"type": "json_object"
}
}
instead of the declared shape:
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "response_schema",
"strict": true,
"schema": {
"type": "object",
"properties": {
"rootCause": {
"type": "string"
},
"confidence": {
"type": "number"
}
},
"required": [
"rootCause",
"confidence"
],
"additionalProperties": false
}
}
}
}
The downgrade is silent. No exception or warning indicates that the declared output shape was discarded.
Steps to Reproduce:
- Create a typed output schema:
Schema outputSchema =
Schema.builder()
.type(Type.Known.OBJECT)
.properties(
Map.of(
"rootCause",
Schema.builder()
.type(Type.Known.STRING)
.build(),
"confidence",
Schema.builder()
.type(Type.Known.NUMBER)
.build()))
.required(List.of("rootCause", "confidence"))
.build();
- Build an
LlmRequest through the same API used by LlmAgent.outputSchema(...):
LlmRequest request =
LlmRequest.builder()
.model("openai-compatible-model")
.contents(List.of())
.outputSchema(outputSchema)
.build();
ChatCompletionsRequest converted =
ChatCompletionsRequest.fromLlmRequest(request, false);
-
Serialize converted and inspect response_format.
-
Alternatively, reproduce the issue through a real LlmAgent using a non-Gemini-2 model name, including when the agent has tools. Basic.processRequest(...) calls builder.outputSchema(...), but the chat-completions conversion still drops the typed shape.
Expected Behavior:
When responseJsonSchema is absent but typed responseSchema is present, ChatCompletionsRequest should convert the typed schema into an OpenAI-compatible strict JSON Schema and emit:
{
"response_format": {
"type": "json_schema"
}
}
An explicitly supplied raw responseJsonSchema should retain precedence.
The conversion should produce a schema valid for strict OpenAI-compatible endpoints, including:
- lowercase JSON Schema types;
additionalProperties: false for object schemas;
- valid nested object and array schemas;
- explicit handling of optional properties.
Optional properties should either be translated into the endpoint's supported nullable representation or rejected clearly rather than producing an invalid strict schema.
Observed Behavior:
The typed schema is ignored.
Because LlmRequest.Builder.outputSchema(...) also sets:
responseMimeType = application/json
ChatCompletionsRequest emits only:
{
"response_format": {
"type": "json_object"
}
}
The model is constrained to produce some JSON object, but its fields are not constrained to the declared output schema.
Models differ in how reliably they follow prompts under this weaker mode. A model may return:
- an arbitrary JSON shape;
- missing required fields;
- narration around JSON.
This can break downstream parsing of the expected output contract.
Environment Details:
- ADK Library Version (see maven dependency):
1.7.1
- Also verified unchanged in ADK Java
1.8.0
- Also verified unchanged on upstream
main on 2026-08-20
- OS: N/A
- Java: 21
- Connector:
com.google.adk.models.chat.ChatCompletionsHttpClient
- Endpoint: OpenAI-compatible gateway
Model Information:
- Which model is being used: OpenAI-compatible gateway routing Claude/Opus models
- The defect is in deterministic request conversion and is model-independent. Model choice only changes how visible the weaker JSON-object constraint becomes.
🟡 Optional Information
Regression:
No known regression. The typed responseSchema and raw responseJsonSchema representations appear never to have been connected in ChatCompletionsRequest.
Logs:
N/A.
Screenshots / Video:
N/A.
Additional Context:
This is the response-side analogue of #1426 / PR #1427:
Minimal Reproduction Code:
Schema outputSchema =
Schema.builder()
.type(Type.Known.OBJECT)
.properties(
Map.of(
"rootCause",
Schema.builder()
.type(Type.Known.STRING)
.build(),
"confidence",
Schema.builder()
.type(Type.Known.NUMBER)
.build()))
.required(List.of("rootCause", "confidence"))
.build();
LlmRequest request =
LlmRequest.builder()
.model("openai-compatible-model")
.contents(List.of())
.outputSchema(outputSchema)
.build();
ChatCompletionsRequest converted =
ChatCompletionsRequest.fromLlmRequest(request, false);
Serialize converted and inspect the resulting response_format.
Suggested Tests:
- Typed
responseSchema only → serialized strict json_schema response format.
- Explicit raw
responseJsonSchema → raw schema retains precedence.
- No schema plus
responseMimeType=application/json → existing json_object behavior.
- Nested object/array schemas with lowercase types and strict object constraints.
- Optional-property semantics are translated correctly or rejected clearly.
- A real
LlmAgent with tools and a non-Gemini-2 model name follows the same path.
How often has this issue occurred?:
🔴 Required Information
Describe the Bug:
LlmAgent.outputSchema(...)does not reach OpenAI-compatible models as a strict JSON Schema when the request is handled by ADK Java's native chat-completions connector.ADK's request pipeline uses two different schema representations:
Basic.processRequest(...)attaches the agent's typed output schema throughLlmRequest.Builder.outputSchema(...)for models that support output schemas alongside tools.LlmRequest.Builder.outputSchema(...)stores that schema inGenerateContentConfig.responseSchemaand setsresponseMimeType = "application/json".ChatCompletionsRequest.fromLlmRequest(...)never readsGenerateContentConfig.responseSchema.response_format.type = "json_schema"only when the separate rawresponseJsonSchemafield is present.responseMimeType = "application/json"survives, the connector silently falls back to generic JSON-object mode.The model therefore receives:
{ "response_format": { "type": "json_object" } }instead of the declared shape:
{ "response_format": { "type": "json_schema", "json_schema": { "name": "response_schema", "strict": true, "schema": { "type": "object", "properties": { "rootCause": { "type": "string" }, "confidence": { "type": "number" } }, "required": [ "rootCause", "confidence" ], "additionalProperties": false } } } }The downgrade is silent. No exception or warning indicates that the declared output shape was discarded.
Steps to Reproduce:
LlmRequestthrough the same API used byLlmAgent.outputSchema(...):Serialize
convertedand inspectresponse_format.Alternatively, reproduce the issue through a real
LlmAgentusing a non-Gemini-2 model name, including when the agent has tools.Basic.processRequest(...)callsbuilder.outputSchema(...), but the chat-completions conversion still drops the typed shape.Expected Behavior:
When
responseJsonSchemais absent but typedresponseSchemais present,ChatCompletionsRequestshould convert the typed schema into an OpenAI-compatible strict JSON Schema and emit:{ "response_format": { "type": "json_schema" } }An explicitly supplied raw
responseJsonSchemashould retain precedence.The conversion should produce a schema valid for strict OpenAI-compatible endpoints, including:
additionalProperties: falsefor object schemas;Optional properties should either be translated into the endpoint's supported nullable representation or rejected clearly rather than producing an invalid strict schema.
Observed Behavior:
The typed schema is ignored.
Because
LlmRequest.Builder.outputSchema(...)also sets:ChatCompletionsRequestemits only:{ "response_format": { "type": "json_object" } }The model is constrained to produce some JSON object, but its fields are not constrained to the declared output schema.
Models differ in how reliably they follow prompts under this weaker mode. A model may return:
This can break downstream parsing of the expected output contract.
Environment Details:
1.7.11.8.0mainon 2026-08-20com.google.adk.models.chat.ChatCompletionsHttpClientModel Information:
🟡 Optional Information
Regression:
No known regression. The typed
responseSchemaand rawresponseJsonSchemarepresentations appear never to have been connected inChatCompletionsRequest.Logs:
N/A.
Screenshots / Video:
N/A.
Additional Context:
This is the response-side analogue of #1426 / PR #1427:
ChatCompletionsRequestreads typed toolparametersbut ignores rawparametersJsonSchema.ChatCompletionsRequestreads rawresponseJsonSchemabut ignores typedresponseSchema.Minimal Reproduction Code:
Serialize
convertedand inspect the resultingresponse_format.Suggested Tests:
responseSchemaonly → serialized strictjson_schemaresponse format.responseJsonSchema→ raw schema retains precedence.responseMimeType=application/json→ existingjson_objectbehavior.LlmAgentwith tools and a non-Gemini-2 model name follows the same path.How often has this issue occurred?: