Skip to content

Commit 5f3c0a9

Browse files
committed
feat: use agent tool loading logic
1 parent cb507dd commit 5f3c0a9

File tree

2 files changed

+169
-216
lines changed

2 files changed

+169
-216
lines changed

src/strands/experimental/agent_config.py

Lines changed: 32 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
33
This module provides utilities for creating agents from configuration files or dictionaries.
44
5-
Note: Configuration-based agent setup only works for tools that don't require code-based
6-
instantiation. For tools that need constructor arguments or complex setup, use the
5+
Note: Configuration-based agent setup only works for tools that don't require code-based
6+
instantiation. For tools that need constructor arguments or complex setup, use the
77
programmatic approach after creating the agent:
88
99
agent = config_to_agent("config.json")
1010
# Add tools that need code-based instantiation
1111
agent.process_tools([ToolWithConfigArg(HttpsConnection("localhost"))])
1212
"""
1313

14-
import importlib
1514
import json
16-
import os
1715
from pathlib import Path
16+
from typing import Any
1817

1918
import jsonschema
2019
from jsonschema import ValidationError
@@ -28,104 +27,61 @@
2827
"description": "Configuration schema for creating agents",
2928
"type": "object",
3029
"properties": {
31-
"name": {
32-
"description": "Name of the agent",
33-
"type": ["string", "null"],
34-
"default": None
35-
},
30+
"name": {"description": "Name of the agent", "type": ["string", "null"], "default": None},
3631
"model": {
3732
"description": "The model ID to use for this agent. If not specified, uses the default model.",
3833
"type": ["string", "null"],
39-
"default": None
34+
"default": None,
4035
},
4136
"prompt": {
4237
"description": "The system prompt for the agent. Provides high level context to the agent.",
4338
"type": ["string", "null"],
44-
"default": None
39+
"default": None,
4540
},
4641
"tools": {
47-
"description": "List of tools the agent can use. Can be file paths, Python module names, or @tool annotated functions in files.",
42+
"description": "List of tools the agent can use. Can be file paths, "
43+
"Python module names, or @tool annotated functions in files.",
4844
"type": "array",
49-
"items": {
50-
"type": "string"
51-
},
52-
"default": []
53-
}
45+
"items": {"type": "string"},
46+
"default": [],
47+
},
5448
},
55-
"additionalProperties": False
49+
"additionalProperties": False,
5650
}
5751

5852
# Pre-compile validator for better performance
5953
_VALIDATOR = jsonschema.Draft7Validator(AGENT_CONFIG_SCHEMA)
6054

6155

62-
def _is_filepath(tool_path: str) -> bool:
63-
"""Check if the tool string is a file path."""
64-
return os.path.exists(tool_path) or tool_path.endswith('.py')
65-
66-
67-
def _validate_tools(tools: list[str]) -> None:
68-
"""Validate that tools can be loaded as files or modules."""
69-
for tool in tools:
70-
if _is_filepath(tool):
71-
# File path - will be handled by Agent's tool loading
72-
continue
73-
74-
try:
75-
# Try to import as module
76-
importlib.import_module(tool)
77-
except ImportError:
78-
# Not a file and not a module - check if it might be a function reference
79-
if '.' in tool:
80-
module_path, func_name = tool.rsplit('.', 1)
81-
try:
82-
module = importlib.import_module(module_path)
83-
if not hasattr(module, func_name):
84-
raise ValueError(
85-
f"Function '{func_name}' not found in module '{module_path}'. "
86-
f"Ensure the function exists and is annotated with @tool."
87-
)
88-
except ImportError:
89-
raise ValueError(
90-
f"Module '{module_path}' not found. "
91-
f"Ensure the module exists and is importable, or use a valid file path."
92-
)
93-
else:
94-
raise ValueError(
95-
f"Tool '{tool}' not found. "
96-
f"The configured tool is not annotated with @tool, and is not a module or file."
97-
)
98-
99-
100-
def config_to_agent(config: str | dict[str, any], **kwargs) -> Agent:
56+
def config_to_agent(config: str | dict[str, Any], **kwargs: dict[str, Any]) -> Agent:
10157
"""Create an Agent from a configuration file or dictionary.
102-
58+
10359
This function supports tools that can be loaded declaratively (file paths, module names,
10460
or @tool annotated functions). For tools requiring code-based instantiation with constructor
10561
arguments, add them programmatically after creating the agent:
106-
62+
10763
agent = config_to_agent("config.json")
10864
agent.process_tools([ToolWithConfigArg(HttpsConnection("localhost"))])
109-
65+
11066
Args:
11167
config: Either a file path (with optional file:// prefix) or a configuration dictionary
11268
**kwargs: Additional keyword arguments to pass to the Agent constructor
113-
69+
11470
Returns:
11571
Agent: A configured Agent instance
116-
72+
11773
Raises:
11874
FileNotFoundError: If the configuration file doesn't exist
11975
json.JSONDecodeError: If the configuration file contains invalid JSON
12076
ValueError: If the configuration is invalid or tools cannot be loaded
121-
77+
12278
Examples:
12379
Create agent from file:
12480
>>> agent = config_to_agent("/path/to/config.json")
125-
81+
12682
Create agent from file with file:// prefix:
12783
>>> agent = config_to_agent("file:///path/to/config.json")
128-
84+
12985
Create agent from dictionary:
13086
>>> config = {"model": "anthropic.claude-3-5-sonnet-20241022-v2:0", "tools": ["calculator"]}
13187
>>> agent = config_to_agent(config)
@@ -134,53 +90,49 @@ def config_to_agent(config: str | dict[str, any], **kwargs) -> Agent:
13490
if isinstance(config, str):
13591
# Handle file path
13692
file_path = config
137-
93+
13894
# Remove file:// prefix if present
13995
if file_path.startswith("file://"):
14096
file_path = file_path[7:]
141-
97+
14298
# Load JSON from file
14399
config_path = Path(file_path)
144100
if not config_path.exists():
145101
raise FileNotFoundError(f"Configuration file not found: {file_path}")
146-
147-
with open(config_path, 'r') as f:
102+
103+
with open(config_path, "r") as f:
148104
config_dict = json.load(f)
149105
elif isinstance(config, dict):
150106
config_dict = config.copy()
151107
else:
152108
raise ValueError("Config must be a file path string or dictionary")
153-
109+
154110
# Validate configuration against schema
155111
try:
156112
_VALIDATOR.validate(config_dict)
157113
except ValidationError as e:
158114
# Provide more detailed error message
159115
error_path = " -> ".join(str(p) for p in e.absolute_path) if e.absolute_path else "root"
160116
raise ValueError(f"Configuration validation error at {error_path}: {e.message}") from e
161-
162-
# Validate tools can be loaded
163-
if "tools" in config_dict and config_dict["tools"]:
164-
_validate_tools(config_dict["tools"])
165-
117+
166118
# Prepare Agent constructor arguments
167119
agent_kwargs = {}
168-
120+
169121
# Map configuration keys to Agent constructor parameters
170122
config_mapping = {
171123
"model": "model",
172-
"prompt": "system_prompt",
124+
"prompt": "system_prompt",
173125
"tools": "tools",
174126
"name": "name",
175127
}
176-
128+
177129
# Only include non-None values from config
178130
for config_key, agent_param in config_mapping.items():
179131
if config_key in config_dict and config_dict[config_key] is not None:
180132
agent_kwargs[agent_param] = config_dict[config_key]
181-
133+
182134
# Override with any additional kwargs provided
183135
agent_kwargs.update(kwargs)
184-
136+
185137
# Create and return Agent
186138
return Agent(**agent_kwargs)

0 commit comments

Comments
 (0)