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

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

未關閉
#2,176 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
bug Integration
主要語言
Python
星號
15.9k
分支
1.4k
平均合併
1 天 17 小時
30 天內合併 PR
163

描述

## 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).

貢獻指南

開啟貢獻指南

評估

這個 Issue 還沒有評估資料。

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。