google / google/adk-python

"Stop generating" — ability to stop `run_async()` from outside the agent

オープン
#4,796 コメント 8 件 リアクション 3 件 担当者 2 名 @wuliang229 が担当を希望しています GitHub で見る
core needs review
主要言語
Python
スター
21.5k
フォーク
4k
平均マージ
1日 14時間
マージ済み PR(30日)
37

説明

## Feature request: "Stop generating" — ability to stop `run_async()` from outside the agent

### Problem

There is no supported way to stop an ongoing `run_async()` generation from outside the agent. This is a critical gap for any chat UI where users expect a "Stop generating" button.

### Current state (ADK v1.25.1)

| Mechanism | Can stop from outside? | Stops mid-stream? | Session stays consistent? |
|---|---|---|---|
| `InvocationContext.end_invocation` | No — only settable from callbacks/tools | No — only checked between steps | Yes |
| `break` from `async for` | Yes — Python async generator protocol | Partially — waits for next `yield` | Unknown — undocumented path |
| `asyncio.Task.cancel()` | Yes | Partially — at next `await` | Unknown — `CancelledError` only handled in MCP cleanup |
| `RunConfig.max_llm_calls` | No — safety limit, not real-time | No | Yes |

None of these are designed as a "stop generation" mechanism for the consumer of `run_async()`.

### What I'd like

A way to signal "stop" from the code consuming `run_async()`, so the agent stops generating as soon as possible:

```python
# Option A: stop() on a run handle
run = runner.start_run(user_id="u1", session_id="s1", new_message=content)

async for event in run:
yield event

# From another coroutine (e.g., user presses "Stop generating"):
run.stop()
```

```python
# Option B: pass a stop signal to run_async()
stop_signal = StopSignal()

async for event in runner.run_async(
user_id="u1",
session_id="s1",
new_message=content,
stop_signal=stop_signal,
):
yield event

# From another coroutine:
stop_signal.stop()
```

### Expected behavior

1. The agent should stop **as soon as possible**: between streaming chunks in SSE mode, before the next tool call, or before the next LLM call.
2. Already-yielded events and their `state_delta` should be **persisted normally** — the session must remain consistent for the next user message.
3. A **final event** should be yielded to let the consumer know the generation was stopped (e.g., `interrupted=True` on the last event, or a dedicated stop event). The `interrupted` field already exists on `LlmResponse` for bidi streaming voice interruptions — the same concept applies here.
4. After stopping, the next call to `run_async()` on the same session should work normally.

### Use case

We run an ADK agent behind a WebSocket in a chat UI. The agent uses SSE streaming (`StreamingMode.SSE`). When the user presses "Stop generating":

1. The LLM should stop producing tokens
2. No further tool calls should be executed
3. The session should remain usable for the next message

This is standard behavior in any LLM-powered chat (ChatGPT, Gemini web, Claude) and is expected by users.

### Current workaround

We `break` from the `async for event in runner.run_async(...)` loop. This triggers `aclose()` → `GeneratorExit` propagation through the `Aclosing` wrappers in the chain (`Runner` → `BaseAgent` → `BaseLlmFlow` → `_run_one_step_async`). It works in practice, but:

- It is **not documented** as a supported pattern
- There is **no guarantee** ADK handles `GeneratorExit` gracefully in all code paths (mid-tool-execution, mid-auth-flow, mid-MCP-call)
- There is **no final event** emitted — the consumer cannot distinguish between "generation completed" and "generation was stopped"
- The **session consistency** after a forced generator close is not guaranteed

### Additional context

- `end_invocation` on `InvocationContext` is close to what's needed, but it's only accessible from inside the agent (callbacks/tools), not from the external consumer.
- Other frameworks provide this: LangGraph has cancellation tokens, OpenAI's API supports `abort()` on streams, Anthropic's SDK supports `controller.abort()`.

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。