google / google/adk-python

Parallel calls to the same streaming tool leave an orphan task after live teardown

Đang mở
#7,058 2 bình luận 0 reaction 1 người được giao Được @llalitkumarrr nhận Xem trên GitHub
live request clarification
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ả

## Required Information

**Describe the Bug:**

When one live model response contains multiple calls to the same async-generator tool, ADK starts each call concurrently but stores only one task under `InvocationContext.active_streaming_tools[tool.name]`. Each registration overwrites the previous `task` reference. As a result, run teardown and `stop_streaming(function_name)` can only cancel the last registered call; earlier calls continue writing to the live request queue after their agent run has ended.

The violated lifecycle invariant is that every background tool task started by a live agent run must remain tracked until it completes or the run tears it down.

**Steps to Reproduce:**

1. Create an async-generator `FunctionTool` that yields once and then waits.
2. Build one live function-call event containing two calls to that tool with different call IDs/arguments.
3. Pass the event through `handle_function_calls_live()`.
4. Wait until both tool tasks have started.
5. Call `SingleFlow()._stop_background_tool_tasks(invocation_context)`.
6. Inspect both tasks.

**Expected Behavior:**

Both calls are tracked and cancelled during live-run teardown.

**Observed Behavior:**

Only the task currently stored in `active_streaming_tools[tool.name].task` is cancelled. The other task remains pending. This reproduces consistently on current `main` (`b0180620f4c2f4f4467a89c37a30f75bf849700b`).

**Environment Details:**

- ADK Library Version: current `main` at `b0180620`
- Desktop OS: macOS
- Python Version: 3.10.20

**Model Information:**

- Are you using LiteLLM: No
- Which model is being used: N/A (unit-level live function-call event)

## Additional Context

The overwrite occurs in `_process_function_live_helper()` when it assigns a newly created task to `active_streaming_tools[tool.name].task`. `stop_background_tool_tasks()` later enumerates one `task` per registry entry, so it has no reference to the earlier call. Non-blocking live tools avoid the same collision by including the function-call ID in their task key.

I intend to keep all concurrent tasks for a streaming tool tracked while preserving the existing name-based `stop_streaming` behavior, and add a regression test through the real live function-call path. I can send the focused fix and test within 24 hours.

**Regression:**

Not confirmed.

**Logs:**

```text
assert all(task.done() for task in tasks)
E assert False
```

**Minimal Reproduction Code:**

```python
await handle_function_calls_live(
invocation_context,
event_with_two_calls_to_monitor,
{monitor_tool.name: monitor_tool},
)
await both_started.wait()
await SingleFlow()._stop_background_tool_tasks(invocation_context)
assert all(task.done() for task in started_tasks) # fails: one is still pending
```

**How often has this issue occurred?:**

- Always (100%)

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.