Add `get_events()` and `filter_events()` methods to Session
- Lenguaje dominante
- Python
- Estrellas
- 21.5k
- Forks
- 4k
- Merge medio
- 1 d 14 h
- PR fusionados (30 d)
- 37
Descripción
# Feature Request: Add `get_events()` and `filter_events()` methods to Session
## Problem Statement
Currently, when working with sessions that have been rewound, there is no built-in way to filter out events that have been invalidated by rewind operations. This forces every consumer to implement their own filtering logic, leading to code duplication, inconsistency, and potential bugs.
## Current Situation
After the rewind feature was introduced in [9dce06f](https://github.com/google/adk-python/commit/9dce06f9b00259ec42241df4f6638955e783a9d1), `session.events` returns **all events**, including those that have been annulled by a rewind. When a rewind occurs, an event with `actions.rewind_before_invocation_id` is appended, but all previous events from that invocation onwards remain in the `events` list.
This means that when iterating over session events, we see "ghost" events that are no longer part of the active conversation flow.
### Example of the Problem
```python
# Session with events:
# - inv-1: user asks "What is Python?"
# - inv-1: agent responds "Python is a programming language"
# - inv-2: user asks "What is Java?"
# - inv-2: agent responds "Java is a programming language"
# - inv-rewind: rewind_before_invocation_id = "inv-2"
# - inv-3: user asks "What is JavaScript?"
# Current behavior - we see ALL events including the rewound inv-2:
for event in session.events:
print(event.content)
# Output shows 6 events, including the "ghost" inv-2 events
# What we want - only see active events:
for event in session.filter_events():
print(event.content)
# Output should show only 3 events: inv-1 and inv-3
```
## Why This Is Needed
### 1. **Code Duplication**
Every consumer needs to implement the same filtering logic. In our codebase, we had to implement:
```python
@staticmethod
def _filter_rewound_events(events: list) -> list:
"""Filter out events that have been annulled by a rewind."""
filtered = []
i = len(events) - 1
while i >= 0:
event = events[i]
if event.actions and event.actions.rewind_before_invocation_id:
rewind_invocation_id = event.actions.rewind_before_invocation_id
for j in range(0, i):
if events[j].invocation_id == rewind_invocation_id:
i = j
break
else:
filtered.append(event)
i -= 1
filtered.reverse()
return filtered
```
This is 15+ lines of non-trivial logic that every ADK consumer must write.
### 2. **Error-Prone**
The rewind filtering algorithm is complex and easy to get wrong:
- Must iterate backward through events
- Must handle multiple sequential rewinds
- Must maintain chronological order after filtering
- Edge cases (rewind to first invocation, missing target, etc.)
### 3. **Inconsistent Behavior**
Different consumers may implement filtering differently, leading to:
- Different interpretations of what "rewound" means
- Subtle bugs in edge cases
- Harder to debug issues across different codebases
### 4. **Breaking the Abstraction**
Consumers shouldn't need to understand the internal structure of rewind events (`actions.rewind_before_invocation_id`) to work with sessions. This is an implementation detail that should be hidden behind a clean API.
## Real-World Use Case
In our agent system, we need to:
1. **Display conversation history** to users - we only want to show active messages, not rewound ones
2. **Process events for analytics** - we need to count actual user interactions, excluding rewound attempts
3. **Build context for the LLM** - when constructing prompts, we only want to include active conversation history
Currently, we must manually filter events in all these places:
```python
# In our chat controller
for event in self._filter_rewound_events(session.events):
if event.author == "user":
conversation_history.append(self._extract_text(event))
# In our analytics service
active_events = self._filter_rewound_events(session.events)
user_messages = [e for e in active_events if e.author == "user"]
# In our context builder
for event in self._filter_rewound_events(session.events):
context += self._format_event(event)
```
## Proposed Solution
Add two methods to the `Session` class:
### `get_events() -> list[Event]`
Returns all events in the session (same as `session.events` but as a method for API consistency).
### `filter_events(*, exclude_rewound: bool = True) -> list[Event]`
Returns filtered events, with the primary use case being exclusion of rewound events.
```python
# Get only active events (default behavior)
for event in session.filter_events():
process(event)
# Get all events including rewound ones
for event in session.filter_events(exclude_rewound=False):
process_all(event)
```
## Benefits
1. **Cleaner code** - One-line filtering instead of 15+ lines
2. **Consistent behavior** - All consumers use the same filtering logic
3. **Fewer bugs** - Centralized, well-tested implementation
4. **Better abstraction** - Hides implementation details of rewind
5. **Backward compatible** - `session.events` still works as before
## Additional Context
- The rewind feature is relatively new and not all consumers may be aware of the need to filter
- `GetSessionConfig` provides `num_recent_events` and `after_timestamp` filtering, but no way to filter rewound events
- This pattern (filtering by state) is common in other session-based frameworks
## Related Pull Request
I've already implemented this feature and submitted a PR: https://github.com/google/adk-python/pull/4960
---
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.