weiqingy opened a new pull request, #1247: URL: https://github.com/apache/flink-agents/pull/1247
Linked issue: #1246 ### Purpose of change When an output schema has an enum whose constants carry `@JsonProperty` or a `@JsonValue` method, the native schema sent by the OpenAI Chat Completions, Azure OpenAI and Anthropic connections (and vLLM, which extends the OpenAI one) now lists the values Jackson reads. Before, it listed the Java constant names, the provider held the model to them, and the reply failed to parse. These three connections built the schema through their SDKs' structured-output builders, which ignore Jackson's enum annotations. Ollama, Gemini, Bedrock and watsonx already use the shared `PojoJsonSchemaGenerator`, so this moves the three onto it. One enum rule then holds for every Java provider, and it matches Python. #### Runtime flow 1. `buildRequest` decides whether the schema travels natively, unchanged. 2. If it does, `OpenAIChatCompletionsUtils.toNativeResponseFormat` (OpenAI, Azure, vLLM) or `AnthropicChatModelConnection.toNativeOutputConfig` calls `PojoJsonSchemaGenerator.generateAllRequired(schemaClass, FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT)`. 3. The schema is wrapped into the SDK request object: OpenAI's `json_schema` response format named `json-schema-from-<SimpleName>` with `strict: true`, or Anthropic's `output_config.format`. #### Key decisions - **A new `generateAllRequired` next to `generate`.** OpenAI strict mode rejects a schema whose `required` omits a property, while `generate` leaves `Optional` fields out. The new method differs only in the required set; an `Optional` field stays nullable. - **Anthropic also requires every field**, as its SDK schema did, so the set of fields a reply may omit does not change. - **The SDK builders are dropped, not tuned.** They take no generator options, and the latest openai-java and anthropic-java still use the same bare `JacksonModule()`. - **One OpenAI helper** in `OpenAIChatCompletionsUtils` replaces the identical copies in the two OpenAI-SDK connections. ### Behavioral Semantics #### Interaction decisions | Enum constants | Listed in the schema, all three connections | |---|---| | No annotations | Constant names, unchanged | | `@JsonProperty` on every constant | The property values (was: names) | | `@JsonValue` method returning a `String` | The method's values (was: names) | | `@JsonProperty` on only some constants | Constant names, unchanged, as on the other four providers | | `@JsonValue` method returning a number | The numbers under `"type": "string"` (was: names), as on the other four providers | #### Behavioral contracts 1. An enum annotated on every constant with `@JsonProperty`, or through a `String` `@JsonValue` method, is listed by the values Jackson deserializes. 2. Every object's properties are all in its `required` set, at every level; an `Optional` field is required and admits `null`. 3. Every object forbids additional properties. 4. The OpenAI response format is strict and named after the schema class. 5. A polymorphic member is still sent as a discriminated union. 6. When the schema does not travel natively, no response format or output config is added, unchanged. #### Failure behavior - A reply that does not match the POJO still fails in structured-output parsing, as before. For the two fixed enum shapes it no longer happens when the model follows the schema. - A schema the provider rejects surfaces as the provider's error, as before. A numeric `@JsonValue` enum's schema may now be rejected by the provider where it previously failed at parse time. - The SDK path's `IllegalStateException` for a missing response format or output config is gone; the generator always returns a schema. ### Tests | Contract | Tests | |---|---| | 1 | `derivedSchemaFollowsJacksonEnumValues` in `OpenAICompletionsConnectionTest`, `AzureOpenAIChatModelConnectionTest`, `AnthropicChatModelConnectionTest` | | 2, 3 | `derivedSchemaRequiresEveryFieldAndForbidsExtras` (OpenAI Completions, Anthropic); `PojoJsonSchemaGeneratorTest.allRequiredRequiresOptionalFieldsAsNullable`, `allRequiredChangesOnlyTheRequiredSet` | | 4 | `testNativeAppliedForPojoCapableModel`, `testNativeAppliedForCapableDeploymentModel` | | 5 | `testPolymorphicMemberSchemaIsSent` (all three) | | 6 | Existing `testNativeNotApplied*`, `testNativeSchemaNotAppliedWithoutSchema`, `testNonClassSchemaKeepsFallback` | The three enum tests fail without the connection change, at the enum round-trip, with Jackson rejecting `"IN_PROGRESS"`. Commands, JDK 17: - `mvn -B --no-transfer-progress -pl integrations/chat-models/common,integrations/chat-models/openai,integrations/chat-models/anthropic clean test`: passed (8, 148, 103 tests, 0 failures). - `./tools/check-license.sh`: passed. Not verified: - No live call to OpenAI, Azure OpenAI, Anthropic or vLLM. - How each provider handles the numeric `@JsonValue` enum schema. <details><summary>Schema snapshot before and after, and implementation invariants</summary> Fourteen fixtures were rendered through both the old SDK path and the new one: enums (all `@JsonProperty`, `String` `@JsonValue`, partial `@JsonProperty`, numeric `@JsonValue`, plain), `Optional` with a nested object and `@JsonPropertyDescription`, collections, primitives, `@JsonProperty` rename with `@JsonIgnore`, a getter bean, a `@JsonTypeInfo`/`@JsonSubTypes` member, a recursive type, an inherited type, and a plain POJO. The only differences: - Enum arrays for the all-`@JsonProperty`, `String` `@JsonValue` and numeric `@JsonValue` fixtures. - Property order, alphabetical before and declaration order after, in 11 fixtures. Identical before and after: `$schema`, every `required` set, `additionalProperties: false` at every level, `Optional` as `["string", "null"]`, the polymorphic `anyOf` with `const` discriminators, descriptions, and the OpenAI format name, `strict` and `type`. Before and after, Azure and OpenAI Completions send the same response format, and Anthropic sends the same schema as OpenAI. Implementation invariants: - `generateAllRequired` and `generate` share one body and differ only in the required-field check; `allRequiredChangesOnlyTheRequiredSet` compares the two outputs with `required` removed. - The schema map is built from the `ObjectNode`'s fields in order, so the SDK serializes properties in the generator's order. - `openai` and `anthropic` now depend on `flink-agents-integrations-chat-models-common` and `jsonschema-generator` (BOM-managed), which `dist` already bundles for the other providers. </details> ### API No public API changes for agent code. `PojoJsonSchemaGenerator` gains `generateAllRequired(Class<?>, Option...)`; `generate` is unchanged. For existing callers of these three connections: - Enums annotated as in contract 1 now parse instead of failing. - Properties are sent in declaration order instead of alphabetical order, as on the other providers. - Swagger 2 `@Schema` annotations on the schema class are no longer read. Jackson annotations, including `@JsonPropertyDescription`, still are. - Everything else in the schema, and requests without a native schema, are unchanged. ### Documentation - [ ] `doc-needed` - [x] `doc-not-needed` - [ ] `doc-included` ### Was this patch authored or co-authored using generative AI tooling? - [x] Yes - [ ] No Generated-by: GitHub Copilot CLI 1.0.95 (claude-opus-5.5) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
