2
2
3
3
from abc import ABC , abstractmethod
4
4
from contextlib import contextmanager
5
- from typing import Any , overload
5
+ from typing import Any
6
6
7
7
from pydantic import BaseModel
8
8
9
9
from openhands .controller .state .state import State
10
10
from openhands .core .config .condenser_config import CondenserConfig
11
11
from openhands .events .action .agent import CondensationAction
12
- from openhands .events .event import Event
13
- from openhands .events .observation .agent import AgentCondensationObservation
12
+ from openhands .memory .view import View
14
13
15
14
CONDENSER_METADATA_KEY = 'condenser_meta'
16
15
"""Key identifying where metadata is stored in a `State` object's `extra_data` field."""
@@ -34,69 +33,6 @@ def get_condensation_metadata(state: State) -> list[dict[str, Any]]:
34
33
"""Registry of condenser configurations to their corresponding condenser classes."""
35
34
36
35
37
- class View (BaseModel ):
38
- """Linearly ordered view of events.
39
-
40
- Produced by a condenser to indicate the included events are ready to process as LLM input.
41
- """
42
-
43
- events : list [Event ]
44
-
45
- def __len__ (self ) -> int :
46
- return len (self .events )
47
-
48
- def __iter__ (self ):
49
- return iter (self .events )
50
-
51
- # To preserve list-like indexing, we ideally support slicing and position-based indexing.
52
- # The only challenge with that is switching the return type based on the input type -- we
53
- # can mark the different signatures for MyPy with `@overload` decorators.
54
-
55
- @overload
56
- def __getitem__ (self , key : slice ) -> list [Event ]: ...
57
-
58
- @overload
59
- def __getitem__ (self , key : int ) -> Event : ...
60
-
61
- def __getitem__ (self , key : int | slice ) -> Event | list [Event ]:
62
- if isinstance (key , slice ):
63
- start , stop , step = key .indices (len (self ))
64
- return [self [i ] for i in range (start , stop , step )]
65
- elif isinstance (key , int ):
66
- return self .events [key ]
67
- else :
68
- raise ValueError (f'Invalid key type: { type (key )} ' )
69
-
70
- @staticmethod
71
- def from_events (events : list [Event ]) -> View :
72
- """Create a view from a list of events, respecting the semantics of any condensation events."""
73
- forgotten_event_ids : set [int ] = set ()
74
- for event in events :
75
- if isinstance (event , CondensationAction ):
76
- forgotten_event_ids .update (event .forgotten )
77
-
78
- kept_events = [event for event in events if event .id not in forgotten_event_ids ]
79
-
80
- # If we have a summary, insert it at the specified offset.
81
- summary : str | None = None
82
- summary_offset : int | None = None
83
-
84
- # The relevant summary is always in the last condensation event (i.e., the most recent one).
85
- for event in reversed (events ):
86
- if isinstance (event , CondensationAction ):
87
- if event .summary is not None and event .summary_offset is not None :
88
- summary = event .summary
89
- summary_offset = event .summary_offset
90
- break
91
-
92
- if summary is not None and summary_offset is not None :
93
- kept_events .insert (
94
- summary_offset , AgentCondensationObservation (content = summary )
95
- )
96
-
97
- return View (events = kept_events )
98
-
99
-
100
36
class Condensation (BaseModel ):
101
37
"""Produced by a condenser to indicate the history has been condensed."""
102
38
@@ -150,13 +86,13 @@ def metadata_batch(self, state: State):
150
86
self .write_metadata (state )
151
87
152
88
@abstractmethod
153
- def condense (self , events : list [ Event ] ) -> View | Condensation :
89
+ def condense (self , View ) -> View | Condensation :
154
90
"""Condense a sequence of events into a potentially smaller list.
155
91
156
92
New condenser strategies should override this method to implement their own condensation logic. Call `self.add_metadata` in the implementation to record any relevant per-condensation diagnostic information.
157
93
158
94
Args:
159
- events : A list of events representing the entire history of the agent .
95
+ View : A view of the history containing all events that should be condensed .
160
96
161
97
Returns:
162
98
View | Condensation: A condensed view of the events or an event indicating the history has been condensed.
@@ -165,7 +101,7 @@ def condense(self, events: list[Event]) -> View | Condensation:
165
101
def condensed_history (self , state : State ) -> View | Condensation :
166
102
"""Condense the state's history."""
167
103
with self .metadata_batch (state ):
168
- return self .condense (state .history )
104
+ return self .condense (state .view )
169
105
170
106
@classmethod
171
107
def register_config (cls , configuration_type : type [CondenserConfig ]) -> None :
@@ -221,10 +157,7 @@ def should_condense(self, view: View) -> bool:
221
157
def get_condensation (self , view : View ) -> Condensation :
222
158
"""Get the condensation from a view."""
223
159
224
- def condense (self , events : list [Event ]) -> View | Condensation :
225
- # Convert the state to a view. This might require some condenser-specific logic.
226
- view = View .from_events (events )
227
-
160
+ def condense (self , view : View ) -> View | Condensation :
228
161
# If we trigger the condenser-specific condensation threshold, compute and return
229
162
# the condensation.
230
163
if self .should_condense (view ):
0 commit comments