sangkyoonnam opened a new pull request, #1260:
URL: https://github.com/apache/flink-agents/pull/1260
Linked issue: #1254
### Purpose of change
#### User-visible outcome
A Java native output schema now lists each enum constant under the value
Jackson writes for it, typed as that value. An enum with `@JsonProperty` on
only some constants lists `["in-progress", "DONE"]` instead of `["IN_PROGRESS",
"DONE"]`, so a model reply using a listed value deserializes. An `int`
`@JsonValue` enum gives `{"type":"integer","enum":[1,-1]}` instead of
`{"type":"string","enum":[1,-1]}`, which no value satisfied. Every Java
provider builds its native schema through `PojoJsonSchemaGenerator`, so all of
them get this.
The Java code change below is what the Implementation Description covers.
The diff also updates the generator's settings comment and the structured
output guide, which listed both cases as limitations.
#### Runtime flow
`PojoJsonSchemaGenerator` registers a custom definition provider for enum
types before it adds the Jackson module. For an enum with at least one
constant, the provider takes `ObjectMapper.valueToTree(constant)` for each
constant, writes a non-finite float or double as the string Jackson emits for
it, lists the results under `enum` (`const` for a single value), and sets
`type` from their JSON types (an array if they differ). victools uses the first
provider that returns a definition, so for these enums the Jackson module's
flattened-enum provider no longer runs. For an enum without constants, or when
writing a constant throws, the provider returns nothing and the Jackson module
handles the enum as before.
#### Key decisions
- Use the value Jackson writes rather than re-reading `@JsonProperty` and
`@JsonValue`. Serialization already resolves both annotations and the Java-name
fallback for each constant.
- Keep the Jackson module's enum options as the fallback for the two cases
the new provider skips.
### Behavioral Semantics
#### Interaction decisions
| Enum | Before | After |
|---|---|---|
| `@JsonProperty` on every constant | mapped values, `string` | same |
| `@JsonProperty` on some constants | Java names for all, `string` | mapped
values where annotated, Java names otherwise, `string` |
| String `@JsonValue` method | mapped values, `string` | same |
| Integer `@JsonValue` method | numbers, `string` | numbers, `integer` |
| Finite floating-point `@JsonValue` method | numbers, `string` | numbers,
`number` |
| Non-finite `@JsonValue` method results | strings such as `"NaN"`, `string`
| same strings; `type` adds `string` beside `number` for finite ones |
| `@JsonValue` on a field | Java names, `string` | the field values, typed
as written |
| Single constant | `const` | `const` |
| No annotations | Java names, `string` | same |
| No constants | `type: string`, no `enum` | same |
#### Behavioral contracts
1. For an enum mapped by `@JsonProperty`, by a `@JsonValue` method returning
a `String` or an integral primitive or wrapper, or by its Java names, and
without a custom creator or deserializer, every listed value deserializes into
the enum with a default `ObjectMapper`.
2. The declared `type` matches the JSON type of the listed values.
3. An enum without constants keeps the schema the Jackson module gives it.
#### Failure behavior
If `valueToTree` throws `IllegalArgumentException`, for example from a
throwing `@JsonValue` method, the provider discards the whole enum definition
and the Jackson module's configuration applies as before. Other failures
propagate. Values from `@JsonValue` methods with other return types, such as
finite floating-point, `boolean`, `BigInteger` or `Object`, are listed as
written and may require matching deserialization support, such as a
`@JsonCreator`, to read back; that was already true of the values the module
listed.
### Tests
| Contract | Tests |
|---|---|
| 1 | `listsPartlyAnnotatedEnumByItsJacksonWireValues` (new),
`listsEnumConstantsByTheirJacksonWireValues` (existing),
`enumDefinitionWinsOverJacksonModule` (new) |
| 2 | `enumDefinitionWinsOverJacksonModule` (new),
`writesNonFiniteNumbersAsStringsAndSingleValuesAsConst` (new) |
| 3 | `leavesEnumWithoutConstantsToJacksonModule` (new), through both
`generate` and `generateAllRequired` |
By risk: the two cases from the issue and the precedence over the Jackson
module are pinned by tests that fail on main; existing tests check round-trip
values for fully annotated and string-`@JsonValue` enums, and the all-required
comparison checks the two methods match apart from `required`.
`PojoJsonSchemaGeneratorTest` and the anthropic, bedrock, gemini, ollama,
openai and watsonx module tests pass on JDK 21 (510 run, 0 failures, 2
skipped), and `tools/lint.sh -c` passes on JDK 11 and 21.
Not verified: no test covers finite-only floating-point, boolean or
mixed-type `@JsonValue` results, `@JsonValue` on a field, or the
throwing-`@JsonValue` fallback; the live provider tests are skipped without
credentials.
<details>
<summary>Implementation invariants and evidence</summary>
- The provider must be registered before `builder.with(new
JacksonModule(...))`, because `with` applies the module immediately and the
first provider that returns a definition wins.
`enumDefinitionWinsOverJacksonModule` fails if the registration moves after the
module.
- The provider returns `new CustomDefinition(definition)`, the same standard
definition type the module produces, so an enum used in several fields still
goes through `$defs`/`$ref` as before.
- Responses for POJO output schemas are read by `ChatModelAction`'s plain
`ObjectMapper`. Both paths use the default `ObjectMapper` configuration;
annotations such as a custom creator can still make serialization and
deserialization asymmetric.
</details>
### API
No signature changes. `generate` and `generateAllRequired` now derive enum
values from Jackson serialization, so the `enum`/`type` change for the rows
above that differ, including `@JsonValue` fields and boolean or mixed-type
results. The ReAct Agent's prompt text uses a different generator and is
unchanged, as is the Python side.
### Documentation
- [ ] `doc-needed`
- [ ] `doc-not-needed`
- [x] `doc-included`
### Was this patch authored or co-authored using generative AI tooling?
- [x] Yes
- [ ] No
Generated-by: Claude Code 2.1.296 (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]