google / google/adk-python

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

Abierto
#7,058 2 comentarios 0 reacciones 1 asignado Reclamado por @llalitkumarrr Ver en GitHub
live request clarification
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

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

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.