google / google/adk-python

Add `get_events()` and `filter_events()` methods to Session

Đang mở
#4,959 4 bình luận 0 reaction 1 người được giao Được @GWeale nhận Xem trên GitHub
needs review services
Ngôn ngữ chính
Python
Star
21.5k
Fork
4k
Merge trung bình
1 ngày 14 giờ
Pull request đã merge (30 ngày)
37

Mô tả

# 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

---

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.