1414
1515from __future__ import annotations
1616
17+ import contextvars
1718import importlib
1819import inspect
1920import os
2021from typing import Any
22+ from typing import Dict
2123from typing import List
2224
2325from typing_extensions import deprecated
2426import yaml
2527
2628from ..features import experimental
2729from ..features import FeatureName
30+ from ..workflow ._base_node import BaseNode
2831from .agent_config import AgentConfig
2932from .base_agent import BaseAgent
3033from .base_agent_config import BaseAgentConfig
3134from .common_configs import AgentRefConfig
3235from .common_configs import CodeConfig
3336
37+ _loaded_nodes_cache : contextvars .ContextVar [Dict [str , BaseNode ]] = (
38+ contextvars .ContextVar ("loaded_nodes_cache" )
39+ )
40+
3441
3542@deprecated ("from_config is deprecated and will be removed in future versions." )
3643@experimental (FeatureName .AGENT_CONFIG )
37- def from_config (config_path : str ) -> BaseAgent :
44+ def from_config (config_path : str ) -> BaseNode :
3845 """Build agent from a configfile path.
3946
4047 Args:
@@ -49,36 +56,137 @@ def from_config(config_path: str) -> BaseAgent:
4956 ValueError: If agent type is unsupported.
5057 """
5158 abs_path = os .path .abspath (config_path )
59+
60+ try :
61+ cache = _loaded_nodes_cache .get ()
62+ except LookupError :
63+ cache = None
64+
65+ if cache is None :
66+ cache_dict = {}
67+ token = _loaded_nodes_cache .set (cache_dict )
68+ try :
69+ return _from_config (abs_path )
70+ finally :
71+ _loaded_nodes_cache .reset (token )
72+ else :
73+ if abs_path in cache :
74+ return cache [abs_path ]
75+ node = _from_config (abs_path )
76+ cache [abs_path ] = node
77+ return node
78+
79+
80+ def _from_config (abs_path : str ) -> BaseNode :
5281 config = _load_config_from_path (abs_path )
5382 agent_config = config .root
5483
5584 # pylint: disable=unidiomatic-typecheck Needs exact class matching.
5685 if type (agent_config ) is BaseAgentConfig :
5786 # Resolve the concrete agent config for user-defined agent classes.
58- agent_class = _resolve_agent_class (agent_config .agent_class )
87+ agent_class = _resolve_node_class (agent_config .agent_class )
88+
89+ from ..workflow ._function_node import FunctionNode
90+ from ..workflow ._join_node import JoinNode
91+ from ..workflow ._tool_node import _ToolNode
92+
93+ if issubclass (agent_class , (JoinNode , FunctionNode , _ToolNode )):
94+ if issubclass (agent_class , JoinNode ):
95+ return agent_class (name = agent_config .name )
96+
97+ elif issubclass (agent_class , FunctionNode ):
98+ func_code = agent_config .model_extra .get ("func_code" )
99+ if not func_code :
100+ raise ValueError (
101+ f"FunctionNode { agent_config .name } configuration is missing"
102+ " 'func_code'"
103+ )
104+
105+ func = resolve_fully_qualified_name (func_code )
106+
107+ kwargs = {
108+ "func" : func ,
109+ "name" : agent_config .name ,
110+ }
111+ if "rerun_on_resume" in agent_config .model_extra :
112+ kwargs ["rerun_on_resume" ] = agent_config .model_extra [
113+ "rerun_on_resume"
114+ ]
115+ if "parameter_binding" in agent_config .model_extra :
116+ kwargs ["parameter_binding" ] = agent_config .model_extra [
117+ "parameter_binding"
118+ ]
119+
120+ return agent_class (** kwargs )
121+
122+ elif issubclass (agent_class , _ToolNode ):
123+ tool_code = agent_config .model_extra .get ("tool_code" )
124+ if not tool_code :
125+ raise ValueError (
126+ f"ToolNode { agent_config .name } configuration is missing"
127+ " 'tool_code'"
128+ )
129+
130+ from ..tools .base_tool import BaseTool
131+ from ..tools .function_tool import FunctionTool
132+ from ..tools .tool_configs import ToolArgsConfig
133+ from ..tools .tool_configs import ToolConfig
134+ from .llm_agent import LlmAgent
135+
136+ args = agent_config .model_extra .get ("args" )
137+ tool_args = ToolArgsConfig .model_validate (args ) if args else None
138+ tool_config = ToolConfig (name = tool_code , args = tool_args )
139+
140+ resolved = LlmAgent ._resolve_tools ([tool_config ], abs_path )
141+ if not resolved :
142+ raise ValueError (
143+ f"Failed to resolve tool { tool_code } for ToolNode"
144+ f" { agent_config .name } "
145+ )
146+
147+ tool = resolved [0 ]
148+ if not isinstance (tool , BaseTool ):
149+ if callable (tool ):
150+ tool = FunctionTool (tool )
151+ else :
152+ raise ValueError (
153+ f"Resolved tool { tool_code } is neither a BaseTool nor callable"
154+ )
155+
156+ return agent_class (tool = tool , name = agent_config .name )
157+
59158 agent_config = agent_class .config_type .model_validate (
60159 agent_config .model_dump ()
61160 )
62161 return agent_class .from_config (agent_config , abs_path )
63162 else :
64163 # For built-in agent classes, no need to re-validate.
65- agent_class = _resolve_agent_class (agent_config .agent_class )
164+ agent_class = _resolve_node_class (agent_config .agent_class )
66165 return agent_class .from_config (agent_config , abs_path )
67166
68167
69- def _resolve_agent_class (agent_class : str ) -> type [BaseAgent ]:
168+ def _resolve_node_class (agent_class : str ) -> type [BaseNode ]:
70169 """Resolve the agent class from its fully qualified name."""
71170 agent_class_name = agent_class or "LlmAgent"
72171 if "." not in agent_class_name :
73- agent_class_name = f"google.adk.agents.{ agent_class_name } "
172+ if agent_class_name == "Workflow" :
173+ agent_class_name = "google.adk.workflow.Workflow"
174+ elif agent_class_name == "JoinNode" :
175+ agent_class_name = "google.adk.workflow.JoinNode"
176+ elif agent_class_name == "FunctionNode" :
177+ agent_class_name = "google.adk.workflow.FunctionNode"
178+ elif agent_class_name == "ToolNode" :
179+ agent_class_name = "google.adk.workflow._tool_node._ToolNode"
180+ else :
181+ agent_class_name = f"google.adk.agents.{ agent_class_name } "
74182
75183 agent_class = resolve_fully_qualified_name (agent_class_name )
76- if inspect .isclass (agent_class ) and issubclass (agent_class , BaseAgent ):
184+ if inspect .isclass (agent_class ) and issubclass (agent_class , BaseNode ):
77185 return agent_class
78186
79187 raise ValueError (
80188 f"Invalid agent class `{ agent_class_name } `. It must be a subclass of"
81- " BaseAgent ."
189+ " BaseNode ."
82190 )
83191
84192
@@ -118,7 +226,7 @@ def resolve_fully_qualified_name(name: str) -> Any:
118226@experimental (FeatureName .AGENT_CONFIG )
119227def resolve_agent_reference (
120228 ref_config : AgentRefConfig , referencing_agent_config_abs_path : str
121- ) -> BaseAgent :
229+ ) -> BaseNode :
122230 """Build an agent from a reference.
123231
124232 Args:
@@ -167,7 +275,7 @@ def _resolve_agent_code_reference(code: str) -> Any:
167275 if callable (obj ):
168276 raise ValueError (f"Invalid agent reference to a callable: { code } " )
169277
170- if not isinstance (obj , BaseAgent ):
278+ if not isinstance (obj , BaseNode ):
171279 raise ValueError (f"Invalid agent reference to a non-agent instance: { code } " )
172280
173281 return obj
0 commit comments