google / google/adk-python

Add a turn/response group identifier to streaming events

Aperta
#4,793 2 commenti 0 reazioni 2 assegnatari Rivendicata da @wuliang229 Vedi su GitHub
core needs review
Lingua principale
Python
Stelle
21.5k
Fork
4k
Merge medio
1g 14h
PR unite (30g)
37

Descrizione

# Add a turn/response group identifier to streaming events

## Problem

When using `runner.run_async()` with `StreamingMode.SSE`, there is no way to distinguish which partial streaming chunks belong to which LLM response turn within a single invocation.

In a typical flow where the LLM responds with text, calls a tool, and then responds with more text, the events look like this:

```
Event(partial=True, text="Looking up...") # LLM call 1
Event(partial=True, text="Looking up info...") # LLM call 1
Event(partial=False, function_call=...) # LLM call 1 (final)
Event(function_response=...) # Tool result
Event(partial=True, text="Here are the...") # LLM call 2
Event(partial=True, text="Here are the results...") # LLM call 2
Event(partial=False, text="Here are the results...") # LLM call 2 (final)
```

All of these events share the same `invocation_id`, and the `id` field changes on every yield (not per turn). There is no field to programmatically determine that the first two partial chunks belong to LLM call 1 and the last two belong to LLM call 2.

## Current workaround

The only way to detect turn boundaries is by observing transition patterns: a `partial=False` event followed by function call/response events implicitly signals the end of one text group. This is fragile and requires the consumer to maintain state tracking.

## Proposed solution

Add a `turn_id` (or `llm_call_id` / `response_group_id`) field to `Event` that:

- Is generated once per LLM call within `_run_one_step_async`
- Remains the same across all partial chunks and the final aggregated event of that LLM call
- Changes when a new LLM call starts (e.g., after tool execution)

This would allow consumers to trivially group streaming chunks by turn:

```python
async for event in runner.run_async(...):
if event.partial:
# event.turn_id groups this chunk with others from the same LLM call
buffer[event.turn_id] += extract_text(event)
```

## Use case

We build a chat UI that streams partial text to the user via WebSocket. When the agent produces text, calls a tool, and then produces more text, we need to render them as separate message bubbles. Without a turn identifier, we cannot cleanly separate the two text blocks on the client side without implementing brittle heuristics based on event type transitions.

## Environment

- ADK version: latest (pip)
- Streaming mode: SSE
- Python 3.12

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.