mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-08 22:38:05 -05:00
fix: Prevent old instructions from being re-executed after conversation condensation (#11982)
This commit is contained in:
@@ -194,9 +194,12 @@ class CodeActAgent(Agent):
|
||||
# event we'll just return that instead of an action. The controller will
|
||||
# immediately ask the agent to step again with the new view.
|
||||
condensed_history: list[Event] = []
|
||||
# Track which event IDs have been forgotten/condensed
|
||||
forgotten_event_ids: set[int] = set()
|
||||
match self.condenser.condensed_history(state):
|
||||
case View(events=events):
|
||||
case View(events=events, forgotten_event_ids=forgotten_ids):
|
||||
condensed_history = events
|
||||
forgotten_event_ids = forgotten_ids
|
||||
|
||||
case Condensation(action=condensation_action):
|
||||
return condensation_action
|
||||
@@ -206,7 +209,9 @@ class CodeActAgent(Agent):
|
||||
)
|
||||
|
||||
initial_user_message = self._get_initial_user_message(state.history)
|
||||
messages = self._get_messages(condensed_history, initial_user_message)
|
||||
messages = self._get_messages(
|
||||
condensed_history, initial_user_message, forgotten_event_ids
|
||||
)
|
||||
params: dict = {
|
||||
'messages': messages,
|
||||
}
|
||||
@@ -245,7 +250,10 @@ class CodeActAgent(Agent):
|
||||
return initial_user_message
|
||||
|
||||
def _get_messages(
|
||||
self, events: list[Event], initial_user_message: MessageAction
|
||||
self,
|
||||
events: list[Event],
|
||||
initial_user_message: MessageAction,
|
||||
forgotten_event_ids: set[int],
|
||||
) -> list[Message]:
|
||||
"""Constructs the message history for the LLM conversation.
|
||||
|
||||
@@ -284,6 +292,7 @@ class CodeActAgent(Agent):
|
||||
messages = self.conversation_memory.process_events(
|
||||
condensed_history=events,
|
||||
initial_user_action=initial_user_message,
|
||||
forgotten_event_ids=forgotten_event_ids,
|
||||
max_message_chars=self.llm.config.max_message_chars,
|
||||
vision_is_active=self.llm.vision_is_active(),
|
||||
)
|
||||
|
||||
@@ -76,6 +76,7 @@ class ConversationMemory:
|
||||
self,
|
||||
condensed_history: list[Event],
|
||||
initial_user_action: MessageAction,
|
||||
forgotten_event_ids: set[int] | None = None,
|
||||
max_message_chars: int | None = None,
|
||||
vision_is_active: bool = False,
|
||||
) -> list[Message]:
|
||||
@@ -85,16 +86,23 @@ class ConversationMemory:
|
||||
|
||||
Args:
|
||||
condensed_history: The condensed history of events to convert
|
||||
initial_user_action: The initial user message action, if available. Used to ensure the conversation starts correctly.
|
||||
forgotten_event_ids: Set of event IDs that have been forgotten/condensed. If the initial user action's ID
|
||||
is in this set, it will not be re-inserted to prevent re-execution of old instructions.
|
||||
max_message_chars: The maximum number of characters in the content of an event included
|
||||
in the prompt to the LLM. Larger observations are truncated.
|
||||
vision_is_active: Whether vision is active in the LLM. If True, image URLs will be included.
|
||||
initial_user_action: The initial user message action, if available. Used to ensure the conversation starts correctly.
|
||||
"""
|
||||
events = condensed_history
|
||||
# Default to empty set if not provided
|
||||
if forgotten_event_ids is None:
|
||||
forgotten_event_ids = set()
|
||||
|
||||
# Ensure the event list starts with SystemMessageAction, then MessageAction(source='user')
|
||||
self._ensure_system_message(events)
|
||||
self._ensure_initial_user_message(events, initial_user_action)
|
||||
self._ensure_initial_user_message(
|
||||
events, initial_user_action, forgotten_event_ids
|
||||
)
|
||||
|
||||
# log visual browsing status
|
||||
logger.debug(f'Visual browsing: {self.agent_config.enable_som_visual_browsing}')
|
||||
@@ -827,9 +835,23 @@ class ConversationMemory:
|
||||
)
|
||||
|
||||
def _ensure_initial_user_message(
|
||||
self, events: list[Event], initial_user_action: MessageAction
|
||||
self,
|
||||
events: list[Event],
|
||||
initial_user_action: MessageAction,
|
||||
forgotten_event_ids: set[int],
|
||||
) -> None:
|
||||
"""Checks if the second event is a user MessageAction and inserts the provided one if needed."""
|
||||
"""Checks if the second event is a user MessageAction and inserts the provided one if needed.
|
||||
|
||||
IMPORTANT: If the initial user action has been condensed (its ID is in forgotten_event_ids),
|
||||
we do NOT re-insert it. This prevents old instructions from being re-executed after
|
||||
conversation condensation. The condensation summary already contains the context of
|
||||
what was requested and completed.
|
||||
|
||||
Args:
|
||||
events: The list of events to modify in-place
|
||||
initial_user_action: The initial user message action from the full history
|
||||
forgotten_event_ids: Set of event IDs that have been forgotten/condensed
|
||||
"""
|
||||
if (
|
||||
not events
|
||||
): # Should have system message from previous step, but safety check
|
||||
@@ -837,6 +859,17 @@ class ConversationMemory:
|
||||
# Or raise? Let's log for now, _ensure_system_message should handle this.
|
||||
return
|
||||
|
||||
# Check if the initial user action has been condensed/forgotten.
|
||||
# If so, we should NOT re-insert it to prevent re-execution of old instructions.
|
||||
# The condensation summary already contains the context of what was requested.
|
||||
initial_user_action_id = initial_user_action.id
|
||||
if initial_user_action_id in forgotten_event_ids:
|
||||
logger.info(
|
||||
f'Initial user action (id={initial_user_action_id}) has been condensed. '
|
||||
'Not re-inserting to prevent re-execution of old instructions.'
|
||||
)
|
||||
return
|
||||
|
||||
# We expect events[0] to be SystemMessageAction after _ensure_system_message
|
||||
if len(events) == 1:
|
||||
# Only system message exists
|
||||
|
||||
@@ -18,6 +18,8 @@ class View(BaseModel):
|
||||
|
||||
events: list[Event]
|
||||
unhandled_condensation_request: bool = False
|
||||
# Set of event IDs that have been forgotten/condensed
|
||||
forgotten_event_ids: set[int] = set()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.events)
|
||||
@@ -90,4 +92,5 @@ class View(BaseModel):
|
||||
return View(
|
||||
events=kept_events,
|
||||
unhandled_condensation_request=unhandled_condensation_request,
|
||||
forgotten_event_ids=forgotten_event_ids,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user