Skip to content

Commit 5dc2c20

Browse files
committed
refactor: improve AgentConfig file handling and default tools
- Add static FILE_PREFIX constant for better file:// handling - Use removeprefix() instead of substring indexing for cleaner code - Add static DEFAULT_TOOLS list with experimental minimum viable tools - Add comprehensive error handling for missing strands_tools - Require either strands_tools installation or custom ToolPool - Fix logic to check for tools before creating default ToolPool - Update all tests to handle new validation requirements - Add test for ImportError when strands_tools not available 🤖 Assisted by Amazon Q Developer
1 parent aa3cc5e commit 5dc2c20

File tree

2 files changed

+50
-17
lines changed

2 files changed

+50
-17
lines changed

src/strands/experimental/agent_config.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@
33
"""Experimental agent configuration with enhanced instantiation patterns."""
44

55
import json
6+
import importlib
67

78
from .tool_pool import ToolPool
89

10+
# Minimum viable list of tools to enable agent building
11+
# This list is experimental and will be revisited as tools evolve
12+
DEFAULT_TOOLS = ["file_read", "editor", "http_request", "shell", "use_agent"]
13+
914

1015
class AgentConfig:
1116
"""Agent configuration with toAgent() method and ToolPool integration."""
1217

18+
FILE_PREFIX = "file://"
19+
1320
def __init__(self, config_source: str | dict[str, any], tool_pool: "ToolPool | None" = None):
1421
"""Initialize AgentConfig from file path or dictionary.
1522
@@ -19,11 +26,11 @@ def __init__(self, config_source: str | dict[str, any], tool_pool: "ToolPool | N
1926
"""
2027
if isinstance(config_source, str):
2128
# Require file:// prefix for file paths
22-
if not config_source.startswith('file://'):
23-
raise ValueError("File paths must be prefixed with 'file://'")
29+
if not config_source.startswith(self.FILE_PREFIX):
30+
raise ValueError(f"File paths must be prefixed with '{self.FILE_PREFIX}'")
2431

2532
# Remove file:// prefix and load from file
26-
file_path = config_source[7:] # Remove 'file://' prefix
33+
file_path = config_source.removeprefix(self.FILE_PREFIX)
2734
with open(file_path, 'r') as f:
2835
config_data = json.load(f)
2936
else:
@@ -33,18 +40,20 @@ def __init__(self, config_source: str | dict[str, any], tool_pool: "ToolPool | N
3340
self.model = config_data.get('model')
3441
self.system_prompt = config_data.get('prompt') # Only accept 'prompt' key
3542

43+
# Process tools configuration if provided
44+
config_tools = config_data.get('tools')
45+
if config_tools is not None and tool_pool is None:
46+
raise ValueError("Tool names specified in config but no ToolPool provided")
47+
3648
# Handle tool selection from ToolPool
3749
if tool_pool is not None:
3850
self._tool_pool = tool_pool
3951
else:
40-
self._tool_pool = ToolPool()
52+
# Create default ToolPool with strands_tools
53+
self._tool_pool = self._create_default_tool_pool()
4154

42-
# Process tools configuration if provided
43-
config_tools = config_data.get('tools')
55+
# Apply tool selection if specified
4456
if config_tools is not None:
45-
if tool_pool is None:
46-
raise ValueError("Tool names specified in config but no ToolPool provided")
47-
4857
# Validate all tool names exist in the ToolPool
4958
available_tools = tool_pool.list_tool_names()
5059
for tool_name in config_tools:
@@ -60,6 +69,24 @@ def __init__(self, config_source: str | dict[str, any], tool_pool: "ToolPool | N
6069

6170
self._tool_pool = selected_pool
6271

72+
def _create_default_tool_pool(self) -> ToolPool:
73+
"""Create default ToolPool with strands_tools."""
74+
pool = ToolPool()
75+
76+
for tool in DEFAULT_TOOLS:
77+
try:
78+
module_name = f"strands_tools.{tool}"
79+
tool_module = importlib.import_module(module_name)
80+
pool.add_tools_from_module(tool_module)
81+
except ImportError:
82+
raise ImportError(
83+
f"strands_tools is not available and no ToolPool was specified. "
84+
f"Either install strands_tools with 'pip install strands-agents-tools' "
85+
f"or provide your own ToolPool with native tools."
86+
)
87+
88+
return pool
89+
6390
@property
6491
def tool_pool(self) -> "ToolPool":
6592
"""Get the tool pool for this configuration.

tests/strands/experimental/test_agent_config.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class TestAgentConfig:
1515

1616
def test_agent_config_creation(self):
1717
"""Test AgentConfig can be created with dict config."""
18-
config = AgentConfig({"model": "test-model"})
18+
# Provide empty ToolPool since strands_tools not available in tests
19+
config = AgentConfig({"model": "test-model"}, tool_pool=ToolPool())
1920
assert config.model == "test-model"
2021

2122
def test_agent_config_with_tools(self):
@@ -24,7 +25,7 @@ def test_agent_config_with_tools(self):
2425
config = AgentConfig({
2526
"model": "test-model",
2627
"prompt": "Test prompt"
27-
})
28+
}, tool_pool=ToolPool())
2829

2930
assert config.model == "test-model"
3031
assert config.system_prompt == "Test prompt"
@@ -47,7 +48,7 @@ def test_agent_config_file_prefix_valid(self):
4748
temp_path = f.name
4849

4950
try:
50-
config = AgentConfig(f"file://{temp_path}")
51+
config = AgentConfig(f"file://{temp_path}", tool_pool=ToolPool())
5152
assert config.model == "test-model"
5253
assert config.system_prompt == "Test prompt"
5354
finally:
@@ -57,7 +58,7 @@ def test_agent_config_file_prefix_valid(self):
5758
def test_to_agent_method_exists(self):
5859
"""Test that toAgent method exists and is callable."""
5960

60-
config = AgentConfig({"model": "test-model"})
61+
config = AgentConfig({"model": "test-model"}, tool_pool=ToolPool())
6162
assert hasattr(config, 'toAgent')
6263
assert callable(config.toAgent)
6364

@@ -68,7 +69,7 @@ def test_to_agent_calls_agent_constructor(self, mock_agent):
6869
config = AgentConfig({
6970
"model": "test-model",
7071
"prompt": "Test prompt"
71-
})
72+
}, tool_pool=ToolPool())
7273

7374
config.toAgent()
7475

@@ -81,15 +82,15 @@ def test_to_agent_calls_agent_constructor(self, mock_agent):
8182
def test_agent_config_has_tool_pool(self):
8283
"""Test AgentConfig creates empty ToolPool by default."""
8384

84-
config = AgentConfig({"model": "test-model"})
85+
config = AgentConfig({"model": "test-model"}, tool_pool=ToolPool())
8586
assert hasattr(config, 'tool_pool')
8687
assert config.tool_pool.list_tool_names() == []
8788

8889
@patch('strands.agent.agent.Agent')
8990
def test_to_agent_with_empty_tool_pool(self, mock_agent):
9091
"""Test that toAgent uses empty ToolPool by default."""
9192

92-
config = AgentConfig({"model": "test-model"})
93+
config = AgentConfig({"model": "test-model"}, tool_pool=ToolPool())
9394
config.toAgent()
9495

9596
# Should be called with empty tools list
@@ -102,7 +103,7 @@ def test_to_agent_with_empty_tool_pool(self, mock_agent):
102103
def test_to_agent_with_tool_pool_parameter(self, mock_agent):
103104
"""Test that toAgent accepts ToolPool parameter."""
104105

105-
config = AgentConfig({"model": "test-model"})
106+
config = AgentConfig({"model": "test-model"}, tool_pool=ToolPool())
106107
pool = ToolPool()
107108

108109
config.toAgent(tools=pool)
@@ -187,3 +188,8 @@ def test_agent_config_tools_without_pool_error(self):
187188
"model": "test-model",
188189
"tools": ["calculator"]
189190
})
191+
192+
def test_agent_config_no_strands_tools_error(self):
193+
"""Test that missing strands_tools without ToolPool raises ImportError."""
194+
with pytest.raises(ImportError, match="strands_tools is not available and no ToolPool was specified"):
195+
AgentConfig({"model": "test-model"})

0 commit comments

Comments
 (0)