11"""LLM node for ReAct Agent graph."""
22
3- from typing import Literal , Sequence
3+ from typing import Literal , Sequence , TypeVar
44
55from langchain_core .language_models import BaseChatModel
66from langchain_core .messages import AIMessage , AnyMessage , ToolCall
77from langchain_core .tools import BaseTool
8+ from pydantic import BaseModel
89from uipath .runtime .errors import UiPathErrorCategory , UiPathErrorCode
910
11+ from uipath_langchain .agent .tools .static_args import (
12+ apply_static_argument_properties_to_schema ,
13+ )
14+ from uipath_langchain .agent .tools .structured_tool_with_argument_properties import (
15+ StructuredToolWithArgumentProperties ,
16+ )
17+
1018from ..exceptions import AgentTerminationException
1119from .constants import (
1220 DEFAULT_MAX_CONSECUTIVE_THINKING_MESSAGES ,
1321 DEFAULT_MAX_LLM_MESSAGES ,
1422)
1523from .types import FLOW_CONTROL_TOOLS , AgentGraphState
16- from .utils import count_consecutive_thinking_messages
24+ from .utils import count_consecutive_thinking_messages , extract_input_data_from_state
1725
1826OPENAI_COMPATIBLE_CHAT_MODELS = (
1927 "UiPathChatOpenAI" ,
@@ -48,9 +56,14 @@ def _filter_control_flow_tool_calls(
4856 return [tc for tc in tool_calls if tc .get ("name" ) not in FLOW_CONTROL_TOOLS ]
4957
5058
59+ StateT = TypeVar ("StateT" , bound = AgentGraphState )
60+ InputT = TypeVar ("InputT" , bound = BaseModel )
61+
62+
5163def create_llm_node (
5264 model : BaseChatModel ,
53- tools : Sequence [BaseTool ] | None = None ,
65+ tools : Sequence [BaseTool ],
66+ input_schema : type [InputT ] | None = None ,
5467 is_conversational : bool = False ,
5568 llm_messages_limit : int = DEFAULT_MAX_LLM_MESSAGES ,
5669 thinking_messages_limit : int = DEFAULT_MAX_CONSECUTIVE_THINKING_MESSAGES ,
@@ -69,12 +82,10 @@ def create_llm_node(
6982 before enforcing tool usage. 0 = force tools every time.
7083 """
7184 bindable_tools = list (tools ) if tools else []
72- base_llm = model .bind_tools (bindable_tools ) if bindable_tools else model
7385 tool_choice_required_value = _get_required_tool_choice_by_model (model )
7486
75- async def llm_node (state : AgentGraphState ):
87+ async def llm_node (state : StateT ):
7688 messages : list [AnyMessage ] = state .messages
77-
7889 agent_ai_messages = sum (1 for msg in messages if isinstance (msg , AIMessage ))
7990 if agent_ai_messages >= llm_messages_limit :
8091 raise AgentTerminationException (
@@ -86,6 +97,11 @@ async def llm_node(state: AgentGraphState):
8697
8798 consecutive_thinking_messages = count_consecutive_thinking_messages (messages )
8899
100+ static_schema_tools = _apply_tool_argument_properties (
101+ bindable_tools , state , input_schema
102+ )
103+ base_llm = model .bind_tools (static_schema_tools )
104+
89105 if (
90106 not is_conversational
91107 and bindable_tools
@@ -111,3 +127,19 @@ async def llm_node(state: AgentGraphState):
111127 return {"messages" : [response ]}
112128
113129 return llm_node
130+
131+
132+ def _apply_tool_argument_properties (
133+ tools : list [BaseTool ],
134+ state : StateT ,
135+ input_schema : type [InputT ] | None = None ,
136+ ) -> list [BaseTool ]:
137+ """Apply dynamic schema modifications to tools based on their argument_properties."""
138+
139+ agent_input = extract_input_data_from_state (state , input_schema or type (state ))
140+ return [
141+ apply_static_argument_properties_to_schema (tool , agent_input )
142+ if isinstance (tool , StructuredToolWithArgumentProperties )
143+ else tool
144+ for tool in tools
145+ ]
0 commit comments