mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
core/agent: skip duplicate synthetic todo nag injection on retry
This commit is contained in:
@@ -26,6 +26,7 @@ from .loop import DEFAULT_MAX_ROUNDS, run_chat_loop
|
|||||||
from .todo_nag import (
|
from .todo_nag import (
|
||||||
DEFAULT_TODO_NAG_STRATEGY,
|
DEFAULT_TODO_NAG_STRATEGY,
|
||||||
inject_todo_nag_for_stack_with_events,
|
inject_todo_nag_for_stack_with_events,
|
||||||
|
is_synthetic_todo_nag_call_id,
|
||||||
parse_todo_nag_strategy,
|
parse_todo_nag_strategy,
|
||||||
refresh_synthetic_todo_nags,
|
refresh_synthetic_todo_nags,
|
||||||
)
|
)
|
||||||
@@ -114,6 +115,37 @@ def committed_prefix_end(messages: Sequence[AnyChatMessage], user_index: int) ->
|
|||||||
return end
|
return end
|
||||||
|
|
||||||
|
|
||||||
|
def _synthetic_todo_pair_after(
|
||||||
|
messages: Sequence[AnyChatMessage],
|
||||||
|
index: int,
|
||||||
|
) -> bool:
|
||||||
|
"""True when a synthetic todo nag pair sits at ``messages[index+1:index+3]``.
|
||||||
|
|
||||||
|
The pattern is ``AssistantChatMessage`` (all tool calls synthetic) followed
|
||||||
|
by a matching ``ToolChatMessage`` with a synthetic call id.
|
||||||
|
On retry after a failed turn, a previously injected turn-start nag may still
|
||||||
|
be in messages (survived rollback). This detects it so we can skip a fresh
|
||||||
|
injection.
|
||||||
|
"""
|
||||||
|
if index + 2 >= len(messages):
|
||||||
|
return False
|
||||||
|
first = messages[index + 1]
|
||||||
|
if not isinstance(first, AssistantChatMessage):
|
||||||
|
return False
|
||||||
|
tool_calls = first.tool_calls
|
||||||
|
if tool_calls is UNSET or not tool_calls:
|
||||||
|
return False
|
||||||
|
if not all(
|
||||||
|
isinstance(call, AssistantFunctionToolCall) and is_synthetic_todo_nag_call_id(call.id) for call in tool_calls
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
synth_ids = {call.id for call in tool_calls if isinstance(call, AssistantFunctionToolCall)}
|
||||||
|
second = messages[index + 2]
|
||||||
|
if not isinstance(second, ToolChatMessage):
|
||||||
|
return False
|
||||||
|
return second.tool_call_id in synth_ids
|
||||||
|
|
||||||
|
|
||||||
class ChatAgent:
|
class ChatAgent:
|
||||||
"""Thin wrapper: chat client + optional tools + optional memory bind."""
|
"""Thin wrapper: chat client + optional tools + optional memory bind."""
|
||||||
|
|
||||||
@@ -398,14 +430,25 @@ class ChatAgent:
|
|||||||
turn_rounds = 0
|
turn_rounds = 0
|
||||||
last_request = TokenUsage()
|
last_request = TokenUsage()
|
||||||
try:
|
try:
|
||||||
|
_turn_nag_strategy: TodoNagStrategy = self.todo_nag_strategy
|
||||||
# Turn-start nag before first completion; yield events so CLI flushes
|
# Turn-start nag before first completion; yield events so CLI flushes
|
||||||
# (synthetic_tool → ToolCall/Result chrome, not glued to text).
|
# (synthetic_tool → ToolCall/Result chrome, not glued to text).
|
||||||
if self.todo_stack is not None and not self.todo_stack.is_empty():
|
# On retry, a synthetic_tool pair from a previous failed attempt may
|
||||||
|
# have survived rollback (it looks like a committed tool batch).
|
||||||
|
# Skip fresh injection — refresh_synthetic_todo_nags already updated
|
||||||
|
# the content of the surviving pair above.
|
||||||
|
if (
|
||||||
|
self.todo_stack is not None
|
||||||
|
and not self.todo_stack.is_empty()
|
||||||
|
and (
|
||||||
|
_turn_nag_strategy != "synthetic_tool" or not _synthetic_todo_pair_after(self.messages, user_index)
|
||||||
|
)
|
||||||
|
):
|
||||||
_injected, nag_events = inject_todo_nag_for_stack_with_events(
|
_injected, nag_events = inject_todo_nag_for_stack_with_events(
|
||||||
self.messages,
|
self.messages,
|
||||||
self.todo_stack,
|
self.todo_stack,
|
||||||
kind="turn_start",
|
kind="turn_start",
|
||||||
strategy=self.todo_nag_strategy,
|
strategy=_turn_nag_strategy,
|
||||||
)
|
)
|
||||||
for nag_event in nag_events:
|
for nag_event in nag_events:
|
||||||
yield nag_event
|
yield nag_event
|
||||||
@@ -423,7 +466,7 @@ class ChatAgent:
|
|||||||
parallel_tools=self.parallel_tools,
|
parallel_tools=self.parallel_tools,
|
||||||
max_context_tokens=self.max_context_tokens,
|
max_context_tokens=self.max_context_tokens,
|
||||||
todo_stack=self.todo_stack,
|
todo_stack=self.todo_stack,
|
||||||
todo_nag_strategy=self.todo_nag_strategy,
|
todo_nag_strategy=_turn_nag_strategy,
|
||||||
directive_reminder_tokens=self.directive_reminder_tokens,
|
directive_reminder_tokens=self.directive_reminder_tokens,
|
||||||
directive_reminder_text=self.directive_reminder_text,
|
directive_reminder_text=self.directive_reminder_text,
|
||||||
reminder_last_band=self.reminder_last_band,
|
reminder_last_band=self.reminder_last_band,
|
||||||
|
|||||||
Reference in New Issue
Block a user