sangkyoonnam opened a new pull request, #1270:
URL: https://github.com/apache/flink-agents/pull/1270
Linked issue: #1256
### Purpose of change
For a flat `RowTypeInfo` output schema with only string, boolean or numeric
fields, the ReAct Agent now describes the schema to the model as JSON Schema
instead of the type's string form, in both languages. For `Row(name STRING,
count INT)` the prompt now carries
`{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}},"required":["name","count"],"additionalProperties":false}`
where it used to carry `Row(name: String, count: Integer)` (Java) or
`RowTypeInfo(name: String, count: Integer)` (Python). The string form names the
fields and Flink types but not the JSON shape to return. Native schema-document
support (`AUTO` and `NATIVE` for a `RowTypeInfo`) is deferred to a follow-up PR.
#### Runtime flow
1. The `ReActAgent` constructor (both languages) builds the schema text
once. For a `RowTypeInfo` it calls `RowTypeJsonSchema.render` (Java,
`@Internal`) or `render_row_type_json_schema` (Python), and uses the string
form, as before, when that returns `null`/`None`.
2. The text goes into both registered prompts, schema and native-schema.
3. `startAction` / `start_action` picks one of them, as before, and inserts
it into the request. The stored output schema, the native capability decision
and response conversion are unchanged.
Mapping: string to `string`, boolean to `boolean`, byte/short/int/long/big
integer to `integer`, float/double/big decimal to `number`.
#### Key decisions
- Same text in both languages: properties in field order, compact
separators, non-ASCII written as is, and lowercase hex in `\u` escapes (Jackson
writes uppercase by default, so the Java writer turns that off).
- Char, date and instant fields have no mapping, so the row keeps the string
form: the response is copied into the `Row` without conversion, and a `string`
value would have the wrong type there.
- Every field is required. The bare property types also exclude `null`;
nullability is deferred.
- A row built without names renders Java's default names `f0`, `f1`, ... in
both languages. Python reads them without `get_field_names()`, which would
start the JVM.
- Python rows with duplicate names or a name count that differs from the
type count, which Java rejects, keep the string form.
### Behavioral Semantics
#### Interaction decisions
| Row fields | Java | Python |
|---|---|---|
| All string, boolean or numeric | JSON Schema | JSON Schema |
| A basic char, date, instant or void field | string form, as before |
string form, as before (char, instant) |
| A nested or non-basic field (Python SQL date included) | construction
fails in `OutputSchema`, as before | string form; serializing `OutputSchema`
fails, as before |
#### Behavioral contracts
- A mapped row's prompt lists the properties in field order.
- All properties are `required`.
- `additionalProperties` is `false`.
- Java and Python produce the same text for the same row, including escaped
field names.
- A row built without names uses `f0`, `f1`, ... as property names.
- A row with an unmapped field keeps the string form in both prompts.
#### Failure behavior
- An unmapped field: the renderer returns `null`/`None` and the agent falls
back to the string form.
- A non-basic field: unchanged (table above).
- A Python row with duplicate or mismatched names: the renderer returns
`None` and the agent keeps the string form, as before.
- A Jackson serialization failure in the Java renderer is rethrown as a
`RuntimeException`, not absorbed into the fallback.
### Tests
| Contract | Tests |
|---|---|
| Field order | `rendersFlatRowInFieldOrder`,
`test_renders_flat_row_in_field_order` |
| Every field required | same two tests (shared expected string) |
| `additionalProperties: false` | same two tests |
| Numeric mapping | `mapsNumericTypes`, `test_maps_numeric_types` |
| Same text in both languages, escaped names |
`escapesFieldNamesLikePython`, `test_escapes_field_names_like_java` (shared
expected string) |
| Default names for an unnamed row | `rendersDefaultFieldNames`,
`test_unnamed_row_uses_default_names_without_the_jvm` (shared expected string),
`test_unnamed_unmapped_row_returns_none_without_the_jvm`; the Python tests fail
if the JVM starts |
| Duplicate or mismatched Python names keep the string form |
`test_returns_none_for_names_java_rejects` (empty, fewer, duplicate),
`test_row_type_info_with_duplicate_names_keeps_the_prompt_fallback` (both
prompts) |
| Unmapped field keeps the string form |
`returnsNullForFieldsWithoutJsonType`,
`test_returns_none_for_fields_without_json_type`,
`testRowTypeSchemaWithoutJsonTypeKeepsItsText` and
`test_row_type_info_without_json_type_keeps_the_prompt_fallback` (both prompts)
|
| Both prompts carry the schema |
`testRowTypeSchemaPromptCarriesJsonSchema`,
`testRowTypeSchemaRegistersNativeSchemaPrompt`,
`test_row_type_info_output_schema_renders_json_schema_in_the_prompt`,
`test_row_type_info_output_schema_registers_the_native_schema_prompt` |
Coverage by risk: parity is pinned by shared expected strings, the fallbacks
by prompt-level tests in both languages.
Not verified: nested or non-basic rows on the new path (unchanged,
untested), and any model's response to the new prompt. The response side still
copies JSON values into the `Row` without converting them to the field types,
and a missing field gives `null` in Java but a `KeyError` in Python; both are
outside this PR.
<details><summary>Verification runs</summary>
`api` tests pass on JDK 17 (748 run, 13 skipped), the Python `api` and
`plan` tests pass (1305), and `tools/lint.sh -c` passes on JDK 11. JDK 21 lint
passes too, but it skips Spotless there.
</details>
### API
Existing signatures and accepted schema kinds are unchanged. Rows with only
string, boolean or numeric fields get different prompt text; other rows keep
theirs. Native support and response conversion are unchanged.
`RowTypeJsonSchema` is new and `@Internal`; `render_row_type_json_schema` is a
new function in `flink_agents.api.agents.types`.
### 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]