Stop repeated duplicate skill loops
This commit is contained in:
@@ -579,6 +579,7 @@ class LocalCodingAgent:
|
||||
assistant_response_segments: list[str] = []
|
||||
continuation_count = 0
|
||||
active_skill_names: set[str] = set()
|
||||
duplicate_skill_counts: dict[str, int] = {}
|
||||
delegated_tasks = sum(
|
||||
1 for entry in file_history if entry.get('action') in ('delegate_agent', 'Agent')
|
||||
)
|
||||
@@ -1131,6 +1132,7 @@ class LocalCodingAgent:
|
||||
tool_result = self._execute_skill(
|
||||
tool_call.arguments,
|
||||
active_skill_names=active_skill_names,
|
||||
duplicate_skill_counts=duplicate_skill_counts,
|
||||
)
|
||||
elif tool_result is None:
|
||||
for update in execute_tool_streaming(
|
||||
@@ -1327,6 +1329,51 @@ class LocalCodingAgent:
|
||||
)
|
||||
if history_entry is not None:
|
||||
file_history.append(history_entry)
|
||||
if tool_result.metadata.get('stop_agent') is True:
|
||||
stop_reason = str(
|
||||
tool_result.metadata.get('stop_reason') or 'tool_requested_stop'
|
||||
)
|
||||
stop_output = self._build_tool_requested_stop_output(tool_result)
|
||||
stop_message_id = f'assistant_{len(session.messages)}'
|
||||
session.append_assistant(
|
||||
stop_output,
|
||||
message_id=stop_message_id,
|
||||
stop_reason=stop_reason,
|
||||
)
|
||||
stream_events.append(
|
||||
{
|
||||
'type': 'tool_requested_stop',
|
||||
'tool_name': tool_call.name,
|
||||
'tool_call_id': tool_call.id,
|
||||
'message_id': session.messages[tool_message_index].message_id,
|
||||
'assistant_message_id': stop_message_id,
|
||||
'metadata': dict(tool_result.metadata),
|
||||
}
|
||||
)
|
||||
_append_final_text_stream_events(stream_events, stop_output)
|
||||
result = AgentRunResult(
|
||||
final_output=stop_output,
|
||||
turns=turn_index,
|
||||
tool_calls=tool_calls,
|
||||
transcript=session.transcript(),
|
||||
events=tuple(stream_events),
|
||||
usage=total_usage,
|
||||
total_cost_usd=total_cost_usd,
|
||||
stop_reason=stop_reason,
|
||||
file_history=tuple(file_history),
|
||||
session_id=session_id,
|
||||
scratchpad_directory=(
|
||||
str(scratchpad_directory) if scratchpad_directory is not None else None
|
||||
),
|
||||
)
|
||||
result = self._append_runtime_after_turn_events(
|
||||
result,
|
||||
prompt=effective_prompt,
|
||||
turn_index=turn_index,
|
||||
)
|
||||
result = self._persist_session(session, result)
|
||||
self.last_run_result = result
|
||||
return result
|
||||
if tool_result.metadata.get('requires_user_review') is True:
|
||||
review_output = self._build_user_review_required_output(tool_result)
|
||||
review_message_id = f'assistant_{len(session.messages)}'
|
||||
@@ -2182,6 +2229,24 @@ class LocalCodingAgent:
|
||||
entry['history_kind'] = 'tool'
|
||||
return entry
|
||||
|
||||
def _build_tool_requested_stop_output(self, tool_result: ToolExecutionResult) -> str:
|
||||
stop_reason = str(tool_result.metadata.get('stop_reason') or '')
|
||||
if stop_reason == 'duplicate_skill_loop':
|
||||
skill_name = str(tool_result.metadata.get('skill_name') or 'unknown')
|
||||
duplicate_count = tool_result.metadata.get('duplicate_count')
|
||||
return (
|
||||
f'检测到模型连续重复调用 Skill `{skill_name}`,本轮已暂停。\n\n'
|
||||
f'- 重复次数:{duplicate_count}\n'
|
||||
'- 已处理:首次 Skill 已成功注入,后续重复调用没有继续注入大段提示词。\n'
|
||||
'- 建议:你可以直接回复“继续”,我会基于已经激活的 Skill 往下推进;'
|
||||
'或者补充更明确的生成边界和数量。'
|
||||
)
|
||||
return (
|
||||
'工具请求暂停当前任务,本轮已停止。\n\n'
|
||||
f'- 工具:{tool_result.name}\n'
|
||||
f'- 原因:{tool_result.content}'
|
||||
)
|
||||
|
||||
def _build_user_review_required_output(self, tool_result: ToolExecutionResult) -> str:
|
||||
fallback = (
|
||||
'已生成待 review 的计划。本轮已暂停,请检查上面的计划;'
|
||||
@@ -2492,6 +2557,7 @@ class LocalCodingAgent:
|
||||
arguments: dict[str, object],
|
||||
*,
|
||||
active_skill_names: set[str] | None = None,
|
||||
duplicate_skill_counts: dict[str, int] | None = None,
|
||||
) -> ToolExecutionResult:
|
||||
"""Execute a skill through the Skill tool.
|
||||
|
||||
@@ -2519,12 +2585,22 @@ class LocalCodingAgent:
|
||||
if bundled is not None:
|
||||
skill_key = bundled.name.strip().lower()
|
||||
if active_skill_names is not None and skill_key in active_skill_names:
|
||||
duplicate_count = 1
|
||||
if duplicate_skill_counts is not None:
|
||||
duplicate_count = duplicate_skill_counts.get(skill_key, 0) + 1
|
||||
duplicate_skill_counts[skill_key] = duplicate_count
|
||||
stop_agent = duplicate_count >= 2
|
||||
return ToolExecutionResult(
|
||||
name='Skill',
|
||||
ok=True,
|
||||
content=(
|
||||
f'Skill "{bundled.name}" 已在本轮激活。'
|
||||
'请直接依据前面注入的 Skill 指南继续执行,不要重复调用 Skill 工具。'
|
||||
if not stop_agent
|
||||
else (
|
||||
f'Skill "{bundled.name}" 已在本轮重复调用 {duplicate_count} 次。'
|
||||
'运行时已暂停,避免继续空转。'
|
||||
)
|
||||
),
|
||||
metadata={
|
||||
'action': 'skill_duplicate',
|
||||
@@ -2533,6 +2609,15 @@ class LocalCodingAgent:
|
||||
'skill_path': bundled.path,
|
||||
'should_query': True,
|
||||
'duplicate': True,
|
||||
'duplicate_count': duplicate_count,
|
||||
**(
|
||||
{
|
||||
'stop_agent': True,
|
||||
'stop_reason': 'duplicate_skill_loop',
|
||||
}
|
||||
if stop_agent
|
||||
else {}
|
||||
),
|
||||
},
|
||||
)
|
||||
if active_skill_names is not None:
|
||||
|
||||
Reference in New Issue
Block a user