apache / apache/flink-agents

[Tech Debt][API][Tool] Unify function-schema derivation and argument validation

Open
#1,093 0 comments 0 reactions 0 assignees View on GitHub
fixVersion/0.4.0 priority/major tech debt
Dominant language
Java
Stars
452
Forks
167
Avg merge
5d 9h
Merged PRs (30d)
49

Description

### Search before asking

- [x] I searched in the [issues](https://github.com/apache/flink-agents/issues) and found nothing similar.

### Description

The Python tool schema utilities currently mix several responsibilities in `flink_agents.api.tools.utils`:

- deriving a Pydantic argument model from a Python callable signature, docstring, defaults, annotations, and injected parameters;
- reconstructing API schema models from serialized JSON Schema;
- converting tool schemas across the Java/Python bridge.

This no longer matches the current API/plan boundary. The API-layer `FunctionTool` is a declarative descriptor that carries a function reference and injected-argument declarations, while callable introspection and executable `ToolMetadata` derivation happen in the plan layer. The Java implementation already places the corresponding reflection-based `SchemaUtils` in `plan.tools`.

Keeping `create_schema_from_function` in the API module makes an implementation-specific compilation step look like part of the API contract. It also turns the generic `utils.py` module into a coupling point for API serialization, plan construction, and runtime bridge code. In addition, Python metadata derivation is partly duplicated between `plan.tools.function_tool` and `runtime.python_java_utils`.

#### Validation gap

The generated Pydantic model is currently used to advertise and serialize the tool input schema, but it is not used to validate the arguments that are actually passed to a Python tool. `tool_call_action` merges model-provided and framework-injected values and forwards the resulting dictionary to `FunctionTool.call`, which invokes the underlying callable directly with `func(**kwargs)`.

As a result, the advertised schema and runtime behavior can diverge:

- Pydantic and `Annotated` constraints can be shown to the model but are not enforced before invocation;
- nested Pydantic models are not necessarily reconstructed from JSON objects;
- missing, invalid, or unexpected arguments fail through Python binding or user code instead of one deterministic validation path;
- defaults and type coercion depend on Python callable behavior rather than the generated schema;
- Java uses a separate, limited `ToolParameters` conversion path, producing different Java/Python semantics.

Schema derivation and runtime validation should therefore be treated as one contract. The same function-schema component that describes model-visible input should also provide validation, normalization, and callable binding for that input.

The desired boundary is:

- keep the tool schema contract and its API-level serialization/deserialization in the API module;
- move Python-callable introspection, schema/metadata derivation, and argument validation/binding to a dedicated plan-layer component;
- let normal plan construction, tool execution, and the cross-language runtime bridge reuse that component;
- validate and normalize model-provided arguments against the same schema advertised to the model before invoking the callable;
- keep framework-injected arguments on a distinct path so they remain hidden from the model, cannot be spoofed, and are not rejected as unexpected model input;
- move Java/Python bridge-specific schema conversion out of the generic API utility where appropriate;
- define consistent Java/Python behavior for required and extra arguments, defaults, coercion, nested values, constraints, and validation errors;
- preserve the existing serialized schema unless an intentional schema change is explicitly agreed.

#### Acceptance criteria

- The API module no longer owns Python-callable introspection or plan-time metadata derivation.
- Plan construction, tool execution, and the runtime bridge share one function-schema implementation rather than independently deriving or interpreting it.
- Model-provided arguments are validated and normalized against the same schema advertised to the model before the callable is invoked.
- Invalid arguments produce a deterministic tool failure with a useful validation error, and the underlying callable is not invoked.
- Required fields, extra fields, defaults, coercion, nested models, and Pydantic/`Annotated` constraints have an explicit, tested policy.
- Injected arguments remain absent from the model-visible schema and cannot be supplied or overridden by the model.
- The `runtime -> plan -> api` dependency direction remains intact.
- Java and Python place equivalent schema-derivation responsibilities at consistent architectural layers.
- Focused tests cover schema derivation and runtime invocation, including docstrings, `Annotated` metadata and constraints, defaults, missing annotations/descriptions, nested models, invalid/extra arguments, and injected arguments.
- Existing tool schema serialization remains unchanged except for separately agreed fixes.

This is related to #814, which added direct tests for the existing utilities, but addresses the architectural boundary rather than the earlier test-coverage gap.

### Are you willing to submit a PR?

- [x] I'm willing to submit a PR!

Contributor guide

Open the contributing guide

Research direction

Start by tracing flink_agents.api.tools.utils, plan.tools.function_tool, runtime.python_java_utils, tool_call_action, and FunctionTool.call to map the duplicated derivation and invocation paths. Compare the Python flow with the Java plan.tools SchemaUtils and the existing focused tests. Done means one shared plan-layer schema implementation validates and normalizes model arguments, preserves hidden injected arguments and serialized schemas, and has tests for the listed edge cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, python
Domain
backend-api-design
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.