agentscope-ai / agentscope-ai/agentscope
[Bug]: streamed tool data blocks lose identity during event replay
- 主要語言
- Python
- 星號
- 31.6k
- 分支
- 3.5k
- 平均合併
- 1 天 16 小時
- 30 天內合併 PR
- 103
描述
### Prerequisites
- [x] I searched existing issues, pull requests, discussions, and recent history for this symptom and root cause.
- [x] This is a reproducible bug rather than a usage question.
### Background / Description
A streaming tool can return multiple `DataBlock` chunks with the same block ID. `ToolChunk` explicitly uses that stable ID to group one multimodal result, and `ToolResponse.append_chunk` merges same-ID Base64 chunks by decoded bytes.
The Agent event path loses that identity. `_convert_tool_chunk_to_event` creates each `ToolResultDataDeltaEvent` without passing `block.id`, so every event receives a new generated `block_id`. `Msg.append_event` then appends every data delta as a separate `DataBlock`.
As a result, the canonical tool response / Agent context contains one complete block, while the persisted reply and SSE-reconstructed message contain multiple partial blocks with unrelated IDs. Audio, file, or image chunks can therefore be exposed as separate incomplete resources after persistence or replay.
### Reproduction
On current `main` at `f8b40f60`, use two independently Base64-encoded chunks with the same source block ID:
```python
chunks = [
DataBlock(
id="audio-1",
source=Base64Source(
data=base64.b64encode(payload).decode("ascii"),
media_type="audio/wav",
),
)
for payload in (b"hello", b"world")
]
```
Pass both through `Agent._convert_tool_chunk_to_event`, apply the resulting events to an `AssistantMsg` between `ToolResultStartEvent` and `ToolResultEndEvent`, and inspect the reconstructed tool result.
Observed before a fix:
```text
event block IDs: two different generated IDs
reconstructed block IDs: the same two generated IDs
reconstructed payloads: [b"hello", b"world"]
```
Expected, matching `ToolResponse.append_chunk`:
```text
event block IDs: ["audio-1", "audio-1"]
reconstructed block IDs: ["audio-1"]
reconstructed payloads: [b"helloworld"]
```
The regression test fails deterministically on current `main`; no timing or external service is involved.
### Root Cause
1. `Agent._convert_tool_chunk_to_event` omits `block_id=block.id` for both Base64 and URL `DataBlock` sources.
2. `Msg.append_event` handles every `TOOL_RESULT_DATA_DELTA` by appending a new block and has no same-ID Base64 accumulation path.
This violates the cross-layer invariant that replaying emitted events must reconstruct the same block boundaries, IDs, and bytes as the canonical `ToolResponse` stored in Agent context.
### Proposed Direction
- Preserve `DataBlock.id` when producing both Base64 and URL `ToolResultDataDeltaEvent` instances.
- When replaying a Base64 data delta into a tool result, merge it with an existing same-ID Base64 block by decoding both chunks, concatenating bytes, and re-encoding. This should mirror the behavior introduced for `ToolResponse.append_chunk` in #1901.
- Keep URL blocks one-shot and leave unrelated event behavior unchanged.
- Add regression coverage for same-ID padded Base64 chunks and URL ID preservation.
I have validated this focused direction locally with the new regression coverage plus `event_to_message_test.py` and `toolkit_test.py` (26 tests), and all file-level pre-commit hooks. I can prepare the focused PR after maintainer confirmation.
### Environment
- AgentScope Version: `2.0.8`
- Commit: `f8b40f60`
- Python Version: `3.11.16`
- OS: macOS
貢獻指南
研究方向
Start by locating `Agent._convert_tool_chunk_to_event` and `Msg.append_event` to see how `ToolResultDataDeltaEvent` is created and how TOOL_RESULT_DATA_DELTA is appended. Compare the event path with `ToolResponse.append_chunk` behavior for same-ID Base64 chunks. Then run or extend `event_to_message_test.py` and `toolkit_test.py` with same-ID chunk and URL ID-preservation cases; done means replayed events reconstruct a single merged block with original IDs and bytes for matching chunk IDs.
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- backend, testing
- Issue 類型
- 缺陷
- 難度
- 2/5
- 預估耗時
- 1-3 小時
- 活躍度
- 活躍
- 描述清晰度
- 描述清楚
- 新手友好度
- 74/100