Python: preserve explicit null arguments in auto function calling#7108
Python: preserve explicit null arguments in auto function calling#7108he-yufeng wants to merge 3 commits into
Conversation
FunctionTool.invoke dumped validated arguments with model_dump(exclude_none=True), which strips any argument the model set to null. A required nullable parameter (e.g. unit: Literal["C","F"] | None) that the model deliberately sets to null was therefore dropped, and the function failed to invoke on the missing argument. Use exclude_unset instead: keep the arguments the model actually provided (null included) and omit only the ones it left out, so the function's own defaults still apply. Because the input model is generated from the function signature, its field defaults match the signature defaults, so omitted optionals are unchanged. Fixes microsoft#5934 Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes a Python tool-invocation edge case where explicitly-provided null arguments were being dropped during argument validation, which can break invocation of required-but-nullable parameters in auto function calling.
Changes:
- Switch
FunctionTool.invoke()argument dumping fromexclude_none=Truetoexclude_unset=Trueto preserve explicitnullvalues while still omitting truly-omitted fields. - Add regression tests to confirm explicit
Noneis preserved for required nullable parameters and omitted optionals still use function defaults.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_tools.py | Updates FunctionTool.invoke() argument dumping behavior to preserve explicit nulls. |
| python/packages/core/tests/core/test_tools.py | Adds regression tests for explicit-null preservation and omitted-optional default behavior. |
| # exclude_unset (not exclude_none): keep arguments the model | ||
| # explicitly provided even when their value is null, and drop | ||
| # only the ones it left out, so the function's own defaults | ||
| # apply. Excluding null instead would strip a required nullable | ||
| # parameter the model deliberately set to null, failing the |
There was a problem hiding this comment.
Good catch, and you're right that this was the path that mattered. I've pushed a follow-up that switches _auto_invoke_function to exclude_unset=True as well, so an explicit null in a model-emitted function_call now reaches the tool instead of being dropped by exclude_none. The three sites (invoke's mapping and BaseModel branches plus the auto-calling path) are consistent now.
| async def test_invoke_preserves_explicit_null_argument(): | ||
| """A required nullable argument the model sets to null must reach the function. | ||
| Regression for #5934: exclude_none dropped the explicit null, so the required | ||
| ``unit`` went missing and the invocation failed. |
There was a problem hiding this comment.
Added in the follow-up: test_auto_invoke_preserves_explicit_null_argument drives _auto_invoke_function directly with {"unit": null} and asserts the tool sees None. It fails on the old exclude_none (the required argument goes missing and parsing errors) and passes with the fix, so it guards the auto-calling path specifically.
The earlier change fixed FunctionTool.invoke, but _auto_invoke_function (the path a model-emitted function_call actually takes) still ran model_dump(exclude_none=True), so an explicit null for a required nullable argument was still dropped and the call failed with a missing argument. Switch it to exclude_unset to match invoke, and add a regression test that drives _auto_invoke_function with an explicit null.
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
Description
Fixes #5934.
FunctionTool.invokedumped the validated arguments withmodel_dump(exclude_none=True), which strips any argument whose value isnull. When the model deliberately sets a required nullable parameter tonull— e.g.unit: Literal["C", "F"] | Nonewith a description telling the model to set it tonullwhen the user gave no unit — that argument was dropped, and the function then failed to invoke on the missing required argument.The fix uses
exclude_unset=Trueinstead: keep the arguments the model actually provided (an explicitnullincluded) and omit only the ones it left out, so the function's own defaults still apply. Because the tool's input model is generated from the function signature, its field defaults match the signature defaults, so omitted optionals behave exactly as before — only explicitly-provided nulls are now preserved.Changed both the mapping path (the auto-function-calling path from the model) and the
BaseModelpath for consistency.Behavior
For a required nullable parameter the model sets to
null:An omitted optional argument still falls back to the function's own default (unchanged).
Tests
Adds
test_invoke_preserves_explicit_null_argument(the #5934 repro, fails against the old behavior) andtest_invoke_omitted_optional_uses_function_default(guards that omitted optionals still use the function default).