@@ -95,16 +95,62 @@ class _ToolExecutionInterrupted(Exception):
9595
9696ToolExecutorResultT = T .TypeVar ("ToolExecutorResultT" )
9797
98- USER_INTERRUPTION_MESSAGE = (
99- "[SYSTEM: User actively interrupted the response generation. "
100- "Partial output before interruption is preserved.]"
101- )
102-
10398
10499class ToolLoopAgentRunner (BaseAgentRunner [TContext ]):
105100 EMPTY_OUTPUT_RETRY_ATTEMPTS = 3
106101 EMPTY_OUTPUT_RETRY_WAIT_MIN_S = 1
107102 EMPTY_OUTPUT_RETRY_WAIT_MAX_S = 4
103+ USER_INTERRUPTION_MESSAGE = (
104+ "[SYSTEM: User actively interrupted the response generation. "
105+ "Partial output before interruption is preserved.]"
106+ )
107+ FOLLOW_UP_NOTICE_TEMPLATE = (
108+ "\n \n [SYSTEM NOTICE] User sent follow-up messages while tool execution "
109+ "was in progress. Prioritize these follow-up instructions in your next "
110+ "actions. In your very next action, briefly acknowledge to the user "
111+ "that their follow-up message(s) were received before continuing.\n "
112+ "{follow_up_lines}"
113+ )
114+ MAX_STEPS_REACHED_PROMPT = (
115+ "Maximum tool call limit reached. "
116+ "Stop calling tools, and based on the information you have gathered, "
117+ "summarize your task and findings, and reply to the user directly."
118+ )
119+ SKILLS_LIKE_REQUERY_INSTRUCTION_TEMPLATE = (
120+ "You have decided to call tool(s): {tool_names}. Now call the tool(s) "
121+ "with required arguments using the tool schema, and follow the existing "
122+ "tool-use rules."
123+ )
124+ SKILLS_LIKE_REQUERY_REPAIR_INSTRUCTION = (
125+ "This is the second-stage tool execution step. "
126+ "You must do exactly one of the following: "
127+ "1. Call one of the selected tools using the provided tool schema. "
128+ "2. If calling a tool is no longer possible or appropriate, reply to the user "
129+ "with a brief explanation of why. "
130+ "Do not return an empty response. "
131+ "Do not ignore the selected tools without explanation."
132+ )
133+ REPEATED_TOOL_NOTICE_L1_THRESHOLD = 2
134+ REPEATED_TOOL_NOTICE_L2_THRESHOLD = 3
135+ REPEATED_TOOL_NOTICE_L3_THRESHOLD = 5
136+ REPEATED_TOOL_NOTICE_L1_TEMPLATE = (
137+ "\n \n [SYSTEM NOTICE] By the way, you have executed the same tool "
138+ "`{tool_name}` {streak} times consecutively. Double-check whether another "
139+ "tool, different arguments, or a summary would move the task forward better."
140+ )
141+ REPEATED_TOOL_NOTICE_L2_TEMPLATE = (
142+ "\n \n [SYSTEM NOTICE] Important: you have executed the same tool "
143+ "`{tool_name}` {streak} times consecutively. Unless this repetition is "
144+ "clearly necessary, stop repeating the same action and either switch "
145+ "tools, refine parameters, or summarize what is still missing."
146+ )
147+ REPEATED_TOOL_NOTICE_L3_TEMPLATE = (
148+ "\n \n [SYSTEM NOTICE] Important: you have executed the same tool "
149+ "`{tool_name}` {streak} times consecutively. Repetition is now very "
150+ "high. Continue only if each call is clearly producing new information. "
151+ "Otherwise, change strategy, adjust arguments, or explain the limitation "
152+ "to the user."
153+ )
108154
109155 def _get_persona_custom_error_message (self ) -> str | None :
110156 """Read persona-level custom error message from event extras when available."""
@@ -415,12 +461,8 @@ def _consume_follow_up_notice(self) -> str:
415461 follow_up_lines = "\n " .join (
416462 f"{ idx } . { ticket .text } " for idx , ticket in enumerate (follow_ups , start = 1 )
417463 )
418- return (
419- "\n \n [SYSTEM NOTICE] User sent follow-up messages while tool execution "
420- "was in progress. Prioritize these follow-up instructions in your next "
421- "actions. In your very next action, briefly acknowledge to the user "
422- "that their follow-up message(s) were received before continuing.\n "
423- f"{ follow_up_lines } "
464+ return self .FOLLOW_UP_NOTICE_TEMPLATE .format (
465+ follow_up_lines = follow_up_lines ,
424466 )
425467
426468 def _merge_follow_up_notice (self , content : str ) -> str :
@@ -438,30 +480,24 @@ def _track_tool_call_streak(self, tool_name: str) -> int:
438480 return self ._same_tool_streak
439481
440482 def _build_same_tool_guidance (self , tool_name : str , streak : int ) -> str :
441- if streak < 3 :
483+ if streak < self . REPEATED_TOOL_NOTICE_L1_THRESHOLD :
442484 return ""
443485
444- if streak >= 5 :
445- return (
446- "\n \n [SYSTEM NOTICE] Important: you have executed the same tool "
447- f"`{ tool_name } ` { streak } times consecutively. Repetition is now very "
448- "high. Continue only if each call is clearly producing new information. "
449- "Otherwise, change strategy, adjust arguments, or explain the limitation "
450- "to the user."
486+ if streak >= self .REPEATED_TOOL_NOTICE_L3_THRESHOLD :
487+ return self .REPEATED_TOOL_NOTICE_L3_TEMPLATE .format (
488+ tool_name = tool_name ,
489+ streak = streak ,
451490 )
452491
453- if streak >= 3 :
454- return (
455- "\n \n [SYSTEM NOTICE] Important: you have executed the same tool "
456- f"`{ tool_name } ` { streak } times consecutively. Unless this repetition is "
457- "clearly necessary, stop repeating the same action and either switch "
458- "tools, refine parameters, or summarize what is still missing."
492+ if streak >= self .REPEATED_TOOL_NOTICE_L2_THRESHOLD :
493+ return self .REPEATED_TOOL_NOTICE_L2_TEMPLATE .format (
494+ tool_name = tool_name ,
495+ streak = streak ,
459496 )
460497
461- return (
462- "\n \n [SYSTEM NOTICE] By the way, you have executed the same tool "
463- f"`{ tool_name } ` { streak } times consecutively. Double-check whether another "
464- "tool, different arguments, or a summary would move the task forward better."
498+ return self .REPEATED_TOOL_NOTICE_L1_TEMPLATE .format (
499+ tool_name = tool_name ,
500+ streak = streak ,
465501 )
466502
467503 @override
@@ -520,7 +556,7 @@ async def step(self):
520556 if self ._is_stop_requested ():
521557 llm_resp_result = LLMResponse (
522558 role = "assistant" ,
523- completion_text = USER_INTERRUPTION_MESSAGE ,
559+ completion_text = self . USER_INTERRUPTION_MESSAGE ,
524560 reasoning_content = llm_response .reasoning_content ,
525561 reasoning_signature = llm_response .reasoning_signature ,
526562 )
@@ -718,7 +754,7 @@ async def step_until_done(
718754 self .run_context .messages .append (
719755 Message (
720756 role = "user" ,
721- content = "工具调用次数已达到上限,请停止使用工具,并根据已经收集到的信息,对你的任务和发现进行总结,然后直接回复用户。" ,
757+ content = self . MAX_STEPS_REACHED_PROMPT ,
722758 )
723759 )
724760 # 再执行最后一步
@@ -990,11 +1026,8 @@ def _build_tool_requery_context(
9901026 contexts .append (msg .model_dump ()) # type: ignore[call-arg]
9911027 elif isinstance (msg , dict ):
9921028 contexts .append (copy .deepcopy (msg ))
993- instruction = (
994- "You have decided to call tool(s): "
995- + ", " .join (tool_names )
996- + ". Now call the tool(s) with required arguments using the tool schema, "
997- "and follow the existing tool-use rules."
1029+ instruction = self .SKILLS_LIKE_REQUERY_INSTRUCTION_TEMPLATE .format (
1030+ tool_names = ", " .join (tool_names )
9981031 )
9991032 if extra_instruction :
10001033 instruction = f"{ instruction } \n { extra_instruction } "
@@ -1065,14 +1098,7 @@ async def _resolve_tool_exec(
10651098 )
10661099 repair_contexts = self ._build_tool_requery_context (
10671100 tool_names ,
1068- extra_instruction = (
1069- "This is the second-stage tool execution step. "
1070- "You must do exactly one of the following: "
1071- "1. Call one of the selected tools using the provided tool schema. "
1072- "2. If calling a tool is no longer possible or appropriate, reply to the user with a brief explanation of why. "
1073- "Do not return an empty response. "
1074- "Do not ignore the selected tools without explanation."
1075- ),
1101+ extra_instruction = self .SKILLS_LIKE_REQUERY_REPAIR_INSTRUCTION ,
10761102 )
10771103 repair_resp = await self .provider .text_chat (
10781104 contexts = repair_contexts ,
@@ -1114,7 +1140,7 @@ async def _finalize_aborted_step(
11141140 if llm_resp .role != "assistant" :
11151141 llm_resp = LLMResponse (
11161142 role = "assistant" ,
1117- completion_text = USER_INTERRUPTION_MESSAGE ,
1143+ completion_text = self . USER_INTERRUPTION_MESSAGE ,
11181144 )
11191145 self .final_llm_resp = llm_resp
11201146 self ._aborted = True
0 commit comments