mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python A2A: Expose supported_protocol_bindings as configurable parameter (#6098)
* Expose supported_protocol_bindings as configurable parameter on A2AAgent Add supported_protocol_bindings parameter to A2AAgent.__init__() allowing users to configure which A2A protocol bindings (JSONRPC, GRPC, HTTP+JSON) the client prefers when connecting to remote agents. - Defaults to ["JSONRPC"] matching current behavior - Passes through to ClientConfig for transport negotiation - Replaces 4 hardcoded references with the configurable value Closes #6057 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix empty list falsy trap and add fallback path test coverage - Use 'is not None' check instead of 'or' to preserve explicit empty list - Add test verifying empty list is not silently replaced with defaults - Add test verifying fallback path uses custom bindings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document known protocol binding values in docstring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use Literal union for protocol binding type hint Provides IDE autocomplete for known values while keeping the type open for custom bindings (Literal is str at runtime). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
d2f79930d5
commit
e9a606344a
@@ -703,7 +703,94 @@ def test_a2a_agent_initialization_with_timeout_parameter() -> None:
|
||||
assert isinstance(timeout_arg, httpx.Timeout)
|
||||
|
||||
|
||||
# region Continuation Token Tests
|
||||
def test_a2a_agent_initialization_with_supported_protocol_bindings() -> None:
|
||||
"""Test A2AAgent initialization with custom supported_protocol_bindings."""
|
||||
with (
|
||||
patch("agent_framework_a2a._agent.httpx.AsyncClient") as mock_async_client,
|
||||
patch("agent_framework_a2a._agent.ClientConfig") as mock_config,
|
||||
patch("agent_framework_a2a._agent.ClientFactory") as mock_factory,
|
||||
):
|
||||
mock_async_client.return_value = MagicMock()
|
||||
mock_client_instance = MagicMock()
|
||||
mock_factory.return_value.create.return_value = mock_client_instance
|
||||
|
||||
A2AAgent(
|
||||
name="Test Agent",
|
||||
url="https://test-agent.example.com",
|
||||
supported_protocol_bindings=["GRPC", "JSONRPC"],
|
||||
)
|
||||
|
||||
# Verify ClientConfig was called with our custom bindings for both streaming and non-streaming
|
||||
assert mock_config.call_count == 2
|
||||
for call in mock_config.call_args_list:
|
||||
assert call.kwargs["supported_protocol_bindings"] == ["GRPC", "JSONRPC"]
|
||||
|
||||
|
||||
def test_a2a_agent_initialization_defaults_to_jsonrpc() -> None:
|
||||
"""Test A2AAgent defaults to JSONRPC when supported_protocol_bindings is not provided."""
|
||||
with (
|
||||
patch("agent_framework_a2a._agent.httpx.AsyncClient") as mock_async_client,
|
||||
patch("agent_framework_a2a._agent.ClientConfig") as mock_config,
|
||||
patch("agent_framework_a2a._agent.ClientFactory") as mock_factory,
|
||||
):
|
||||
mock_async_client.return_value = MagicMock()
|
||||
mock_client_instance = MagicMock()
|
||||
mock_factory.return_value.create.return_value = mock_client_instance
|
||||
|
||||
A2AAgent(name="Test Agent", url="https://test-agent.example.com")
|
||||
|
||||
# Verify ClientConfig was called with default JSONRPC bindings
|
||||
assert mock_config.call_count == 2
|
||||
for call in mock_config.call_args_list:
|
||||
assert call.kwargs["supported_protocol_bindings"] == ["JSONRPC"]
|
||||
|
||||
|
||||
def test_a2a_agent_initialization_empty_list_preserved() -> None:
|
||||
"""Test that an explicit empty list is preserved and not replaced with defaults."""
|
||||
with (
|
||||
patch("agent_framework_a2a._agent.httpx.AsyncClient") as mock_async_client,
|
||||
patch("agent_framework_a2a._agent.ClientConfig") as mock_config,
|
||||
patch("agent_framework_a2a._agent.ClientFactory") as mock_factory,
|
||||
):
|
||||
mock_async_client.return_value = MagicMock()
|
||||
mock_client_instance = MagicMock()
|
||||
mock_factory.return_value.create.return_value = mock_client_instance
|
||||
|
||||
A2AAgent(
|
||||
name="Test Agent",
|
||||
url="https://test-agent.example.com",
|
||||
supported_protocol_bindings=[],
|
||||
)
|
||||
|
||||
# Verify ClientConfig was called with the explicit empty list, not the default
|
||||
assert mock_config.call_count == 2
|
||||
for call in mock_config.call_args_list:
|
||||
assert call.kwargs["supported_protocol_bindings"] == []
|
||||
|
||||
|
||||
def test_a2a_agent_fallback_uses_custom_bindings() -> None:
|
||||
"""Test that transport fallback path uses custom bindings."""
|
||||
mock_agent_card = MagicMock()
|
||||
mock_agent_card.supported_interfaces = [MagicMock(url="https://fallback.example.com")]
|
||||
|
||||
mock_factory = MagicMock()
|
||||
# First create() call fails (primary streaming), then fallback calls succeed
|
||||
primary_error = Exception("no compatible transports found")
|
||||
mock_factory.create.side_effect = [primary_error, MagicMock(), MagicMock()]
|
||||
|
||||
with (
|
||||
patch("agent_framework_a2a._agent.ClientFactory", return_value=mock_factory),
|
||||
patch("agent_framework_a2a._agent.minimal_agent_card") as mock_minimal_card,
|
||||
patch("agent_framework_a2a._agent.httpx.AsyncClient"),
|
||||
):
|
||||
A2AAgent(
|
||||
name="test-agent",
|
||||
agent_card=mock_agent_card,
|
||||
supported_protocol_bindings=["GRPC", "HTTP+JSON"],
|
||||
)
|
||||
|
||||
# Verify minimal_agent_card was called with the custom bindings
|
||||
mock_minimal_card.assert_called_once_with("https://fallback.example.com", ["GRPC", "HTTP+JSON"])
|
||||
|
||||
|
||||
async def test_working_task_emits_continuation_token(a2a_agent: A2AAgent, mock_a2a_client: MockA2AClient) -> None:
|
||||
|
||||
Reference in New Issue
Block a user