Skip to content

Commit a051bef

Browse files
committed
feat: add tool validation and clarify limitations
- 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
1 parent 383b18b commit a051bef

File tree

3 files changed

+99
-40
lines changed

3 files changed

+99
-40
lines changed

src/strands/experimental/agent_config.py

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,112 @@
11
"""Experimental agent configuration utilities.
22
33
This module provides utilities for creating agents from configuration files or dictionaries.
4+
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
7+
programmatic approach after creating the agent:
8+
9+
agent = config_to_agent("config.json")
10+
# Add tools that need code-based instantiation
11+
agent.process_tools([ToolWithConfigArg(HttpsConnection("localhost"))])
412
"""
513

14+
import importlib
615
import json
16+
import os
717
from pathlib import Path
818

919
import jsonschema
1020
from jsonschema import ValidationError
1121

1222
from ..agent import Agent
1323

24+
# JSON Schema for agent configuration
25+
AGENT_CONFIG_SCHEMA = {
26+
"$schema": "http://json-schema.org/draft-07/schema#",
27+
"title": "Agent Configuration",
28+
"description": "Configuration schema for creating agents",
29+
"type": "object",
30+
"properties": {
31+
"name": {
32+
"description": "Name of the agent",
33+
"type": ["string", "null"],
34+
"default": None
35+
},
36+
"model": {
37+
"description": "The model ID to use for this agent. If not specified, uses the default model.",
38+
"type": ["string", "null"],
39+
"default": None
40+
},
41+
"prompt": {
42+
"description": "The system prompt for the agent. Provides high level context to the agent.",
43+
"type": ["string", "null"],
44+
"default": None
45+
},
46+
"tools": {
47+
"description": "List of tools the agent can use. Can be file paths, Python module names, or @tool annotated functions in files.",
48+
"type": "array",
49+
"items": {
50+
"type": "string"
51+
},
52+
"default": []
53+
}
54+
},
55+
"additionalProperties": False
56+
}
1457

15-
def _load_schema() -> dict:
16-
"""Load the agent configuration schema from file."""
17-
schema_path = Path(__file__).parent / "schemas" / "agent-config-v1.json"
18-
with open(schema_path, 'r') as f:
19-
return json.load(f)
58+
# Pre-compile validator for better performance
59+
_VALIDATOR = jsonschema.Draft7Validator(AGENT_CONFIG_SCHEMA)
2060

2161

22-
# Pre-compile validator for better performance
23-
_VALIDATOR = jsonschema.Draft7Validator(_load_schema())
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"Tool '{tool}' not found. The configured tool is not annotated with @tool, "
86+
f"and is not a module or file. To properly import this tool, you must annotate it with @tool."
87+
)
88+
except ImportError:
89+
raise ValueError(
90+
f"Tool '{tool}' not found. The configured tool is not annotated with @tool, "
91+
f"and is not a module or file. To properly import this tool, you must annotate it with @tool."
92+
)
93+
else:
94+
raise ValueError(
95+
f"Tool '{tool}' not found. The configured tool is not annotated with @tool, "
96+
f"and is not a module or file. To properly import this tool, you must annotate it with @tool."
97+
)
2498

2599

26100
def config_to_agent(config: str | dict[str, any], **kwargs) -> Agent:
27101
"""Create an Agent from a configuration file or dictionary.
28102
103+
This function supports tools that can be loaded declaratively (file paths, module names,
104+
or @tool annotated functions). For tools requiring code-based instantiation with constructor
105+
arguments, add them programmatically after creating the agent:
106+
107+
agent = config_to_agent("config.json")
108+
agent.process_tools([ToolWithConfigArg(HttpsConnection("localhost"))])
109+
29110
Args:
30111
config: Either a file path (with optional file:// prefix) or a configuration dictionary
31112
**kwargs: Additional keyword arguments to pass to the Agent constructor
@@ -36,7 +117,7 @@ def config_to_agent(config: str | dict[str, any], **kwargs) -> Agent:
36117
Raises:
37118
FileNotFoundError: If the configuration file doesn't exist
38119
json.JSONDecodeError: If the configuration file contains invalid JSON
39-
ValueError: If the configuration is invalid
120+
ValueError: If the configuration is invalid or tools cannot be loaded
40121
41122
Examples:
42123
Create agent from file:
@@ -78,6 +159,10 @@ def config_to_agent(config: str | dict[str, any], **kwargs) -> Agent:
78159
error_path = " -> ".join(str(p) for p in e.absolute_path) if e.absolute_path else "root"
79160
raise ValueError(f"Configuration validation error at {error_path}: {e.message}") from e
80161

162+
# Validate tools can be loaded
163+
if "tools" in config_dict and config_dict["tools"]:
164+
_validate_tools(config_dict["tools"])
165+
81166
# Prepare Agent constructor arguments
82167
agent_kwargs = {}
83168

src/strands/experimental/schemas/agent-config-v1.json

Lines changed: 0 additions & 32 deletions
This file was deleted.

tests/strands/experimental/test_agent_config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ def test_config_to_agent_validation_error_invalid_tool_item(self):
130130
config = {"model": "test-model", "tools": ["valid-tool", 123]}
131131
with pytest.raises(ValueError, match="Configuration validation error"):
132132
config_to_agent(config)
133+
134+
def test_config_to_agent_validation_error_invalid_tool(self):
135+
"""Test that invalid tools raise helpful error messages."""
136+
config = {"model": "test-model", "tools": ["nonexistent_tool"]}
137+
with pytest.raises(ValueError, match="The configured tool is not annotated with @tool"):
138+
config_to_agent(config)

0 commit comments

Comments
 (0)