fix: Enforce strict JSON Schema compliance to prevent OpenAI 400 Bad Request errors#1266
Conversation
|
Hi @svetanis, thank you for your contribution! We appreciate you taking the time to submit this pull request. I noticed that your changes are not covered by the current test cases and the test you included passes even without your changes. Could you please include the corresponding unit tests to verify your changes? |
1f64496 to
7017e53
Compare
|
Hello @hemasekhar-p , Thank you for the review and the feedback! I've updated the PR and added three new dedicated unit tests in
I have also successfully rebased the branch on top of the latest Additionally, I ran comprehensive end-to-end testing on the rebased branch, and all of the demos (including a new one validating the Please let me know if there is anything else you need! |
|
@svetanis, thank you for addressing the comments. Currently this PR is under review by our team, we will keep you posted if any additional information is required. thank you. |
|
@anFatum, Could you please review this PR. |
wikaaaaa
left a comment
There was a problem hiding this comment.
Thank you for the contribution!
We're on board with the empty/null handling: treating empty arguments/content and zero-argument parameters as valid and serializing them correctly ("{}" / {"type":"object","properties":{}}).
We have two main points we'd like to align on before approving:
-
enforceJsonObject function - please see the other comment
-
Inbound parsing only covers the non-streaming path. The streaming finalizer (ChatCompletionsResponse.getFinalToolCallParts) parses tool-call args separately and isn't updated, so the same input can behave differently depending on stream=true/false. The PR should align both paths (ideally via one shared parser).
| * "null", "NULL", "none", or conversational text, we fallback to an empty JSON object "{}". This | ||
| * prevents OpenAI-compatible proxies (like Groq) from throwing 400 Bad Request errors. | ||
| */ | ||
| static String enforceJsonObject(String json) { |
There was a problem hiding this comment.
The raised issue and main problem if I understand correctly is that empty fields are valid (e.g. a zero-arg call legitimately has no args) but ADK serializes them incorrectly (null/omitted) which causes failures.
enforceJsonObject, though, does something broader than the empty case: its !json.trim().startsWith("{") branch silently rewrites non-empty, non-object argument payloads (e.g. malformed/garbage input) to {}. That's really invalid/malformed-input handling — a separate concern from this issue. Silently dropping that content probably isn't what we want, and how invalid args should be handled deserves its own look (and a consistent approach across ADK) rather than being settled implicitly here.
And that startsWith branch is effectively the only thing the method does — without it, enforceJsonObject is just json == null ? "{}" : json.trim(), which is a no-op given the call sites (input is never null, and writeValueAsString/readValue already handle whitespace). So I'd suggest dropping the whole enforceJsonObject helper.
bfd3ed6 to
b7f5eb2
Compare
|
Thank you for the feedback! Both points are addressed in the updated commit. 1.
|
| Input | Expected Result | Test |
|---|---|---|
Valid JSON ({"key":"val"}) |
Parsed Map |
withValidJson |
null |
Map.of() |
withNullString |
"" |
Map.of() |
withEmptyString |
" " |
Map.of() |
withWhitespaceString |
Invalid JSON ("none", "{bad:") |
JsonProcessingException |
withInvalidJson |
Literal "null" |
JsonMappingException |
withLiteralNullString |
The existing ChatCompletionsResponseTest streaming tests (parsesValidJsonArgs, handlesEmptyArgs, throwsOnInvalidJsonArgs) exercise the streaming path through the same shared parser.
Rebased onto latest main, single clean commit, all tests passing.
|
|
||
| static final String EMPTY_JSON_OBJECT = "{}"; | ||
| static final Map<String, Object> EMPTY_PARAMETERS_SCHEMA = | ||
| Map.of("type", "object", "properties", Map.of()); |
There was a problem hiding this comment.
Please change Map.of() here and in other places to ImmutableMap.of() from com.google.common.collect.ImmutableMap;
Otherwise our internal tests fail
b7f5eb2 to
784dd98
Compare
|
Thank you for catching this! Completely agree — Done. All
I also updated Rebased onto latest |
Enforce strict JSON Schema compliance to prevent OpenAI 400 Bad Request errors
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
When using strict OpenAI-compatible providers like Groq, empty function arguments or responses are serialized as
nullor omitted byChatCompletionsRequest. Furthermore, zero-argument tools fail to declare aparametersschema object. This breaks strict JSON schema parsers, causing them to drop conversation history, which leads to400 Bad Requestexceptions due to models hallucinating raw XML<function>tags. JSON Schema enum types are also improperly serialized in uppercase.Solution:
schemaNormalizerModuleto theObjectMapperinChatCompletionsRequest.javato forceTypeenums to serialize in lowercase (e.g.,"string").enforceJsonObjecttoChatCompletionsCommon.javato guarantee that emptyargumentsand functionresponsepayloads fallback to"{}"instead ofnullor raw strings.ChatCompletionsRequest.javato explicitly inject a{"type":"object", "properties":{}}parameters schema for zero-argument functions instead of omitting it.Testing Plan
Unit Tests:
Passed:
mvn testAdded
testFromLlmRequest_withEmptyFunctionArgumentstoChatCompletionsRequestTest.javato explicitly test"{}"serialization for zero-argument tools. UpdatedtestFromLlmRequest_withFunctionResponseexpectations.Manual End-to-End (E2E) Tests:
Wired the Google native chat completion client into the external
model-prismproject (PR #1199). Ran the integration through all the demo tests covering all main ADK features. The applications successfully execute multi-turn multi-tool conversations using strict JSON schema endpoints without throwing400 Bad Requestdue to context loss.Checklist
Additional context
This change unlocks robust support for the rapidly growing ecosystem of OpenAI-compatible endpoints that enforce strict JSON Schema validation.