|
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