-
Notifications
You must be signed in to change notification settings - Fork 2.2k
fix: Support list[BaseModel] annotation about FunctionTool args #3186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
t-miyak
wants to merge
11
commits into
google:main
from
t-miyak:feature/support-list-of-pydantic-model-as-tool-args-completely
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
985e948
fix: support list of pydantic model to FunctionTool arg annotation
t-miyak f07a5aa
refactor: format files changed
t-miyak b139406
Merge branch 'main' into feature/support-list-of-pydantic-model-as-to…
t-miyak 2bea40c
fix: use original item data if its conversion fails
t-miyak fe575f7
test: update pytest
t-miyak 464de3b
fix: format in single quotes
t-miyak 2ecf493
fix: format in single quotes
t-miyak 7a4aa5c
Revert "fix: format in single quotes"
t-miyak efa45ac
Revert "fix: format in single quotes"
t-miyak 49f3987
Revert "test: update pytest"
t-miyak 7589882
Revert "fix: use original item data if its conversion fails"
t-miyak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -280,5 +280,121 @@ async def test_run_async_with_optional_pydantic_models(): | |
| assert result["theme"] == "dark" | ||
| assert result["notifications"] is True | ||
| assert result["preferences_type"] == "PreferencesModel" | ||
| assert result["preferences_type"] == "PreferencesModel" | ||
| assert result["preferences_type"] == "PreferencesModel" | ||
|
|
||
|
|
||
| def function_with_list_of_pydantic_models(users: list[UserModel]) -> dict: | ||
| """Function that takes a list of Pydantic models.""" | ||
| return { | ||
| "count": len(users), | ||
| "names": [user.name for user in users], | ||
| "ages": [user.age for user in users], | ||
| "types": [type(user).__name__ for user in users], | ||
| } | ||
|
|
||
|
|
||
| def function_with_optional_list_of_pydantic_models( | ||
| users: Optional[list[UserModel]] = None, | ||
| ) -> dict: | ||
| """Function that takes an optional list of Pydantic models.""" | ||
| if users is None: | ||
| return {"count": 0, "names": []} | ||
| return { | ||
| "count": len(users), | ||
| "names": [user.name for user in users], | ||
| } | ||
|
|
||
|
|
||
| def test_preprocess_args_with_list_of_dicts_to_pydantic_models(): | ||
| """Test _preprocess_args converts list of dicts to list of Pydantic models.""" | ||
| tool = FunctionTool(function_with_list_of_pydantic_models) | ||
|
|
||
| input_args = { | ||
| "users": [ | ||
| {"name": "Alice", "age": 30, "email": "[email protected]"}, | ||
| {"name": "Bob", "age": 25}, | ||
| {"name": "Charlie", "age": 35, "email": "[email protected]"}, | ||
| ] | ||
| } | ||
|
|
||
| processed_args = tool._preprocess_args(input_args) | ||
|
|
||
| # Check that the list of dicts was converted to a list of Pydantic models | ||
| assert "users" in processed_args | ||
| users = processed_args["users"] | ||
| assert isinstance(users, list) | ||
| assert len(users) == 3 | ||
|
|
||
| # Check each element is a Pydantic model with correct data | ||
| assert isinstance(users[0], UserModel) | ||
| assert users[0].name == "Alice" | ||
| assert users[0].age == 30 | ||
| assert users[0].email == "[email protected]" | ||
|
|
||
| assert isinstance(users[1], UserModel) | ||
| assert users[1].name == "Bob" | ||
| assert users[1].age == 25 | ||
| assert users[1].email is None | ||
|
|
||
| assert isinstance(users[2], UserModel) | ||
| assert users[2].name == "Charlie" | ||
| assert users[2].age == 35 | ||
| assert users[2].email == "[email protected]" | ||
|
|
||
|
|
||
| def test_preprocess_args_with_optional_list_of_pydantic_models_none(): | ||
| """Test _preprocess_args handles None for optional list parameter.""" | ||
| tool = FunctionTool(function_with_optional_list_of_pydantic_models) | ||
|
|
||
| input_args = {"users": None} | ||
|
|
||
| processed_args = tool._preprocess_args(input_args) | ||
|
|
||
| # Check that None is preserved | ||
| assert "users" in processed_args | ||
| assert processed_args["users"] is None | ||
|
|
||
|
|
||
| def test_preprocess_args_with_optional_list_of_pydantic_models_with_data(): | ||
| """Test _preprocess_args converts list for optional list parameter.""" | ||
| tool = FunctionTool(function_with_optional_list_of_pydantic_models) | ||
|
|
||
| input_args = { | ||
| "users": [ | ||
| {"name": "Alice", "age": 30}, | ||
| {"name": "Bob", "age": 25}, | ||
| ] | ||
| } | ||
|
|
||
| processed_args = tool._preprocess_args(input_args) | ||
|
|
||
| # Check conversion | ||
| assert "users" in processed_args | ||
| users = processed_args["users"] | ||
| assert len(users) == 2 | ||
| assert all(isinstance(user, UserModel) for user in users) | ||
| assert users[0].name == "Alice" | ||
| assert users[1].name == "Bob" | ||
|
|
||
|
|
||
| def test_preprocess_args_with_list_skips_invalid_items(): | ||
| """Test _preprocess_args skips items that fail validation.""" | ||
| tool = FunctionTool(function_with_list_of_pydantic_models) | ||
|
|
||
| input_args = { | ||
| "users": [ | ||
| {"name": "Alice", "age": 30}, | ||
| {"name": "Invalid"}, # Missing required 'age' field | ||
| {"name": "Bob", "age": 25}, | ||
| ] | ||
| } | ||
|
|
||
| processed_args = tool._preprocess_args(input_args) | ||
|
|
||
| # Check that invalid item was skipped | ||
| assert "users" in processed_args | ||
| users = processed_args["users"] | ||
| assert len(users) == 2 # Only 2 valid items | ||
| assert users[0].name == "Alice" | ||
| assert users[0].age == 30 | ||
| assert users[1].name == "Bob" | ||
| assert users[1].age == 25 | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While using
except Exceptionis robust, it's generally better practice to catch more specific exceptions. This prevents accidentally catching and silencing unrelated errors (likeKeyboardInterruptorSystemExit) and makes the code's intent clearer. In this case,pydantic.ValidationErroris the most expected exception during model validation.