Skip to content

Conversation

mr-lee
Copy link
Member

@mr-lee mr-lee commented Sep 26, 2025

Resuming #865

Description

  • Add AgentConfig class for loading configuration from JSON files or dicts
  • Support model, prompt, and tools configuration options
  • Update tool registry to resolve tool names from strands_tools package
  • Maintain backward compatibility with existing Agent constructor
  • Add comprehensive tests for configuration loading and validation

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

from strands.experimental import AgentConfig

config = AgentConfig({"model": "test-model", "prompt": "Test prompt", "tools": ["file_read", "shell"]})
agent = config.to_agent() # kwargs are passed to the agent init

To configure the set of tools available to the config, you can supply your own tool registry:

from strands import tool
from strands.experimental import AgentConfig
from strands.tools.registry import ToolRegistry

@tool
def add(x: int, y: int) -> int:
    return x + y

tool_registry = ToolRegistry()
tool_registry.process_tools([add])

# Create config with tool selection
config = AgentConfig(
    {"model": "test-model", "prompt": "Test prompt", "tools": ["add"]}, tool_registry=tool_registry
)
agent = config.to_agent() # kwargs are passed to the agent init

You can also load a config from a file:

from strands.experimental import AgentConfig

config = AgentConfig("file://path/to/local/config) # Config gets loaded into dict
agent = config.to_agent() # kwargs are passed to the agent init

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

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

- 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
Copy link
Member

@awsarron awsarron left a 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:

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
- 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
@mr-lee mr-lee force-pushed the feature/agent-config-squashed branch from 27bb82f to c2d1baa Compare September 26, 2025 22:40
- 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
Copy link
Member

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

@Unshure Unshure self-assigned this Sep 29, 2025

# Map configuration keys to Agent constructor parameters
config_mapping = {
"model": "model",
Copy link
Member

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)
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants