-
Notifications
You must be signed in to change notification settings - Fork 420
feat: add experimental AgentConfig with comprehensive tool management #935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add AgentConfig class for declarative agent configuration via JSON/dict - Support file:// prefix for loading configurations from JSON files - Implement ToolRegistry integration with automatic default tool loading - Add raise_exception_on_missing_tool parameter for flexible error handling - Support tool selection from registry via tool names in config - Add comprehensive test coverage for all configuration scenarios - Move hook events from experimental to production with updated names - Add OpenAI model provider enhancements and Gemini model improvements - Update event loop and tool executors to use production hook events 🤖 Assisted by Amazon Q Developer
- Reset experimental/__init__.py to not import AgentConfig by default - This may resolve import issues in CI environments - AgentConfig can still be imported directly from strands.experimental.agent_config 🤖 Assisted by Amazon Q Developer
- Reset pyproject.toml to not include strands-agents-tools as test dependency - Tests handle missing strands_tools gracefully with mocking - This should resolve CI dependency issues 🤖 Assisted by Amazon Q Developer
- Remove test_agent_config_loads_from_default_tools_without_tool_registry - This test assumes strands_tools is available which causes CI failures - Other tests adequately cover AgentConfig functionality 🤖 Assisted by Amazon Q Developer
- Add back test_agent_config_tools_without_tool_registry_error with mocking - Add back test_agent_config_loads_from_default_tools_without_tool_registry with mocking - Mock _create_default_tool_registry to avoid dependency on strands_tools - Add tool import for creating mock tools in tests - All 15 tests now pass without external dependencies 🤖 Assisted by Amazon Q Developer
- Use platform-specific tempfile handling in test_agent_config_file_prefix_valid - Use mkstemp() with explicit cleanup on Windows for better permission handling - Keep NamedTemporaryFile on non-Windows platforms for simplicity - Should resolve permission errors on Windows GitHub runners 🤖 Assisted by Amazon Q Developer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proposed interface during experimental:
from strands.experimental import config_to_agent
agent = config_to_agent("/path/to/amazing-agent.json")
agent("do the thing")
potential interface for non-experimental in future releases:
from strands import config_to_agent
agent = config_to_agent("/path/to/amazing-agent.json")
agent("do the thing")
example amazing-agent.json
definition of tools:
{
// ...
tools: ["strands_tools.file_read", "my_app.tools.cake_tool", "/path/to/another_tool.py"]
}
strands supports loading tools from python modules and filepaths:
sdk-python/src/strands/tools/registry.py
Lines 57 to 105 in eef11cc
def add_tool(tool: Any) -> None: | |
# Case 1: String file path | |
if isinstance(tool, str): | |
# Extract tool name from path | |
tool_name = os.path.basename(tool).split(".")[0] | |
self.load_tool_from_filepath(tool_name=tool_name, tool_path=tool) | |
tool_names.append(tool_name) | |
# Case 2: Dictionary with name and path | |
elif isinstance(tool, dict) and "name" in tool and "path" in tool: | |
self.load_tool_from_filepath(tool_name=tool["name"], tool_path=tool["path"]) | |
tool_names.append(tool["name"]) | |
# Case 3: Dictionary with path only | |
elif isinstance(tool, dict) and "path" in tool: | |
tool_name = os.path.basename(tool["path"]).split(".")[0] | |
self.load_tool_from_filepath(tool_name=tool_name, tool_path=tool["path"]) | |
tool_names.append(tool_name) | |
# Case 4: Imported Python module | |
elif hasattr(tool, "__file__") and inspect.ismodule(tool): | |
# Get the module file path | |
module_path = tool.__file__ | |
# Extract the tool name from the module name | |
tool_name = tool.__name__.split(".")[-1] | |
# Check for TOOL_SPEC in module to validate it's a Strands tool | |
if hasattr(tool, "TOOL_SPEC") and hasattr(tool, tool_name) and module_path: | |
self.load_tool_from_filepath(tool_name=tool_name, tool_path=module_path) | |
tool_names.append(tool_name) | |
else: | |
function_tools = self._scan_module_for_tools(tool) | |
for function_tool in function_tools: | |
self.register_tool(function_tool) | |
tool_names.append(function_tool.tool_name) | |
if not function_tools: | |
logger.warning("tool_name=<%s>, module_path=<%s> | invalid agent tool", tool_name, module_path) | |
# Case 5: AgentTools (which also covers @tool) | |
elif isinstance(tool, AgentTool): | |
self.register_tool(tool) | |
tool_names.append(tool.tool_name) | |
# Case 6: Nested iterable (list, tuple, etc.) - add each sub-tool | |
elif isinstance(tool, Iterable) and not isinstance(tool, (str, bytes, bytearray)): | |
for t in tool: | |
add_tool(t) | |
else: | |
logger.warning("tool=<%s> | unrecognized tool specification", tool) |
for the initial implementation in this PR we could do something like:
def config_to_agent(filepath: str) -> Agent:
# pseudo code
parsed_config = JSON.loads(filepath)
tools_list = []
for tool in parsed_config["tools"]:
tool_arg = tool
if not is_filepath(tool):
try:
tool_arg = importlib.import_module(tool)
except ImportError as e:
# handle tool not a file and not a module
tools_list.append(tool_arg)
return Agent(tools=tools_list)
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
832b290
to
f372666
Compare
- Remove support for advanced Agent parameters in config_to_agent - Only support: model, prompt, tools, name in configuration - Advanced parameters can still be passed via kwargs - Remove agent_id test and update function mapping - Keep interface simple and focused on basic agent configuration 🤖 Assisted by Amazon Q Developer
27bb82f
to
c2d1baa
Compare
- Replace Union[str, Dict[str, Any]] with str | dict[str, any] - Remove typing module imports - Use modern Python 3.10+ native typing syntax 🤖 Assisted by Amazon Q Developer
- Use NamedTemporaryFile with delete=True for automatic cleanup - Remove manual os.unlink call and try/finally block - Keep file operation within single context manager scope - Add f.flush() to ensure data is written before reading 🤖 Assisted by Amazon Q Developer
- Add jsonschema dependency for configuration validation - Implement JSON schema based on supported configuration keys - Provide detailed validation error messages with field paths - Add validation tests for invalid fields, types, and tool items - Support null values for optional fields (model, prompt, name) - Reject additional properties not in the schema - All 14 tests passing including new validation tests 🤖 Assisted by Amazon Q Developer
- Extract agent configuration schema to schemas/agent-config-v1.json - Add _load_schema() function to load schema from file at runtime - Improve code readability by separating schema from Python logic - Enable schema reuse by other tools and documentation - Maintain all existing validation functionality and tests 🤖 Assisted by Amazon Q Developer
- Create Draft7Validator instance at module level for better performance - Avoid loading and compiling schema on every validation call - Schema is loaded once at import time and validator is reused - Maintains all existing validation functionality and error messages - Standard best practice for jsonschema validation performance 🤖 Assisted by Amazon Q Developer
- Move JSON schema back to inline variable for simplicity - Add comprehensive tool validation with helpful error messages - Validate tools can be loaded as files, modules, or @tool functions - Add clear documentation about code-based instantiation limitations - Update module docstring and function comments with usage patterns - Add test for tool validation error messages - Remove schemas directory (no longer needed) 🤖 Assisted by Amazon Q Developer
- Fix error message for missing modules to be more descriptive - Remove redundant 'to properly import this tool' text from error messages - Add specific error messages for missing modules vs missing functions - Add unit tests for each error case: - Invalid tool (not file/module/@tool) - Missing module (module doesn't exist) - Missing function (function not found in existing module) - All 17 tests passing with better error coverage 🤖 Assisted by Amazon Q Developer
- Change error message from 'Tool X not found' to 'Module X not found' - More accurate since we're trying to import it as a module at this point - Maintains existing test compatibility and error handling logic 🤖 Assisted by Amazon Q Developer
- Revert previous change from 'Module X not found' back to 'Tool X not found' - Keep original error message format as requested 🤖 Assisted by Amazon Q Developer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add tests for loading module tools, as well as specific functions from a module? You can add the tools to the tests/fixtures
directory
|
||
# Map configuration keys to Agent constructor parameters | ||
config_mapping = { | ||
"model": "model", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocker: can we support mcp_servers, a2a_servers, load_tool_from_directory, system_prompt (not as prompt given below), provider, session id, s3 session support etc here?
agent_kwargs.update(kwargs) | ||
|
||
# Create and return Agent | ||
return Agent(**agent_kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we support multi-agents as array of agents instead of just support one agent?
Resuming #865
Description
Usage example
For basic usage with the default set of tools available to choose from the config: file_read, editor, http_request, use_agent, and shell
To configure the set of tools available to the config, you can supply your own tool registry:
You can also load a config from a file:
Documentation PR
strands-agents/docs#252
Type of Change
New feature
Testing
How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli
hatch run prepare
Checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.