Skip to content

Commit fee9a3f

Browse files
committed
Complete cache removal from ConfigLoader system
Implemented comprehensive cache removal plan to simplify ConfigLoader architecture: Core Implementation Changes: - Removed cache dictionaries from all ConfigLoaders (AgentConfigLoader, GraphConfigLoader, SwarmConfigLoader, ToolConfigLoader) - Removed cache_key parameters from all load_* methods - Removed cache check and storage logic throughout system - Removed clear_cache() and get_available_* methods from all loaders - Simplified module loading by removing _loaded_modules cache Method Signature Updates: - load_agent(config) - removed cache_key parameter - load_graph(config) - removed cache_key parameter - load_swarm(config) - removed cache_key parameter - Updated all cross-loader dependencies to use new signatures Test Updates: - Replaced cache-specific tests with object independence tests - Added test_multiple_loads_create_independent_objects across all loaders - Removed 7 cache-related test methods - All 113 tests passing with new architecture Benefits Achieved: - Cleaner code: Removed ~200+ lines of cache-related complexity - Predictable behavior: Each load creates fresh objects - Easier testing: No cache state management needed - Reduced memory usage: No indefinite object retention - Simpler API: No cache management methods or parameters - Immediate configuration change reflection Breaking Changes: - cache_key parameter removed from all load_* methods - clear_cache() methods removed from all loaders - get_available_* methods removed from all loaders Migration: Users can implement application-level caching if needed. Performance: Slight increase in load time, significant reduction in memory usage and complexity. Ready for production deployment with simplified, maintainable architecture.
1 parent e746f9b commit fee9a3f

File tree

8 files changed

+88
-253
lines changed

8 files changed

+88
-253
lines changed

src/strands/experimental/config_loader/agent/agent_config_loader.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ def __init__(self, tool_config_loader: Optional["ToolConfigLoader"] = None):
5252
If not provided, will be imported and created when needed.
5353
"""
5454
self._tool_config_loader = tool_config_loader
55-
self._agent_cache: Dict[str, Agent] = {}
5655
self.schema_registry = SchemaRegistry()
5756
self._global_schemas_loaded = False
5857
self._structured_output_defaults: Dict[str, Any] = {}
@@ -72,12 +71,11 @@ def _get_tool_config_loader(self) -> "ToolConfigLoader":
7271
self._tool_config_loader = ToolConfigLoader()
7372
return self._tool_config_loader
7473

75-
def load_agent(self, config: Dict[str, Any], cache_key: Optional[str] = None) -> Agent:
74+
def load_agent(self, config: Dict[str, Any]) -> Agent:
7675
"""Load an Agent from a dictionary configuration.
7776
7877
Args:
7978
config: Dictionary containing agent configuration with top-level 'agent' key.
80-
cache_key: Optional key for caching the loaded agent.
8179
8280
Returns:
8381
Agent instance configured according to the provided dictionary.
@@ -94,11 +92,6 @@ def load_agent(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
9492
if not isinstance(agent_config, dict):
9593
raise ValueError("The 'agent' configuration must be a dictionary")
9694

97-
# Check cache first
98-
if cache_key and cache_key in self._agent_cache:
99-
logger.debug("agent_cache_key=<%s> | found in cache", cache_key)
100-
return self._agent_cache[cache_key]
101-
10295
# Load global schemas if present and not already loaded
10396
if not self._global_schemas_loaded and "schemas" in config:
10497
self._load_global_schemas(config["schemas"])
@@ -180,11 +173,6 @@ def load_agent(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
180173
if "structured_output" in agent_config:
181174
self._configure_agent_structured_output(agent, agent_config["structured_output"])
182175

183-
# Cache the agent if cache key provided
184-
if cache_key:
185-
self._agent_cache[cache_key] = agent
186-
logger.debug("agent_cache_key=<%s> | cached agent", cache_key)
187-
188176
return agent
189177

190178
def serialize_agent(self, agent: Agent) -> Dict[str, Any]:
@@ -435,19 +423,6 @@ def _load_session_manager(self, sm_config: Optional[Dict[str, Any]]) -> Optional
435423
logger.warning("Session manager loading from configuration is not yet implemented")
436424
return None
437425

438-
def clear_cache(self) -> None:
439-
"""Clear the internal agent cache."""
440-
self._agent_cache.clear()
441-
logger.debug("agent cache cleared")
442-
443-
def get_available_agents(self) -> List[str]:
444-
"""Get list of cached agent keys.
445-
446-
Returns:
447-
List of cached agent keys.
448-
"""
449-
return list(self._agent_cache.keys())
450-
451426
def _load_global_schemas(self, schemas_config: List[Dict[str, Any]]) -> None:
452427
"""Load global schema registry from configuration.
453428

src/strands/experimental/config_loader/graph/graph_config_loader.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ def __init__(
5252
self._agent_loader = agent_loader
5353
self._swarm_loader = swarm_loader
5454
self._condition_registry = ConditionRegistry()
55-
self._graph_cache: Dict[str, Graph] = {}
5655

5756
def _get_agent_config_loader(self) -> "AgentConfigLoader":
5857
"""Get or create an AgentConfigLoader instance.
@@ -84,12 +83,11 @@ def _get_swarm_config_loader(self) -> "SwarmConfigLoader":
8483
self._swarm_loader = SwarmConfigLoader()
8584
return self._swarm_loader
8685

87-
def load_graph(self, config: Dict[str, Any], cache_key: Optional[str] = None) -> Graph:
88-
"""Load a Graph from YAML configuration (loaded as dictionary).
86+
def load_graph(self, config: Dict[str, Any]) -> Graph:
87+
"""Load a Graph from configuration dictionary.
8988
9089
Args:
9190
config: Dictionary containing graph configuration with top-level 'graph' key.
92-
cache_key: Optional key for caching the loaded graph.
9391
9492
Returns:
9593
Graph instance configured according to the provided dictionary.
@@ -106,11 +104,6 @@ def load_graph(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
106104
if not isinstance(graph_config, dict):
107105
raise ValueError("The 'graph' configuration must be a dictionary")
108106

109-
# Check cache first
110-
if cache_key and cache_key in self._graph_cache:
111-
logger.debug("graph_cache_key=<%s> | found in cache", cache_key)
112-
return self._graph_cache[cache_key]
113-
114107
# Validate configuration structure
115108
self._validate_config(graph_config)
116109

@@ -129,11 +122,6 @@ def load_graph(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
129122
# Create graph
130123
graph = Graph(nodes=nodes, edges=edges, entry_points=entry_points, **graph_params)
131124

132-
# Cache the graph if cache key provided
133-
if cache_key:
134-
self._graph_cache[cache_key] = graph
135-
logger.debug("graph_cache_key=<%s> | cached graph", cache_key)
136-
137125
return graph
138126

139127
def serialize_graph(self, graph: Graph) -> Dict[str, Any]:
@@ -183,19 +171,6 @@ def serialize_graph(self, graph: Graph) -> Dict[str, Any]:
183171

184172
return {"graph": graph_config}
185173

186-
def clear_cache(self) -> None:
187-
"""Clear the internal graph cache."""
188-
self._graph_cache.clear()
189-
logger.debug("graph cache cleared")
190-
191-
def get_available_graphs(self) -> List[str]:
192-
"""Get list of cached graph keys.
193-
194-
Returns:
195-
List of cached graph keys.
196-
"""
197-
return list(self._graph_cache.keys())
198-
199174
def _load_nodes(self, nodes_config: List[Dict[str, Any]]) -> Dict[str, GraphNode]:
200175
"""Load graph nodes from configuration.
201176

src/strands/experimental/config_loader/swarm/swarm_config_loader.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ def __init__(self, agent_config_loader: Optional["AgentConfigLoader"] = None):
4040
If not provided, will be imported and created when needed.
4141
"""
4242
self._agent_config_loader = agent_config_loader
43-
self._swarm_cache: Dict[str, Swarm] = {}
4443

4544
def _get_agent_config_loader(self) -> "AgentConfigLoader":
4645
"""Get or create an AgentConfigLoader instance.
@@ -57,12 +56,11 @@ def _get_agent_config_loader(self) -> "AgentConfigLoader":
5756
self._agent_config_loader = AgentConfigLoader()
5857
return self._agent_config_loader
5958

60-
def load_swarm(self, config: Dict[str, Any], cache_key: Optional[str] = None) -> Swarm:
61-
"""Load a Swarm from YAML configuration (loaded as dictionary).
59+
def load_swarm(self, config: Dict[str, Any]) -> Swarm:
60+
"""Load a Swarm from configuration dictionary.
6261
6362
Args:
6463
config: Dictionary containing swarm configuration with top-level 'swarm' key.
65-
cache_key: Optional key for caching the loaded swarm.
6664
6765
Returns:
6866
Swarm instance configured according to the provided dictionary.
@@ -79,11 +77,6 @@ def load_swarm(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
7977
if not isinstance(swarm_config, dict):
8078
raise ValueError("The 'swarm' configuration must be a dictionary")
8179

82-
# Check cache first
83-
if cache_key and cache_key in self._swarm_cache:
84-
logger.debug("swarm_cache_key=<%s> | found in cache", cache_key)
85-
return self._swarm_cache[cache_key]
86-
8780
# Validate configuration structure
8881
self._validate_config(swarm_config)
8982

@@ -101,11 +94,6 @@ def load_swarm(self, config: Dict[str, Any], cache_key: Optional[str] = None) ->
10194
# Create swarm
10295
swarm = Swarm(nodes=agents, **swarm_params)
10396

104-
# Cache the swarm if cache key provided
105-
if cache_key:
106-
self._swarm_cache[cache_key] = swarm
107-
logger.debug("swarm_cache_key=<%s> | cached swarm", cache_key)
108-
10997
return swarm
11098

11199
def serialize_swarm(self, swarm: Swarm) -> Dict[str, Any]:
@@ -223,14 +211,12 @@ def load_agents(self, agents_config: List[Dict[str, Any]]) -> List[Agent]:
223211
if "model" not in agent_config:
224212
raise ValueError(f"Agent configuration at index {i} must include 'model' field")
225213

226-
# Use agent name as cache key for individual agent caching
227214
agent_name = agent_config["name"]
228-
cache_key = f"agent:{agent_name}"
229215

230216
try:
231217
# Wrap the agent config in the required top-level 'agent' key
232218
wrapped_agent_config = {"agent": agent_config}
233-
agent = agent_loader.load_agent(wrapped_agent_config, cache_key=cache_key)
219+
agent = agent_loader.load_agent(wrapped_agent_config)
234220
agents.append(agent)
235221
logger.debug("agent_name=<%s> | loaded agent for swarm", agent_name)
236222
except Exception as e:
@@ -329,16 +315,3 @@ def _validate_config(self, config: Dict[str, Any]) -> None:
329315
value = config[param_name]
330316
if not isinstance(value, (int, float)):
331317
raise ValueError(f"{param_name} must be a number, got: {type(value)}")
332-
333-
def clear_cache(self) -> None:
334-
"""Clear the internal swarm cache."""
335-
self._swarm_cache.clear()
336-
logger.debug("swarm cache cleared")
337-
338-
def get_available_swarms(self) -> List[str]:
339-
"""Get list of cached swarm keys.
340-
341-
Returns:
342-
List of cached swarm keys.
343-
"""
344-
return list(self._swarm_cache.keys())

src/strands/experimental/config_loader/tools/tool_config_loader.py

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,6 @@ def __init__(self, registry: Optional[ToolRegistry] = None):
680680
If not provided, a new registry will be created.
681681
"""
682682
self._registry = registry or ToolRegistry()
683-
self._loaded_modules: Dict[str, Any] = {}
684-
self._tool_cache: Dict[str, AgentTool] = {}
685683
self._agent_config_loader: Optional["AgentConfigLoader"] = None
686684
self._swarm_config_loader: Optional["SwarmConfigLoader"] = None
687685
self._graph_config_loader: Optional["GraphConfigLoader"] = None
@@ -856,11 +854,6 @@ def _load_swarm_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
856854
if not swarm_config:
857855
raise ValueError("Swarm tool configuration must include 'swarm' field")
858856

859-
# Check cache first
860-
cache_key = f"swarm:{tool_name}:{hash(str(sorted(tool_config.items())))}"
861-
if cache_key in self._tool_cache:
862-
return self._tool_cache[cache_key]
863-
864857
try:
865858
# Load the swarm using SwarmConfigLoader
866859
# Wrap the swarm config in the required top-level 'swarm' key
@@ -878,10 +871,6 @@ def _load_swarm_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
878871
entry_agent=entry_agent,
879872
)
880873

881-
# Cache the result
882-
self._tool_cache[cache_key] = swarm_tool
883-
logger.debug("swarm_tool=<%s> | loaded and cached", tool_name)
884-
885874
return swarm_tool
886875

887876
except Exception as e:
@@ -1036,11 +1025,6 @@ def _load_graph_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
10361025
if not graph_config:
10371026
raise ValueError("Graph tool configuration must include 'graph' field")
10381027

1039-
# Check cache first
1040-
cache_key = f"graph:{tool_name}:{hash(str(sorted(tool_config.items())))}"
1041-
if cache_key in self._tool_cache:
1042-
return self._tool_cache[cache_key]
1043-
10441028
try:
10451029
# Load the graph using GraphConfigLoader
10461030
graph_loader = self._get_graph_config_loader()
@@ -1062,10 +1046,6 @@ def _load_graph_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
10621046
entry_point=entry_point,
10631047
)
10641048

1065-
# Cache the result
1066-
self._tool_cache[cache_key] = graph_tool
1067-
logger.debug("graph_tool=<%s> | loaded and cached", tool_name)
1068-
10691049
return graph_tool
10701050

10711051
except Exception as e:
@@ -1140,11 +1120,6 @@ def _load_agent_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
11401120
# Extract prompt template from agent config
11411121
prompt = agent_config.get("prompt")
11421122

1143-
# Check cache first
1144-
cache_key = f"agent:{tool_name}:{hash(str(sorted(tool_config.items())))}"
1145-
if cache_key in self._tool_cache:
1146-
return self._tool_cache[cache_key]
1147-
11481123
try:
11491124
# Load the agent using AgentConfigLoader
11501125
# Wrap the agent config in the required top-level 'agent' key
@@ -1157,10 +1132,6 @@ def _load_agent_as_tool(self, tool_config: Dict[str, Any]) -> AgentTool:
11571132
agent=agent, tool_name=tool_name, description=description, input_schema=input_schema, prompt=prompt
11581133
)
11591134

1160-
# Cache the result
1161-
self._tool_cache[cache_key] = agent_tool
1162-
logger.debug("agent_tool=<%s> | loaded and cached", tool_name)
1163-
11641135
return agent_tool
11651136

11661137
except Exception as e:
@@ -1177,11 +1148,6 @@ def _load_string_tool(self, identifier: str, module_path: Optional[str] = None)
11771148
Returns:
11781149
AgentTool instance for the specified identifier.
11791150
"""
1180-
# Check cache first
1181-
cache_key = f"{module_path or ''}:{identifier}"
1182-
if cache_key in self._tool_cache:
1183-
return self._tool_cache[cache_key]
1184-
11851151
tool = None
11861152

11871153
# Strategy 1: Check registry for already loaded tools
@@ -1205,10 +1171,6 @@ def _load_string_tool(self, identifier: str, module_path: Optional[str] = None)
12051171
if tool is None:
12061172
raise ValueError(f"Tool '{identifier}' not found")
12071173

1208-
# Cache the result
1209-
self._tool_cache[cache_key] = tool
1210-
logger.debug("tool_identifier=<%s> | loaded and cached", identifier)
1211-
12121174
return tool
12131175

12141176
def load_tools(self, identifiers: List[Union[str, Dict[str, Any]]]) -> List[AgentTool]:
@@ -1396,10 +1358,6 @@ def _import_module_from_path(self, module_path: str) -> Any:
13961358
if not path.exists():
13971359
raise ImportError(f"Module file not found: {module_path}")
13981360

1399-
# Check cache
1400-
if module_path in self._loaded_modules:
1401-
return self._loaded_modules[module_path]
1402-
14031361
# Import the module
14041362
spec = importlib.util.spec_from_file_location(path.stem, module_path)
14051363
if spec is None or spec.loader is None:
@@ -1416,8 +1374,6 @@ def _import_module_from_path(self, module_path: str) -> Any:
14161374
if path.stem in sys.modules and sys.modules[path.stem] is module:
14171375
del sys.modules[path.stem]
14181376

1419-
# Cache the module
1420-
self._loaded_modules[module_path] = module
14211377
return module
14221378

14231379
def _extract_tool_from_module(self, identifier: str, module: Any) -> Optional[AgentTool]:
@@ -1563,18 +1519,3 @@ def _scan_module_for_tool_names(self, module_path: str) -> List[str]:
15631519
except Exception as e:
15641520
logger.warning("module_path=<%s> | failed to scan | %s", module_path, e)
15651521
return []
1566-
1567-
def clear_cache(self) -> None:
1568-
"""Clear the internal tool and module caches."""
1569-
self._tool_cache.clear()
1570-
self._loaded_modules.clear()
1571-
1572-
# Clear caches in dependent loaders if they exist
1573-
if self._agent_config_loader:
1574-
self._agent_config_loader.clear_cache()
1575-
if self._swarm_config_loader:
1576-
self._swarm_config_loader.clear_cache()
1577-
if self._graph_config_loader:
1578-
self._graph_config_loader.clear_cache()
1579-
1580-
logger.debug("tool and module caches cleared")

0 commit comments

Comments
 (0)