agentscope-ai / agentscope-ai/agentscope

[Bug]: streamed tool data blocks lose identity during event replay

未關閉 適合新手
#2,549 6 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
主要語言
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

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

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