Skip to content

Commit f372666

Browse files
committed
refactor: replace AgentConfig class with config_to_agent function
BREAKING CHANGE: Replace class-based AgentConfig with function-based config_to_agent - Replace AgentConfig class with config_to_agent function for simpler interface - Remove ToolRegistry dependency - let Agent handle tool loading internally - Remove DEFAULT_TOOLS concept and raise_exception_on_missing_tool parameter - Support both file paths and dictionary inputs with file:// prefix handling - Only pass non-None config values to Agent constructor (use Agent defaults) - Update experimental module exports to expose config_to_agent function - Rewrite all tests to use new function-based interface - Simplify tool handling by delegating to Agent class New interface: from strands.experimental import config_to_agent agent = config_to_agent('/path/to/config.json') Previous interface (removed): from strands.experimental.agent_config import AgentConfig config = AgentConfig('/path/to/config.json') agent = config.to_agent() 🤖 Assisted by Amazon Q Developer
1 parent f4ed508 commit f372666

File tree

3 files changed

+178
-403
lines changed

3 files changed

+178
-403
lines changed

src/strands/experimental/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
33
This module implements experimental features that are subject to change in future revisions without notice.
44
"""
5+
6+
from .agent_config import config_to_agent
7+
8+
__all__ = ["config_to_agent"]
Lines changed: 84 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,193 +1,87 @@
1-
"""Experimental agent configuration with enhanced instantiation patterns."""
2-
3-
import importlib
4-
import json
5-
from typing import TYPE_CHECKING, Any
6-
7-
from ..tools.registry import ToolRegistry
8-
9-
if TYPE_CHECKING:
10-
# Import here to avoid circular imports:
11-
# experimental/agent_config.py -> agent.agent -> event_loop.event_loop ->
12-
# experimental.hooks -> experimental.__init__.py -> AgentConfig
13-
from ..agent.agent import Agent
14-
15-
# File prefix for configuration file paths
16-
FILE_PREFIX = "file://"
17-
18-
# Minimum viable list of tools to enable agent building
19-
# This list is experimental and will be revisited as tools evolve
20-
DEFAULT_TOOLS = ["file_read", "editor", "http_request", "shell", "use_agent"]
1+
"""Experimental agent configuration utilities.
212
3+
This module provides utilities for creating agents from configuration files or dictionaries.
4+
"""
225

23-
class AgentConfig:
24-
"""Agent configuration with to_agent() method and ToolRegistry integration.
25-
26-
Example config.json:
27-
{
28-
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
29-
"prompt": "You are a helpful assistant",
30-
"tools": ["file_read", "editor"]
31-
}
6+
import json
7+
from pathlib import Path
8+
from typing import Any, Dict, Union
9+
10+
from ..agent import Agent
11+
12+
13+
def config_to_agent(config: Union[str, Dict[str, Any]], **kwargs) -> Agent:
14+
"""Create an Agent from a configuration file or dictionary.
15+
16+
Args:
17+
config: Either a file path (with optional file:// prefix) or a configuration dictionary
18+
**kwargs: Additional keyword arguments to pass to the Agent constructor
19+
20+
Returns:
21+
Agent: A configured Agent instance
22+
23+
Raises:
24+
FileNotFoundError: If the configuration file doesn't exist
25+
json.JSONDecodeError: If the configuration file contains invalid JSON
26+
ValueError: If the configuration is invalid
27+
28+
Examples:
29+
Create agent from file:
30+
>>> agent = config_to_agent("/path/to/config.json")
31+
32+
Create agent from file with file:// prefix:
33+
>>> agent = config_to_agent("file:///path/to/config.json")
34+
35+
Create agent from dictionary:
36+
>>> config = {"model": "anthropic.claude-3-5-sonnet-20241022-v2:0", "tools": ["calculator"]}
37+
>>> agent = config_to_agent(config)
3238
"""
33-
34-
def __init__(
35-
self,
36-
config_source: str | dict[str, Any],
37-
tool_registry: ToolRegistry | None = None,
38-
raise_exception_on_missing_tool: bool = True,
39-
):
40-
"""Initialize AgentConfig from file path or dictionary.
41-
42-
Args:
43-
config_source: Path to JSON config file (must start with 'file://') or config dictionary
44-
tool_registry: Optional ToolRegistry to select tools from when 'tools' is specified in config
45-
raise_exception_on_missing_tool: If False, skip missing tools instead of raising ImportError
46-
47-
Example:
48-
# Dictionary config
49-
config = AgentConfig({
50-
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
51-
"prompt": "You are a helpful assistant",
52-
"tools": ["file_read", "editor"]
53-
})
54-
55-
# File config
56-
config = AgentConfig("file://config.json")
57-
"""
58-
if isinstance(config_source, str):
59-
# Require file:// prefix for file paths
60-
if not config_source.startswith(FILE_PREFIX):
61-
raise ValueError(f"File paths must be prefixed with '{FILE_PREFIX}'")
62-
63-
# Remove file:// prefix and load from file
64-
file_path = config_source.removeprefix(FILE_PREFIX)
65-
with open(file_path, "r") as f:
66-
config_data = json.load(f)
67-
else:
68-
# Use dictionary directly
69-
config_data = config_source
70-
71-
self.model = config_data.get("model")
72-
self.system_prompt = config_data.get("prompt") # Only accept 'prompt' key
73-
self._raise_exception_on_missing_tool = raise_exception_on_missing_tool
74-
75-
# Handle tool selection from ToolRegistry
76-
if tool_registry is not None:
77-
self._tool_registry = tool_registry
78-
else:
79-
# Create default ToolRegistry with strands_tools
80-
self._tool_registry = self._create_default_tool_registry()
81-
82-
# Process tools configuration if provided
83-
config_tools = config_data.get("tools")
84-
85-
# Track configured tools separately from full tool pool
86-
self._configured_tools = []
87-
88-
# Apply tool selection if specified
89-
if config_tools is not None:
90-
# Validate all tool names exist in the ToolRegistry
91-
available_tools = self._tool_registry.registry.keys()
92-
93-
missing_tools = set(config_tools).difference(available_tools)
94-
if missing_tools and self._raise_exception_on_missing_tool:
95-
raise ValueError(
96-
f"Tool(s) '{missing_tools}' not found in ToolRegistry. Available tools: {available_tools}"
97-
)
98-
99-
for tool_name in config_tools:
100-
if tool_name in self._tool_registry.registry:
101-
tool = self._tool_registry.registry[tool_name]
102-
self._configured_tools.append(tool)
103-
# If no tools specified in config, use no tools (empty list)
104-
105-
def _create_default_tool_registry(self) -> ToolRegistry:
106-
"""Create default ToolRegistry with strands_tools."""
107-
tool_registry = ToolRegistry()
108-
109-
try:
110-
tool_modules = [importlib.import_module(f"strands_tools.{tool}") for tool in DEFAULT_TOOLS]
111-
tool_registry.process_tools(tool_modules)
112-
except ImportError as e:
113-
if self._raise_exception_on_missing_tool:
114-
raise ImportError(
115-
"strands_tools is not available and no ToolRegistry was specified. "
116-
"Either install strands_tools with 'pip install strands-agents-tools' "
117-
"or provide your own ToolRegistry with your own tools."
118-
) from e
119-
120-
return tool_registry
121-
122-
@property
123-
def tool_registry(self) -> ToolRegistry:
124-
"""Get the full ToolRegistry (superset of all available tools).
125-
126-
Returns:
127-
ToolRegistry instance containing all available tools
128-
"""
129-
return self._tool_registry
130-
131-
@property
132-
def configured_tools(self) -> list:
133-
"""Get the configured tools (subset selected for this agent).
134-
135-
Returns:
136-
List of tools configured for this agent
137-
"""
138-
return self._configured_tools
139-
140-
def to_agent(self, **kwargs: Any) -> "Agent":
141-
"""Create an Agent instance from this configuration.
142-
143-
Args:
144-
**kwargs: Additional parameters to override config values.
145-
Supports all Agent constructor parameters.
146-
147-
Returns:
148-
Configured Agent instance
149-
150-
Example:
151-
# Using default tools from strands_tools
152-
config = AgentConfig({
153-
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
154-
"prompt": "You are a helpful assistant",
155-
"tools": ["file_read"]
156-
})
157-
agent = config.to_agent()
158-
response = agent("Read the contents of README.md")
159-
160-
# Using custom ToolRegistry
161-
from strands import tool
162-
163-
@tool
164-
def custom_tool(input: str) -> str:
165-
return f"Custom: {input}"
166-
167-
custom_tool_registry = ToolRegistry()
168-
custom_tool_registry.process_tools([custom_tool])
169-
config = AgentConfig({
170-
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
171-
"prompt": "You are a custom assistant",
172-
"tools": ["custom_tool"]
173-
}, tool_registry=custom_tool_registry)
174-
agent = config.to_agent()
175-
"""
176-
# Import at runtime since TYPE_CHECKING import is not available during execution
177-
from ..agent.agent import Agent
178-
179-
# Start with config values
180-
agent_params = {}
181-
182-
if self.model is not None:
183-
agent_params["model"] = self.model
184-
if self.system_prompt is not None:
185-
agent_params["system_prompt"] = self.system_prompt
186-
187-
# Use configured tools (subset of tool pool)
188-
agent_params["tools"] = self._configured_tools
189-
190-
# Override with any other provided kwargs
191-
agent_params.update(kwargs)
192-
193-
return Agent(**agent_params)
39+
# Parse configuration
40+
if isinstance(config, str):
41+
# Handle file path
42+
file_path = config
43+
44+
# Remove file:// prefix if present
45+
if file_path.startswith("file://"):
46+
file_path = file_path[7:]
47+
48+
# Load JSON from file
49+
config_path = Path(file_path)
50+
if not config_path.exists():
51+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
52+
53+
with open(config_path, 'r') as f:
54+
config_dict = json.load(f)
55+
elif isinstance(config, dict):
56+
config_dict = config.copy()
57+
else:
58+
raise ValueError("Config must be a file path string or dictionary")
59+
60+
# Prepare Agent constructor arguments
61+
agent_kwargs = {}
62+
63+
# Map configuration keys to Agent constructor parameters
64+
config_mapping = {
65+
"model": "model",
66+
"prompt": "system_prompt",
67+
"tools": "tools",
68+
"name": "name",
69+
"agent_id": "agent_id",
70+
"session_manager": "session_manager",
71+
"conversation_manager": "conversation_manager",
72+
"hooks": "hooks",
73+
"callback_handler": "callback_handler",
74+
"state": "state",
75+
"trace_attributes": "trace_attributes",
76+
}
77+
78+
# Only include non-None values from config
79+
for config_key, agent_param in config_mapping.items():
80+
if config_key in config_dict and config_dict[config_key] is not None:
81+
agent_kwargs[agent_param] = config_dict[config_key]
82+
83+
# Override with any additional kwargs provided
84+
agent_kwargs.update(kwargs)
85+
86+
# Create and return Agent
87+
return Agent(**agent_kwargs)

0 commit comments

Comments
 (0)