Skip to content

Commit ba9f05c

Browse files
committed
fix: Address comments
1 parent d0c611a commit ba9f05c

File tree

2 files changed

+176
-161
lines changed

2 files changed

+176
-161
lines changed

src/strands/experimental/agent_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121

2222

2323
class AgentConfig:
24-
"""Agent configuration with to_agent() method and ToolRegistry integration.
24+
"""Load Agents from configuration.
25+
26+
AgentConfig has support for loading agents from JSON or inline-dictionaries, selecting tools from a
27+
pre-configured list, or a list of tools provided by a ToolRegistry.
2528
2629
Example config.json:
2730
{

tests/strands/experimental/test_agent_config.py

Lines changed: 172 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -10,199 +10,211 @@
1010
from strands.types.tools import AgentTool
1111

1212

13-
class TestAgentConfig:
14-
"""Test experimental AgentConfig functionality."""
13+
class MockTool(AgentTool):
14+
def __init__(self, name):
15+
self._name = name
1516

16-
class MockTool(AgentTool):
17-
def __init__(self, name):
18-
self._name = name
17+
@property
18+
def tool_name(self):
19+
return self._name
1920

20-
@property
21-
def tool_name(self):
22-
return self._name
21+
@property
22+
def tool_type(self):
23+
return "mock"
2324

24-
@property
25-
def tool_type(self):
26-
return "mock"
25+
@property
26+
def tool_spec(self):
27+
return {"name": self._name, "type": "mock"}
2728

28-
@property
29-
def tool_spec(self):
30-
return {"name": self._name, "type": "mock"}
29+
@property
30+
def _is_dynamic(self):
31+
return False
3132

32-
@property
33-
def _is_dynamic(self):
34-
return False
33+
def stream(self, input_data, context):
34+
return iter([])
3535

36-
def stream(self, input_data, context):
37-
return iter([])
3836

39-
def test_agent_config_creation(self):
40-
"""Test AgentConfig can be created with dict config."""
41-
# Provide empty ToolRegistry since strands_tools not available in tests
42-
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
43-
assert config.model == "test-model"
37+
def test_agent_config_creation():
38+
"""Test AgentConfig can be created with dict config."""
39+
# Provide empty ToolRegistry since strands_tools not available in tests
40+
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
41+
assert config.model == "test-model"
42+
43+
44+
def test_agent_config_with_tools():
45+
"""Test AgentConfig with basic configuration."""
46+
47+
config = AgentConfig({"model": "test-model", "prompt": "Test prompt"}, tool_registry=ToolRegistry())
48+
49+
assert config.model == "test-model"
50+
assert config.system_prompt == "Test prompt"
51+
52+
53+
def test_agent_config_file_prefix_required():
54+
"""Test that file paths must have file:// prefix."""
55+
56+
with pytest.raises(ValueError, match="File paths must be prefixed with 'file://'"):
57+
AgentConfig("/path/to/config.json")
4458

45-
def test_agent_config_with_tools(self):
46-
"""Test AgentConfig with basic configuration."""
4759

48-
config = AgentConfig({"model": "test-model", "prompt": "Test prompt"}, tool_registry=ToolRegistry())
60+
def test_agent_config_file_prefix_valid():
61+
"""Test that file:// prefix is properly handled."""
62+
import json
63+
import tempfile
4964

65+
# Create a temporary config file
66+
config_data = {"model": "test-model", "prompt": "Test prompt"}
67+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=True) as f:
68+
json.dump(config_data, f)
69+
f.flush() # Ensure data is written to disk
70+
71+
config = AgentConfig(f"file://{f.name}", tool_registry=ToolRegistry())
5072
assert config.model == "test-model"
5173
assert config.system_prompt == "Test prompt"
5274

53-
def test_agent_config_file_prefix_required(self):
54-
"""Test that file paths must have file:// prefix."""
5575

56-
with pytest.raises(ValueError, match="File paths must be prefixed with 'file://'"):
57-
AgentConfig("/path/to/config.json")
76+
@patch("strands.agent.agent.Agent")
77+
def test_to_agent_calls_agent_constructor(mock_agent):
78+
"""Test that to_agent calls Agent constructor with correct parameters."""
5879

59-
def test_agent_config_file_prefix_valid(self):
60-
"""Test that file:// prefix is properly handled."""
61-
import json
62-
import tempfile
80+
config = AgentConfig({"model": "test-model", "prompt": "Test prompt"}, tool_registry=ToolRegistry())
6381

64-
# Create a temporary config file
65-
config_data = {"model": "test-model", "prompt": "Test prompt"}
66-
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=True) as f:
67-
json.dump(config_data, f)
68-
f.flush() # Ensure data is written to disk
82+
config.to_agent()
6983

70-
config = AgentConfig(f"file://{f.name}", tool_registry=ToolRegistry())
71-
assert config.model == "test-model"
72-
assert config.system_prompt == "Test prompt"
84+
mock_agent.assert_called_once_with(model="test-model", tools=[], system_prompt="Test prompt")
7385

74-
@patch("strands.agent.agent.Agent")
75-
def test_to_agent_calls_agent_constructor(self, mock_agent):
76-
"""Test that to_agent calls Agent constructor with correct parameters."""
7786

78-
config = AgentConfig({"model": "test-model", "prompt": "Test prompt"}, tool_registry=ToolRegistry())
87+
def test_agent_config_has_tool_registry():
88+
"""Test AgentConfig creates ToolRegistry and tracks configured tools."""
7989

80-
config.to_agent()
90+
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
91+
assert hasattr(config, "tool_registry")
92+
assert hasattr(config, "configured_tools")
93+
assert config.configured_tools == [] # No tools configured
8194

82-
mock_agent.assert_called_once_with(model="test-model", tools=[], system_prompt="Test prompt")
8395

84-
def test_agent_config_has_tool_registry(self):
85-
"""Test AgentConfig creates ToolRegistry and tracks configured tools."""
96+
@patch("strands.agent.agent.Agent")
97+
def test_to_agent_with_empty_tool_registry(mock_agent):
98+
"""Test that to_agent uses empty ToolRegistry by default."""
8699

87-
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
88-
assert hasattr(config, "tool_registry")
89-
assert hasattr(config, "configured_tools")
90-
assert config.configured_tools == [] # No tools configured
100+
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
101+
config.to_agent()
91102

92-
@patch("strands.agent.agent.Agent")
93-
def test_to_agent_with_empty_tool_registry(self, mock_agent):
94-
"""Test that to_agent uses empty ToolRegistry by default."""
103+
# Should be called with empty tools list
104+
mock_agent.assert_called_once()
105+
call_args = mock_agent.call_args[1]
106+
assert "tools" in call_args
107+
assert call_args["tools"] == []
95108

96-
config = AgentConfig({"model": "test-model"}, tool_registry=ToolRegistry())
97-
config.to_agent()
98109

99-
# Should be called with empty tools list
100-
mock_agent.assert_called_once()
101-
call_args = mock_agent.call_args[1]
102-
assert "tools" in call_args
103-
assert call_args["tools"] == []
110+
def test_agent_config_with_tool_registry_constructor():
111+
"""Test AgentConfig with ToolRegistry parameter in constructor."""
112+
# Create mock tools
113+
tool1 = MockTool("calculator")
114+
tool2 = MockTool("web_search")
104115

105-
def test_agent_config_with_tool_registry_constructor(self):
106-
"""Test AgentConfig with ToolRegistry parameter in constructor."""
107-
# Create mock tools
108-
tool1 = self.MockTool("calculator")
109-
tool2 = self.MockTool("web_search")
116+
# Create ToolRegistry with tools
117+
tool_registry = ToolRegistry()
118+
tool_registry.process_tools([tool1, tool2])
110119

111-
# Create ToolRegistry with tools
112-
tool_registry = ToolRegistry()
113-
tool_registry.process_tools([tool1, tool2])
120+
# Create config with tool selection
121+
config = AgentConfig(
122+
{"model": "test-model", "prompt": "Test prompt", "tools": ["calculator"]}, tool_registry=tool_registry
123+
)
114124

115-
# Create config with tool selection
116-
config = AgentConfig(
117-
{"model": "test-model", "prompt": "Test prompt", "tools": ["calculator"]}, tool_registry=tool_registry
118-
)
125+
# Should have selected only calculator
126+
assert len(config.configured_tools) == 1
127+
assert config.configured_tools[0].tool_name == "calculator"
119128

120-
# Should have selected only calculator
121-
assert len(config.configured_tools) == 1
122-
assert config.configured_tools[0].tool_name == "calculator"
123-
124-
def test_agent_config_tool_validation_error(self):
125-
"""Test that invalid tool names raise validation error."""
126-
tool1 = self.MockTool("calculator")
127-
tool_registry = ToolRegistry()
128-
tool_registry.process_tools([tool1])
129-
130-
# Should raise error for unknown tool
131-
with pytest.raises(
132-
ValueError,
133-
match=re.escape(
134-
"Tool(s) '{'unknown_tool'}' not found in ToolRegistry. Available tools: dict_keys(['calculator'])"
135-
),
136-
):
137-
AgentConfig({"model": "test-model", "tools": ["unknown_tool"]}, tool_registry=tool_registry)
138-
139-
def test_agent_config_tools_without_tool_registry_error(self):
140-
"""Test that config can load tools from default ToolRegistry when strands_tools is available."""
141-
142-
config = AgentConfig({"model": "test-model", "tools": ["file_read"]})
143-
assert len(config.configured_tools) == 1
144-
assert config.configured_tools[0].tool_name == "file_read"
145-
146-
@patch("importlib.import_module")
147-
def test_agent_config_import_error(self, mock_import):
148-
"""Test that import error for strands_tools is handled correctly."""
149-
mock_import.side_effect = ImportError("No module named 'strands_tools'")
150-
151-
with pytest.raises(ImportError, match="strands_tools is not available and no ToolRegistry was specified"):
152-
AgentConfig({"model": "test-model"})
153-
154-
def test_agent_config_skip_missing_tools(self):
155-
"""Test that missing strands_tools can be skipped with flag."""
156-
# Should not raise error when flag is False and no ToolRegistry provided
157-
config = AgentConfig({"model": "test-model"}, raise_exception_on_missing_tool=False)
158-
assert config.model == "test-model"
159-
assert config.configured_tools == [] # No tools loaded since strands_tools missing
160129

161-
def test_agent_config_skip_missing_tools_with_selection(self):
162-
"""Test that missing tools in ToolRegistry can be skipped with flag."""
130+
def test_agent_config_tool_validation_error():
131+
"""Test that invalid tool names raise validation error."""
132+
tool1 = MockTool("calculator")
133+
tool_registry = ToolRegistry()
134+
tool_registry.process_tools([tool1])
135+
136+
# Should raise error for unknown tool
137+
with pytest.raises(
138+
ValueError,
139+
match=re.escape(
140+
"Tool(s) '{'unknown_tool'}' not found in ToolRegistry. Available tools: dict_keys(['calculator'])"
141+
),
142+
):
143+
AgentConfig({"model": "test-model", "tools": ["unknown_tool"]}, tool_registry=tool_registry)
144+
145+
146+
def test_agent_config_tools_without_tool_registry_error():
147+
"""Test that config can load tools from default ToolRegistry when strands_tools is available."""
148+
149+
config = AgentConfig({"model": "test-model", "tools": ["file_read"]})
150+
assert len(config.configured_tools) == 1
151+
assert config.configured_tools[0].tool_name == "file_read"
163152

164-
existing_tool = self.MockTool("existing_tool")
165-
custom_tool_registry = ToolRegistry()
166-
custom_tool_registry.process_tools([existing_tool])
167153

168-
# Should skip missing tool when flag is False
169-
config = AgentConfig(
170-
{
171-
"model": "test-model",
172-
"tools": ["existing_tool", "missing_tool"], # One exists, one doesn't
173-
},
154+
@patch("importlib.import_module")
155+
def test_agent_config_import_error(mock_import):
156+
"""Test that import error for strands_tools is handled correctly."""
157+
mock_import.side_effect = ImportError("No module named 'strands_tools'")
158+
159+
with pytest.raises(ImportError, match="strands_tools is not available and no ToolRegistry was specified"):
160+
AgentConfig({"model": "test-model"})
161+
162+
163+
def test_agent_config_skip_missing_tools():
164+
"""Test that missing strands_tools can be skipped with flag."""
165+
# Should not raise error when flag is False and no ToolRegistry provided
166+
config = AgentConfig({"model": "test-model"}, raise_exception_on_missing_tool=False)
167+
assert config.model == "test-model"
168+
assert config.configured_tools == [] # No tools loaded since strands_tools missing
169+
170+
171+
def test_agent_config_skip_missing_tools_with_selection():
172+
"""Test that missing tools in ToolRegistry can be skipped with flag."""
173+
174+
existing_tool = MockTool("existing_tool")
175+
custom_tool_registry = ToolRegistry()
176+
custom_tool_registry.process_tools([existing_tool])
177+
178+
# Should skip missing tool when flag is False
179+
config = AgentConfig(
180+
{
181+
"model": "test-model",
182+
"tools": ["existing_tool", "missing_tool"], # One exists, one doesn't
183+
},
184+
tool_registry=custom_tool_registry,
185+
raise_exception_on_missing_tool=False,
186+
)
187+
188+
# Should only have the existing tool
189+
assert len(config.configured_tools) == 1
190+
assert config.configured_tools[0].tool_name == "existing_tool"
191+
192+
193+
def test_agent_config_missing_tool_validation_with_flag_true():
194+
"""Test that missing tools still raise error when flag is True."""
195+
196+
existing_tool = MockTool("existing_tool")
197+
custom_tool_registry = ToolRegistry()
198+
custom_tool_registry.process_tools([existing_tool])
199+
200+
# Should raise error for missing tool when flag is True (default)
201+
with pytest.raises(
202+
ValueError,
203+
match=re.escape(
204+
"Tool(s) '{'missing_tool'}' not found in ToolRegistry. Available tools: dict_keys(['existing_tool'])"
205+
),
206+
):
207+
AgentConfig(
208+
{"model": "test-model", "tools": ["missing_tool"]},
174209
tool_registry=custom_tool_registry,
175-
raise_exception_on_missing_tool=False,
210+
raise_exception_on_missing_tool=True,
176211
)
177212

178-
# Should only have the existing tool
179-
assert len(config.configured_tools) == 1
180-
assert config.configured_tools[0].tool_name == "existing_tool"
181-
182-
def test_agent_config_missing_tool_validation_with_flag_true(self):
183-
"""Test that missing tools still raise error when flag is True."""
184-
185-
existing_tool = self.MockTool("existing_tool")
186-
custom_tool_registry = ToolRegistry()
187-
custom_tool_registry.process_tools([existing_tool])
188-
189-
# Should raise error for missing tool when flag is True (default)
190-
with pytest.raises(
191-
ValueError,
192-
match=re.escape(
193-
"Tool(s) '{'missing_tool'}' not found in ToolRegistry. Available tools: dict_keys(['existing_tool'])"
194-
),
195-
):
196-
AgentConfig(
197-
{"model": "test-model", "tools": ["missing_tool"]},
198-
tool_registry=custom_tool_registry,
199-
raise_exception_on_missing_tool=True,
200-
)
201-
202-
def test_agent_config_loads_from_default_tools_without_tool_registry(self):
203-
"""Test that config can load tools from default strands_tools without explicit tool registry."""
204-
205-
config = AgentConfig({"model": "test-model", "tools": ["file_read"]})
206-
# Verify the tool was loaded from the default tool registry
207-
assert len(config.configured_tools) == 1
208-
assert config.configured_tools[0].tool_name == "file_read"
213+
214+
def test_agent_config_loads_from_default_tools_without_tool_registry():
215+
"""Test that config can load tools from default strands_tools without explicit tool registry."""
216+
217+
config = AgentConfig({"model": "test-model", "tools": ["file_read"]})
218+
# Verify the tool was loaded from the default tool registry
219+
assert len(config.configured_tools) == 1
220+
assert config.configured_tools[0].tool_name == "file_read"

0 commit comments

Comments
 (0)