Skip to content

Add a turn/response group identifier to streaming events #4793

Closed

Description

@ferponse

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

  1. added
    live[Component] This issue is related to live, voice and video chat
    on Mar 12, 2026
  2. added theissue type on Mar 12, 2026
  3. klateefa commented on Mar 13, 2026

    @klateefa

    @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.

  4. added
    needs review[Status] The PR/issue is awaiting review from the maintainer
    on Mar 13, 2026
  5. ilyesJammouci commented on Mar 16, 2026

    @ilyesJammouci

    Hello @klateefa
    I’ve started working on this issue.
    So far I’ve added a turn_id field 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.

  6. added
    core[Component] This issue is related to the core interface and implementation
    and removed
    live[Component] This issue is related to live, voice and video chat
    on Jun 22, 2026
  7. self-assigned this
    on Sep 22, 2026
  8. GWeale commented on Sep 22, 2026

    @GWeale
    Collaborator

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementationneeds review[Status] The PR/issue is awaiting review from the maintainer

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions