22
33from typing import Literal
44
5- from langchain_core .messages import ToolCall
6- from uipath .agent .react import END_EXECUTION_TOOL , RAISE_ERROR_TOOL
7-
85from ..exceptions import AgentNodeRoutingException
9- from .router_utils import validate_last_message_is_AI
10- from .types import AgentGraphNode , AgentGraphState
11- from .utils import count_consecutive_thinking_messages
12-
13- FLOW_CONTROL_TOOLS = [END_EXECUTION_TOOL .name , RAISE_ERROR_TOOL .name ]
14-
15-
16- def __filter_control_flow_tool_calls (
17- tool_calls : list [ToolCall ],
18- ) -> list [ToolCall ]:
19- """Remove control flow tools when multiple tool calls exist."""
20- if len (tool_calls ) <= 1 :
21- return tool_calls
22-
23- return [tc for tc in tool_calls if tc .get ("name" ) not in FLOW_CONTROL_TOOLS ]
24-
25-
26- def __has_control_flow_tool (tool_calls : list [ToolCall ]) -> bool :
27- """Check if any tool call is of a control flow tool."""
28- return any (tc .get ("name" ) in FLOW_CONTROL_TOOLS for tc in tool_calls )
6+ from .types import FLOW_CONTROL_TOOLS , AgentGraphNode , AgentGraphState
7+ from .utils import (
8+ count_consecutive_thinking_messages ,
9+ extract_current_tool_call_index ,
10+ find_latest_ai_message ,
11+ )
2912
3013
3114def create_route_agent (thinking_messages_limit : int = 0 ):
@@ -40,50 +23,62 @@ def create_route_agent(thinking_messages_limit: int = 0):
4023
4124 def route_agent (
4225 state : AgentGraphState ,
43- ) -> list [ str ] | Literal [AgentGraphNode .AGENT , AgentGraphNode .TERMINATE ]:
44- """Route after agent: handles all routing logic including control flow detection .
26+ ) -> str | Literal [AgentGraphNode .AGENT , AgentGraphNode .TERMINATE ]:
27+ """Route after agent: handles sequential tool execution .
4528
4629 Routing logic:
47- 1. If multiple tool calls exist, filter out control flow tools (EndExecution, RaiseError)
48- 2. If control flow tool(s) remain , route to TERMINATE
49- 3. If regular tool calls remain, route to specific tool nodes (return list of tool names)
50- 4. If no tool calls, handle consecutive completions
30+ 1. Get current tool call index from messages
31+ 2. If current tool call index is None (all tools completed) , route to AGENT
32+ 3. If current tool call is a flow control tool, route to TERMINATE
33+ 4. Otherwise, route to the specific tool node
5134
5235 Returns:
53- - list[ str]: Tool node names for parallel execution
54- - AgentGraphNode.AGENT: For consecutive completions
36+ - str: Single tool node name for sequential execution
37+ - AgentGraphNode.AGENT: When all tool calls completed or no tool calls
5538 - AgentGraphNode.TERMINATE: For control flow termination
5639
5740 Raises:
58- AgentNodeRoutingException: When encountering unexpected state (empty messages, non-AIMessage, or excessive completions)
41+ AgentNodeRoutingException: When encountering unexpected state
5942 """
6043 messages = state .messages
61- last_message = validate_last_message_is_AI (messages )
62-
63- tool_calls = list (last_message .tool_calls ) if last_message .tool_calls else []
64- tool_calls = __filter_control_flow_tool_calls (tool_calls )
44+ last_message = find_latest_ai_message (messages )
45+ if last_message is None :
46+ raise AgentNodeRoutingException (
47+ "No AIMessage found in messages for routing."
48+ )
6549
66- if tool_calls and __has_control_flow_tool (tool_calls ):
67- return AgentGraphNode .TERMINATE
50+ if not last_message .tool_calls :
51+ consecutive_thinking_messages = count_consecutive_thinking_messages (
52+ messages
53+ )
6854
69- if tool_calls :
70- return [tc ["name" ] for tc in tool_calls ]
55+ if consecutive_thinking_messages > thinking_messages_limit :
56+ raise AgentNodeRoutingException (
57+ f"Agent exceeded consecutive completions limit without producing tool calls "
58+ f"(completions: { consecutive_thinking_messages } , max: { thinking_messages_limit } ). "
59+ f"This should not happen as tool_choice='required' is enforced at the limit."
60+ )
7161
72- consecutive_thinking_messages = count_consecutive_thinking_messages (messages )
62+ if last_message .content :
63+ return AgentGraphNode .AGENT
7364
74- if consecutive_thinking_messages > thinking_messages_limit :
7565 raise AgentNodeRoutingException (
76- f"Agent exceeded consecutive completions limit without producing tool calls "
77- f"(completions: { consecutive_thinking_messages } , max: { thinking_messages_limit } ). "
78- f"This should not happen as tool_choice='required' is enforced at the limit."
66+ f"Agent produced empty response without tool calls "
67+ f"(completions: { consecutive_thinking_messages } , has_content: False)"
7968 )
8069
81- if last_message .content :
70+ current_index = extract_current_tool_call_index (messages )
71+
72+ # all tool calls completed, go back to agent
73+ if current_index is None :
8274 return AgentGraphNode .AGENT
8375
84- raise AgentNodeRoutingException (
85- f"Agent produced empty response without tool calls "
86- f"(completions: { consecutive_thinking_messages } , has_content: False)"
87- )
76+ current_tool_call = last_message .tool_calls [current_index ]
77+ current_tool_name = current_tool_call ["name" ]
78+
79+ if current_tool_name in FLOW_CONTROL_TOOLS :
80+ return AgentGraphNode .TERMINATE
81+
82+ return current_tool_name
8883
8984 return route_agent
0 commit comments