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