ag-ui-protocol / ag-ui-protocol/ag-ui

[Bug] adk_events_to_messages returns stale messages after rewind — rewound events are not filtered

Đang mở
#2,176 2 bình luận 0 reaction 0 người được giao Xem trên GitHub
bug Integration
Ngôn ngữ chính
Python
Star
15.9k
Fork
1.4k
Merge trung bình
1 ngày 17 giờ
Pull request đã merge (30 ngày)
163

Mô tả

## Summary

`adk_events_to_messages(session.events)` iterates all ADK session events without accounting for rewind markers. After `Runner.rewind_async()` is called, the session still contains the old (now-invalid) events — `rewind_async` appends a special event with `actions.rewind_before_invocation_id` instead of deleting the rewound events. Any consumer that calls `adk_events_to_messages` directly on `session.events` will therefore receive the stale messages that were supposed to be discarded.

## Reproduction

1. Start a session, send a message, get a response (turn 1).
2. Call `Runner.rewind_async(rewind_before_invocation_id=)`.
3. Send the same (or a different) message again (turn 2).
4. Call `adk_events_to_messages(session.events)`.

**Expected:** messages from turn 2 only (or turn 1 + turn 2 if only part of turn 1 was rewound).
**Actual:** messages from both the rewound turn 1 AND turn 2 — the stale messages reappear.

## Root cause

`Runner.rewind_async` does not delete events; it appends a rewind-marker event:

```python
rewind_event = Event(
invocation_id=new_invocation_context_id(),
author='user',
actions=EventActions(
rewind_before_invocation_id=rewind_before_invocation_id,
state_delta=state_delta,
artifact_delta=artifact_delta,
),
)
await self.session_service.append_event(session=session, event=rewind_event)
```

The ADK `Runner` itself correctly filters these markers before running the LLM (using an internal `_filter_rewound_events`-style pass). But `adk_events_to_messages` bypasses that filtering — it does not check `event.actions.rewind_before_invocation_id`.

## Impact

Any endpoint or component that reconstructs message history from `session.events` via `adk_events_to_messages` — notably the `POST /agents/state` endpoint registered by `add_adk_fastapi_endpoint` — will return incorrect history after a rewind. Frontend clients that use `/agents/state` to hydrate or display conversation history will show messages the user explicitly chose to discard.

## Suggested fix

Apply the same rewind-filtering pass that the ADK `Runner` uses internally before passing events to `adk_events_to_messages`:

```python
def filter_rewound_events(events: list) -> list:
filtered = []
i = len(events) - 1
while i >= 0:
event = events[i]
if event.actions and event.actions.rewind_before_invocation_id:
rewind_id = event.actions.rewind_before_invocation_id
for j in range(i):
if events[j].invocation_id == rewind_id:
i = j
break
else:
filtered.append(event)
i -= 1
filtered.reverse()
return filtered

# Then:
messages = adk_events_to_messages(filter_rewound_events(session.events))
```

Alternatively, `adk_events_to_messages` itself could apply this filtering internally so callers do not need to know about rewind semantics.

## Related

- #2154 — rewind / edit / regenerate at the protocol level (this bug is the history-consistency consequence of implementing rewind).

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.