generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 697
feat: add public tool_spec setter #1822
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c7d02b3
feat: add public tool_spec setter to DecoratedFunctionTool and Python…
1d1d444
fix: add validation to tool_spec setter
a7865bd
refactor: loop over required fields in tool_spec setter validation
5ebf44d
fix: drop json key validation from tool_spec setter
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
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 |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| """Tests for tool_spec setter on DecoratedFunctionTool and PythonAgentTool.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from strands.tools.decorator import tool | ||
| from strands.tools.tools import PythonAgentTool | ||
| from strands.types.tools import ToolSpec | ||
|
|
||
|
|
||
| class TestDecoratedFunctionToolSpecSetter: | ||
| """Tests for DecoratedFunctionTool.tool_spec setter.""" | ||
|
|
||
| def test_set_tool_spec_replaces_spec(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| new_spec: ToolSpec = { | ||
| "name": "my_tool", | ||
| "description": "Updated tool", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": { | ||
| "query": {"type": "string", "description": "The query"}, | ||
| "limit": {"type": "integer", "description": "Max results"}, | ||
| }, | ||
| "required": ["query"], | ||
| } | ||
| }, | ||
| } | ||
| my_tool.tool_spec = new_spec | ||
| assert my_tool.tool_spec is new_spec | ||
| assert "limit" in my_tool.tool_spec["inputSchema"]["json"]["properties"] | ||
|
|
||
| def test_set_tool_spec_persists_across_reads(self): | ||
| @tool | ||
| def another_tool(x: int) -> int: | ||
| """Another test tool.""" | ||
| return x | ||
|
|
||
| new_spec: ToolSpec = { | ||
| "name": "another_tool", | ||
| "description": "Modified", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": {"x": {"type": "integer"}, "y": {"type": "integer"}}, | ||
| "required": ["x"], | ||
| } | ||
| }, | ||
| } | ||
| another_tool.tool_spec = new_spec | ||
| assert another_tool.tool_spec["description"] == "Modified" | ||
| assert another_tool.tool_spec["description"] == "Modified" | ||
|
|
||
| def test_add_property_via_setter(self): | ||
| @tool | ||
| def dynamic_tool(base: str) -> str: | ||
| """A dynamic tool.""" | ||
| return base | ||
|
|
||
| spec = dynamic_tool.tool_spec.copy() | ||
| spec["inputSchema"] = dynamic_tool.tool_spec["inputSchema"].copy() | ||
| spec["inputSchema"]["json"] = dynamic_tool.tool_spec["inputSchema"]["json"].copy() | ||
| spec["inputSchema"]["json"]["properties"] = dynamic_tool.tool_spec["inputSchema"]["json"]["properties"].copy() | ||
| spec["inputSchema"]["json"]["properties"]["extra"] = { | ||
| "type": "string", | ||
| "description": "Extra param", | ||
| } | ||
| dynamic_tool.tool_spec = spec | ||
| assert "extra" in dynamic_tool.tool_spec["inputSchema"]["json"]["properties"] | ||
|
|
||
| def test_set_tool_spec_rejects_name_change(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| bad_spec: ToolSpec = { | ||
| "name": "wrong_name", | ||
| "description": "Updated tool", | ||
| "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, | ||
| } | ||
| with pytest.raises(ValueError, match="cannot change tool name via tool_spec"): | ||
| my_tool.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_rejects_missing_description(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| bad_spec: ToolSpec = { | ||
| "name": "my_tool", | ||
| "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, | ||
| } | ||
| with pytest.raises(ValueError, match="tool_spec must contain 'description'"): | ||
| my_tool.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_rejects_missing_input_schema(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| bad_spec: ToolSpec = { | ||
| "name": "my_tool", | ||
| "description": "Updated tool", | ||
| } | ||
| with pytest.raises(ValueError, match="tool_spec must contain 'inputSchema'"): | ||
| my_tool.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_accepts_bare_input_schema(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| bare_spec: ToolSpec = { | ||
| "name": "my_tool", | ||
| "description": "Bare schema", | ||
| "inputSchema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}, | ||
| } | ||
| my_tool.tool_spec = bare_spec | ||
| assert my_tool.tool_spec is bare_spec | ||
|
|
||
| def test_set_tool_spec_accepts_valid_spec(self): | ||
| @tool | ||
| def my_tool(query: str) -> str: | ||
| """A test tool.""" | ||
| return query | ||
|
|
||
| valid_spec: ToolSpec = { | ||
| "name": "my_tool", | ||
| "description": "A valid updated spec", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": {"query": {"type": "string"}}, | ||
| "required": ["query"], | ||
| } | ||
| }, | ||
| } | ||
| my_tool.tool_spec = valid_spec | ||
| assert my_tool.tool_spec is valid_spec | ||
|
|
||
|
|
||
| class TestPythonAgentToolSpecSetter: | ||
| """Tests for PythonAgentTool.tool_spec setter.""" | ||
|
|
||
| def _make_tool(self) -> PythonAgentTool: | ||
| def func(tool_use, **kwargs): | ||
| return {"status": "success", "content": [{"text": "ok"}], "toolUseId": tool_use["toolUseId"]} | ||
|
|
||
| spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "A test tool", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": {"input": {"type": "string"}}, | ||
| "required": ["input"], | ||
| } | ||
| }, | ||
| } | ||
| return PythonAgentTool("test_tool", spec, func) | ||
|
|
||
| def test_set_tool_spec(self): | ||
| t = self._make_tool() | ||
| new_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "Updated", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": { | ||
| "input": {"type": "string"}, | ||
| "extra": {"type": "integer"}, | ||
| }, | ||
| "required": ["input"], | ||
| } | ||
| }, | ||
| } | ||
| t.tool_spec = new_spec | ||
| assert t.tool_spec is new_spec | ||
| assert "extra" in t.tool_spec["inputSchema"]["json"]["properties"] | ||
|
|
||
| def test_set_tool_spec_persists(self): | ||
| t = self._make_tool() | ||
| new_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "Persisted", | ||
| "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, | ||
| } | ||
| t.tool_spec = new_spec | ||
| assert t.tool_spec["description"] == "Persisted" | ||
| assert t.tool_spec["description"] == "Persisted" | ||
|
|
||
| def test_set_tool_spec_rejects_name_change(self): | ||
| t = self._make_tool() | ||
| bad_spec: ToolSpec = { | ||
| "name": "wrong_name", | ||
| "description": "Updated", | ||
| "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, | ||
| } | ||
| with pytest.raises(ValueError, match="cannot change tool name via tool_spec"): | ||
| t.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_rejects_missing_description(self): | ||
| t = self._make_tool() | ||
| bad_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, | ||
| } | ||
| with pytest.raises(ValueError, match="tool_spec must contain 'description'"): | ||
| t.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_rejects_missing_input_schema(self): | ||
| t = self._make_tool() | ||
| bad_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "Updated", | ||
| } | ||
| with pytest.raises(ValueError, match="tool_spec must contain 'inputSchema'"): | ||
| t.tool_spec = bad_spec | ||
|
|
||
| def test_set_tool_spec_accepts_bare_input_schema(self): | ||
| t = self._make_tool() | ||
| bare_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "Bare schema", | ||
| "inputSchema": {"type": "object", "properties": {"input": {"type": "string"}}, "required": ["input"]}, | ||
| } | ||
| t.tool_spec = bare_spec | ||
| assert t.tool_spec is bare_spec | ||
|
|
||
| def test_set_tool_spec_accepts_valid_spec(self): | ||
| t = self._make_tool() | ||
| valid_spec: ToolSpec = { | ||
| "name": "test_tool", | ||
| "description": "A valid updated spec", | ||
| "inputSchema": { | ||
| "json": { | ||
| "type": "object", | ||
| "properties": {"input": {"type": "string"}}, | ||
| "required": ["input"], | ||
| } | ||
| }, | ||
| } | ||
| t.tool_spec = valid_spec | ||
| assert t.tool_spec is valid_spec |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.