11from config .config import config
22from graph .state import AgentState
3- from langchain_core .messages import AIMessage , HumanMessage , SystemMessage
3+ from langchain_core .messages import AIMessage , HumanMessage , SystemMessage , ToolMessage
44from langgraph .prebuilt import ToolNode
55from model .model import model , summary_model
66from tools .registry import TOOLS_REGISTRY
77
88tool_node = ToolNode (tools = TOOLS_REGISTRY , messages_key = "messages" )
99
1010
11+ def _is_repeating_tool_result (messages : list ) -> bool :
12+ recent_non_system = [message for message in messages if message .type != "system" ][- 8 :]
13+ recent_tool_messages = [message for message in recent_non_system if isinstance (message , ToolMessage )]
14+ if len (recent_tool_messages ) < 2 :
15+ return False
16+ last_tool = recent_tool_messages [- 1 ]
17+ prev_tool = recent_tool_messages [- 2 ]
18+ return last_tool .name == prev_tool .name and str (last_tool .content ).strip () == str (prev_tool .content ).strip ()
19+
20+
21+ def _task_requires_file_changes (messages : list ) -> bool :
22+ first_human = next ((message for message in messages if isinstance (message , HumanMessage )), None )
23+ if not first_human :
24+ return False
25+ prompt_text = str (first_human .content ).lower ()
26+ write_keywords = [
27+ "crea" ,
28+ "create" ,
29+ "implement" ,
30+ "estructura" ,
31+ "scaffold" ,
32+ "refactor" ,
33+ "fix" ,
34+ "corrige" ,
35+ "modifica" ,
36+ "build" ,
37+ ]
38+ return any (keyword in prompt_text for keyword in write_keywords )
39+
40+
41+ def _has_successful_write (messages : list ) -> bool :
42+ write_tools = {"make_dir" , "make_dirs" , "create_file" , "write_file" }
43+ for message in messages :
44+ if isinstance (message , ToolMessage ) and message .name in write_tools :
45+ content = str (message .content )
46+ if "Success:" in content :
47+ return True
48+ return False
49+
50+
51+ def _last_tool_error (messages : list ) -> bool :
52+ for message in reversed (messages ):
53+ if isinstance (message , ToolMessage ):
54+ return str (message .content ).strip ().startswith ("Error" )
55+ if isinstance (message , AIMessage ):
56+ break
57+ return False
58+
59+
1160def memory_manager_node (state : AgentState ):
1261 """
1362 Makes a summary of oldest 'config.messages_to_summarize' messages
@@ -38,6 +87,18 @@ def explorer_node(state: AgentState):
3887 if steps > config .max_steps :
3988 return {"steps" : steps , "messages" : [AIMessage (content = "Stopping: too many steps" )]}
4089 all_messages = state ["messages" ]
90+ if _is_repeating_tool_result (all_messages ):
91+ return {
92+ "steps" : steps ,
93+ "messages" : [
94+ AIMessage (
95+ content = (
96+ "Stopping to avoid a tool loop: same tool result was repeated. "
97+ "Use a different tool or finalize with a concise result."
98+ )
99+ )
100+ ],
101+ }
41102 system_message = all_messages [0 ]
42103 window_size = config .chat_window_size
43104 if len (all_messages ) > window_size :
@@ -46,6 +107,18 @@ def explorer_node(state: AgentState):
46107 messages_to_send = [system_message , * chat_context ]
47108 else :
48109 messages_to_send = all_messages
110+
111+ if _last_tool_error (all_messages ):
112+ messages_to_send = [
113+ * messages_to_send ,
114+ HumanMessage (content = "The previous tool call failed. Retry with corrected tool arguments." ),
115+ ]
116+ elif _task_requires_file_changes (all_messages ) and not _has_successful_write (all_messages ):
117+ messages_to_send = [
118+ * messages_to_send ,
119+ HumanMessage (content = "This task requires file changes. Use writing tools before finishing." ),
120+ ]
121+
49122 response = model .invoke (messages_to_send )
50123 return {"messages" : [response ], "steps" : steps }
51124
@@ -54,6 +127,12 @@ def router_logic(state: AgentState):
54127 """
55128 Control function that decides next step
56129 """
130+ if state .get ("steps" , 0 ) < config .max_steps :
131+ if _last_tool_error (state ["messages" ]):
132+ return "retry"
133+ if _task_requires_file_changes (state ["messages" ]) and not _has_successful_write (state ["messages" ]):
134+ return "retry"
135+
57136 last_message = state ["messages" ][- 1 ]
58137 if getattr (last_message , "tool_calls" , None ):
59138 return "tool_executor"
0 commit comments