mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Calvin Smith <calvin@all-hands.dev>
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import overload
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from openhands.core.logger import openhands_logger as logger
|
|
from openhands.events.action.agent import CondensationAction
|
|
from openhands.events.event import Event
|
|
from openhands.events.observation.agent import AgentCondensationObservation
|
|
|
|
|
|
class View(BaseModel):
|
|
"""Linearly ordered view of events.
|
|
|
|
Produced by a condenser to indicate the included events are ready to process as LLM input.
|
|
"""
|
|
|
|
events: list[Event]
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.events)
|
|
|
|
def __iter__(self):
|
|
return iter(self.events)
|
|
|
|
# To preserve list-like indexing, we ideally support slicing and position-based indexing.
|
|
# The only challenge with that is switching the return type based on the input type -- we
|
|
# can mark the different signatures for MyPy with `@overload` decorators.
|
|
|
|
@overload
|
|
def __getitem__(self, key: slice) -> list[Event]: ...
|
|
|
|
@overload
|
|
def __getitem__(self, key: int) -> Event: ...
|
|
|
|
def __getitem__(self, key: int | slice) -> Event | list[Event]:
|
|
if isinstance(key, slice):
|
|
start, stop, step = key.indices(len(self))
|
|
return [self[i] for i in range(start, stop, step)]
|
|
elif isinstance(key, int):
|
|
return self.events[key]
|
|
else:
|
|
raise ValueError(f'Invalid key type: {type(key)}')
|
|
|
|
@staticmethod
|
|
def from_events(events: list[Event]) -> View:
|
|
"""Create a view from a list of events, respecting the semantics of any condensation events."""
|
|
forgotten_event_ids: set[int] = set()
|
|
for event in events:
|
|
if isinstance(event, CondensationAction):
|
|
forgotten_event_ids.update(event.forgotten)
|
|
# Make sure we also forget the condensation action itself
|
|
forgotten_event_ids.add(event.id)
|
|
|
|
kept_events = [event for event in events if event.id not in forgotten_event_ids]
|
|
|
|
# If we have a summary, insert it at the specified offset.
|
|
summary: str | None = None
|
|
summary_offset: int | None = None
|
|
|
|
# The relevant summary is always in the last condensation event (i.e., the most recent one).
|
|
for event in reversed(events):
|
|
if isinstance(event, CondensationAction):
|
|
if event.summary is not None and event.summary_offset is not None:
|
|
summary = event.summary
|
|
summary_offset = event.summary_offset
|
|
break
|
|
|
|
if summary is not None and summary_offset is not None:
|
|
logger.info(f'Inserting summary at offset {summary_offset}')
|
|
|
|
kept_events.insert(
|
|
summary_offset, AgentCondensationObservation(content=summary)
|
|
)
|
|
|
|
return View(events=kept_events)
|