Skip to content

Python: feat(a2a): use non-streaming transport and return_immediately for background ops#5963

Open
giles17 wants to merge 5 commits into
microsoft:mainfrom
giles17:a2a-non-streaming-transport
Open

Python: feat(a2a): use non-streaming transport and return_immediately for background ops#5963
giles17 wants to merge 5 commits into
microsoft:mainfrom
giles17:a2a-non-streaming-transport

Conversation

@giles17
Copy link
Copy Markdown
Contributor

@giles17 giles17 commented May 19, 2026

Motivation and Context

Resolves #5936

The Python A2A agent was always using SSE streaming transport (message/stream) regardless of whether the caller requested streaming. This means even a simple run('Hello') call opens an SSE connection instead of making a single HTTP POST to message/send. Additionally, when background=True, the server had no way to know it should return immediately.

Changes

Non-streaming transport selection

  • Create separate streaming and non-streaming internal clients (sharing the same httpx.AsyncClient connection pool) to match A2A protocol transport semantics
  • When stream=False, select the non-streaming client which uses message/send (single request/response)
  • When stream=True, use the streaming client which uses message/stream (SSE)
  • Graceful fallback to streaming client when a user provides their own client via the constructor

return_immediately configuration

  • Set SendMessageConfiguration(return_immediately=background) on outgoing SendMessageRequest
  • When background=True, the server is informed it should return the Task immediately without blocking

Why two clients?

The Python A2A SDK (a2a-sdk) bakes the transport choice (streaming vs non-streaming) into ClientConfig at client construction time — there's no per-call control. The .NET SDK exposes separate SendMessageAsync and SendStreamingMessageAsync methods on a single client. To achieve equivalent behavior in Python, we create two lightweight wrapper clients that share the same underlying HTTP connection pool.

Tests

  • Added tests verifying non-streaming client is used for stream=False
  • Added tests verifying streaming client is used for stream=True
  • Added test for fallback when non-streaming client unavailable
  • Added tests for return_immediately being set correctly
  • All 123 tests pass

Comparison with .NET

This matches the .NET implementation in A2AAgent.cs:

  • RunCoreAsyncSendMessageAsync (non-streaming) with ReturnImmediately
  • RunCoreStreamingAsyncSendStreamingMessageAsync (streaming)

…kground ops

When stream=False, use a client configured with streaming=False so the
SDK sends a single HTTP POST to message/send instead of opening an SSE
connection via message/stream. This matches the A2A protocol's design:
non-streaming calls use direct request/response, streaming calls use
Server-Sent Events.

Also sets return_immediately=background on SendMessageConfiguration so
the server respects the caller's intent for background operations.

Changes:
- Create separate streaming and non-streaming internal clients (sharing
  the same httpx connection pool) to match protocol transport semantics
- Select non-streaming client for run(stream=False) calls
- Add SendMessageConfiguration with return_immediately=background
- Fallback to streaming client when non-streaming unavailable (e.g. user
  provides their own client via constructor)
- Add tests for client selection and return_immediately behavior

Resolves microsoft#5936

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 19, 2026 22:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the Python A2A agent to use the correct transport for non-streaming calls and to signal background execution to the server via return_immediately, aligning behavior with A2A protocol intent and the .NET implementation.

Changes:

  • Instantiate both streaming and non-streaming internal A2A clients and select between them per run(stream=...).
  • Set SendMessageConfiguration(return_immediately=background) on outgoing SendMessageRequest.
  • Add tests covering transport selection, fallback behavior, and return_immediately configuration.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
python/packages/a2a/agent_framework_a2a/_agent.py Adds dual-client construction (streaming + non-streaming) and per-call client selection; attaches return_immediately to send requests.
python/packages/a2a/tests/test_a2a_agent.py Adds tests for client selection/fallback and background return_immediately; captures last request in the mock client.

Comment thread python/packages/a2a/tests/test_a2a_agent.py
Comment thread python/packages/a2a/agent_framework_a2a/_agent.py Outdated
Comment thread python/packages/a2a/agent_framework_a2a/_agent.py Outdated
@giles17 giles17 marked this pull request as draft May 19, 2026 22:25
@github-actions github-actions Bot changed the title feat(a2a): use non-streaming transport and return_immediately for background ops Python: feat(a2a): use non-streaming transport and return_immediately for background ops May 19, 2026
@moonbox3
Copy link
Copy Markdown
Contributor

moonbox3 commented May 19, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/a2a/agent_framework_a2a
   _agent.py2861993%176, 184, 378, 383, 385, 429–430, 583, 599, 607, 627, 648, 676, 692, 702, 713, 720–721, 762
TOTAL35220410388% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
6996 30 💤 0 ❌ 0 🔥 1m 47s ⏱️

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 81%

✓ Correctness

The PR correctly implements non-streaming transport selection and return_immediately configuration. The logic in active_client selection, constructor initialization paths, and the continuation_token/subscribe code path are all sound. The two issues flaged in the prior review thread (mock last_request not initialized in init, and using truthiness instead of is not None for client check) remain unresolved but no new correctness issues were found.

✓ Security Reliability

The changes are well-structured from a security and reliability perspective. Both streaming and non-streaming clients share the same httpx.AsyncClient connection pool, which is properly cleaned up in aexit. The _non_streaming_client attribute is correctly initialized on all successful construction paths (set to None when a user-provided client is used, or set via factory on the normal path). The existing unresolved review comments already cover the relevant defensive-coding concerns (explicit is-not-None check, last_request initialization, and conditional configuration). No new security or reliability issues found beyond those.

✓ Test Coverage

The new tests cover the key behavioral aspects well: client selection logic (streaming vs non-streaming), fallback behavior, and return_immediately configuration. However, the constructor path that creates dual clients via ClientFactory with streaming=True/False configs is entirely untested — all tests use the client= parameter which bypasses that code. The existing test_a2a_agent_initialization_with_timeout_parameter patches ClientFactory at class level, so it doesn't verify the two separate streaming configs are created correctly.

✗ Design Approach

The main design issue is in A2Agent initialization: the new constructor now makes creation of the optional non-streaming client part of the same required negotiation step as the streaming client. That means a server/configuration that can be used successfully via streaming can still fail agent construction entirely if only the non-streaming client cannot be created, even though the updated run path already treats a missing _non_streaming_client as a valid fallback.


Automated review by giles17's agents

Comment thread python/packages/a2a/tests/test_a2a_agent.py
giles17 and others added 3 commits May 19, 2026 18:18
- Initialize last_request in MockA2AClient.__init__ for explicit state
- Use 'is not None' instead of truthiness for _non_streaming_client check
- Assert return_immediately propagates through non-streaming client path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only attach SendMessageConfiguration to the request when background=True,
keeping requests minimal and preserving server-side defaults for normal
(foreground) operations. This follows the framework pattern of only
setting optional fields when they have meaningful values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@giles17 giles17 marked this pull request as ready for review May 20, 2026 15:28
Comment thread python/packages/a2a/agent_framework_a2a/_agent.py
Comment thread python/packages/a2a/agent_framework_a2a/_agent.py
Per the A2A spec, return_immediately only applies to message/send
(non-streaming). It has no effect on streaming operations. Only set
the configuration field when both background=True and stream=False.

Adds test verifying streaming+background does not set return_immediately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [A2A] Respect server-side blocking=false instead of local polling

4 participants