Repository navigation
Add a turn/response group identifier to streaming events #4793
Description
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:
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
Activity
klateefa commentedon Mar 13, 2026
@ferponse Thanks for the update. The request is currently being reviewed, and I will let you know if anything further is needed. Thank you for understanding.
ilyesJammouci commentedon Mar 16, 2026
Hello @klateefa
I’ve started working on this issue.
So far I’ve added a
turn_idfield that is generated for each LLM call. I’m currently working on propagating this attribute through the streaming event pipeline so that it remains consistent across all partial chunks and the final event of the same LLM response.The propagation part is nearly finished. I’m just making sure it flows correctly through the relevant components before opening a PR.
GWeale commentedon Sep 22, 2026
fixed in 8f98bcd, shipped in v2.6.0. every partial chunk of one llm call and its aggregated final event now carry the same event.id, and the id changes only after a complete event, so text before a tool call and text after it get different ids. grouping by event.id gives the per-turn buckets you described. a separate turn_id field was declined on #5307 because it would change the stored Event schema. the 1.x line does not have this. open a new issue if you want us to look at it again.