@@ -111,3 +111,87 @@ def __str__(self) -> str:
111
111
ret = '**RecallAction**\n '
112
112
ret += f'QUERY: { self .query [:50 ]} '
113
113
return ret
114
+
115
+
116
+ @dataclass
117
+ class CondensationAction (Action ):
118
+ """This action indicates a condensation of the conversation history is happening.
119
+
120
+ There are two ways to specify the events to be forgotten:
121
+ 1. By providing a list of event IDs.
122
+ 2. By providing the start and end IDs of a range of events.
123
+
124
+ In the second case, we assume that event IDs are monotonically increasing, and that _all_ events between the start and end IDs are to be forgotten.
125
+
126
+ Raises:
127
+ ValueError: If the optional fields are not instantiated in a valid configuration.
128
+ """
129
+
130
+ action : str = ActionType .CONDENSATION
131
+
132
+ forgotten_event_ids : list [int ] | None = None
133
+ """The IDs of the events that are being forgotten (removed from the `View` given to the LLM)."""
134
+
135
+ forgotten_events_start_id : int | None = None
136
+ """The ID of the first event to be forgotten in a range of events."""
137
+
138
+ forgotten_events_end_id : int | None = None
139
+ """The ID of the last event to be forgotten in a range of events."""
140
+
141
+ summary : str | None = None
142
+ """An optional summary of the events being forgotten."""
143
+
144
+ summary_offset : int | None = None
145
+ """An optional offset to the start of the resulting view indicating where the summary should be inserted."""
146
+
147
+ def _validate_field_polymorphism (self ) -> bool :
148
+ """Check if the optional fields are instantiated in a valid configuration."""
149
+ # For the forgotton events, there are only two valid configurations:
150
+ # 1. We're forgetting events based on the list of provided IDs, or
151
+ using_event_ids = self .forgotten_event_ids is not None
152
+ # 2. We're forgetting events based on the range of IDs.
153
+ using_event_range = (
154
+ self .forgotten_events_start_id is not None
155
+ and self .forgotten_events_end_id is not None
156
+ )
157
+
158
+ # Either way, we can only have one of the two valid configurations.
159
+ forgotten_event_configuration = using_event_ids ^ using_event_range
160
+
161
+ # We also need to check that if the summary is provided, so is the
162
+ # offset (and vice versa).
163
+ summary_configuration = (
164
+ self .summary is None and self .summary_offset is None
165
+ ) or (self .summary is not None and self .summary_offset is not None )
166
+
167
+ return forgotten_event_configuration and summary_configuration
168
+
169
+ def __post_init__ (self ):
170
+ if not self ._validate_field_polymorphism ():
171
+ raise ValueError ('Invalid configuration of the optional fields.' )
172
+
173
+ @property
174
+ def forgotten (self ) -> list [int ]:
175
+ """The list of event IDs that should be forgotten."""
176
+ # Start by making sure the fields are instantiated in a valid
177
+ # configuration. We check this whenever the event is initialized, but we
178
+ # can't make the dataclass immutable so we need to check it again here
179
+ # to make sure the configuration is still valid.
180
+ if not self ._validate_field_polymorphism ():
181
+ raise ValueError ('Invalid configuration of the optional fields.' )
182
+
183
+ if self .forgotten_event_ids is not None :
184
+ return self .forgotten_event_ids
185
+
186
+ # If we've gotten this far, the start/end IDs are not None.
187
+ assert self .forgotten_events_start_id is not None
188
+ assert self .forgotten_events_end_id is not None
189
+ return list (
190
+ range (self .forgotten_events_start_id , self .forgotten_events_end_id + 1 )
191
+ )
192
+
193
+ @property
194
+ def message (self ) -> str :
195
+ if self .summary :
196
+ return f'Summary: { self .summary } '
197
+ return f'Condenser is dropping the events: { self .forgotten } .'
0 commit comments