fix(a2a): emit only new artifact parts on streaming artifact updates#6386
Open
Brumbelow wants to merge 1 commit into
Open
fix(a2a): emit only new artifact parts on streaming artifact updates#6386Brumbelow wants to merge 1 commit into
Brumbelow wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
Problem:
With
RemoteA2aAgenton the default (legacy) response handler, a streaming remote agent that sends an artifact in multiple chunks gets its first chunk duplicated in the response. The artifact-update branch in_handle_a2a_responseconverts the whole accumulated task —task.artifacts[-1]carries the merged parts of every chunk received so far — so a stream likeappend=False/last_chunk=False("Hello, ") followed byappend=True/last_chunk=True("world!") renders "Hello, Hello, world!". Streams with 3+ chunks additionally lose their middle chunks until the final re-emission, because the oldnot update.append or update.last_chunkfilter dropped them.This is why the issue only reproduces with
streaming=True, and whyuse_legacy=Falseworks around it: the v2 handler already converts only the update's own parts (convert_a2a_artifact_update_to_event).Solution:
Bring the legacy branch in line with v2: convert only the parts carried by the incoming
TaskArtifactUpdateEvent(via_compat.make_message, mirroring the message constructionconvert_a2a_task_to_eventitself uses), and skip updates that carry no parts. Each chunk is emitted exactly once, so the rendered and persisted content is the exact concatenation of what the server streamed. The(task, None)initial-response branch, status-update handling, task metadata stamping, and the v2 path are unchanged.Testing Plan
Unit Tests:
New
TestRemoteA2aAgentStreamingArtifactChunksdrives_handle_a2a_responsewith real converters and a two-chunk artifact stream, asserting the emitted text is exactly"Hello, world!"— on main it fails with"Hello, Hello, world!". A second case asserts an artifact update with no parts emits nothing. The existing artifact-update tests were updated to assert per-update conversion, and the stale "partial updates are ignored" test now verifies an appended chunk emits its own parts.pytest tests/unittests/agents/test_remote_a2a_agent.py— 109 passed (a2a-sdk 1.1.0)pytest tests/unittests/a2a tests/unittests/agents/test_remote_a2a_agent.py -n auto— 475 passed, 20 skipped (a2a-sdk 1.1.0)tests/unittests/integrations/agent_registryunder a2a-sdk 0.3.26 (mirrors theunit-test-a2a-v0-3CI job) — 525 passed, 17 skippedpytest tests/unittests/agents -n auto— 616 passed, 2 xfailedpre-commit runclean on touched files;mypyreports the same pre-existing errors as main (no new ones)Manual End-to-End (E2E) Tests:
Reproduced the report with a minimal harness feeding a chunked artifact stream —
(task, update)pairs exactly as the client yields them — through the legacy handler: main renders'Hello, Hello, world!', this branch renders'Hello, world!'.Checklist