Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions python/packages/core/agent_framework/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,14 @@ async def invoke(
if isinstance(arguments, Mapping):
parsed_arguments = dict(arguments)
if self.input_model is not None and not self._schema_supplied:
# 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
Comment on lines +658 to +662
# invocation on the missing argument (#5934).
parsed_arguments = self.input_model.model_validate(parsed_arguments).model_dump(
exclude_none=True
exclude_unset=True
)
elif isinstance(arguments, BaseModel):
if (
Expand All @@ -665,7 +671,7 @@ async def invoke(
and not isinstance(arguments, self.input_model)
):
raise TypeError(f"Expected {self.input_model.__name__}, got {type(arguments).__name__}")
parsed_arguments = arguments.model_dump(exclude_none=True)
parsed_arguments = arguments.model_dump(exclude_unset=True)
else:
raise TypeError(
f"Expected mapping-like arguments for tool '{self.name}', got {type(arguments).__name__}"
Expand Down
27 changes: 27 additions & 0 deletions python/packages/core/tests/core/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,33 @@ def search(query: str) -> str:
await search.invoke(arguments={})


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.
Comment on lines +154 to +158
"""

@tool
def get_weather(location: str, unit: Literal["C", "F"] | None) -> str:
return f"{location}:{unit}"

result = await get_weather.invoke(arguments={"location": "Seattle", "unit": None})
assert isinstance(result, list)
assert result[0].text == "Seattle:None"


async def test_invoke_omitted_optional_uses_function_default():
"""An omitted optional argument still falls back to the function's own default."""

@tool
def get_weather(location: str, unit: str = "C") -> str:
return f"{location}:{unit}"

result = await get_weather.invoke(arguments={"location": "Seattle"})
assert result[0].text == "Seattle:C"


async def test_tool_decorator_with_json_schema_invoke_invalid_type():
"""Test schema type checks run for mapping arguments."""

Expand Down