Skip to content

Commit 2d3ba95

Browse files
giles17dmytrostruk
andauthored
Python: Add configurable timeout support to A2AAgent (#2432)
* a2a timeout config * added default timeout info --------- Co-authored-by: Dmytro Struk <[email protected]>
1 parent f5f909f commit 2d3ba95

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

python/packages/a2a/agent_framework_a2a/_agent.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def __init__(
8080
client: Client | None = None,
8181
http_client: httpx.AsyncClient | None = None,
8282
auth_interceptor: AuthInterceptor | None = None,
83+
timeout: float | httpx.Timeout | None = None,
8384
**kwargs: Any,
8485
) -> None:
8586
"""Initialize the A2AAgent.
@@ -93,10 +94,14 @@ def __init__(
9394
client: The A2A client for the agent.
9495
http_client: Optional httpx.AsyncClient to use.
9596
auth_interceptor: Optional authentication interceptor for secured endpoints.
97+
timeout: Request timeout configuration. Can be a float (applied to all timeout components),
98+
httpx.Timeout object (for full control), or None (uses 10.0s connect, 60.0s read,
99+
10.0s write, 5.0s pool - optimized for A2A operations).
96100
kwargs: any additional properties, passed to BaseAgent.
97101
"""
98102
super().__init__(id=id, name=name, description=description, **kwargs)
99103
self._http_client: httpx.AsyncClient | None = http_client
104+
self._timeout_config = self._create_timeout_config(timeout)
100105
if client is not None:
101106
self.client = client
102107
self._close_http_client = True
@@ -109,14 +114,8 @@ def __init__(
109114

110115
# Create or use provided httpx client
111116
if http_client is None:
112-
timeout = httpx.Timeout(
113-
connect=10.0, # 10 seconds to establish connection
114-
read=60.0, # 60 seconds to read response (A2A operations can take time)
115-
write=10.0, # 10 seconds to send request
116-
pool=5.0, # 5 seconds to get connection from pool
117-
)
118117
headers = prepend_agent_framework_to_user_agent()
119-
http_client = httpx.AsyncClient(timeout=timeout, headers=headers)
118+
http_client = httpx.AsyncClient(timeout=self._timeout_config, headers=headers)
120119
self._http_client = http_client # Store for cleanup
121120
self._close_http_client = True
122121

@@ -143,6 +142,32 @@ def __init__(
143142
f"Fallback error: {fallback_error}"
144143
) from transport_error
145144

145+
def _create_timeout_config(self, timeout: float | httpx.Timeout | None) -> httpx.Timeout:
146+
"""Create httpx.Timeout configuration from user input.
147+
148+
Args:
149+
timeout: User-provided timeout configuration
150+
151+
Returns:
152+
Configured httpx.Timeout object
153+
"""
154+
if timeout is None:
155+
# Default timeout configuration (preserving original values)
156+
return httpx.Timeout(
157+
connect=10.0, # 10 seconds to establish connection
158+
read=60.0, # 60 seconds to read response (A2A operations can take time)
159+
write=10.0, # 10 seconds to send request
160+
pool=5.0, # 5 seconds to get connection from pool
161+
)
162+
if isinstance(timeout, float):
163+
# Simple timeout
164+
return httpx.Timeout(timeout)
165+
if isinstance(timeout, httpx.Timeout):
166+
# Full timeout configuration provided by user
167+
return timeout
168+
msg = f"Invalid timeout type: {type(timeout)}. Expected float, httpx.Timeout, or None."
169+
raise TypeError(msg)
170+
146171
async def __aenter__(self) -> "A2AAgent":
147172
"""Async context manager entry."""
148173
return self

python/packages/a2a/tests/test_a2a_agent.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from unittest.mock import AsyncMock, MagicMock, patch
66
from uuid import uuid4
77

8+
import httpx
89
from a2a.types import (
910
AgentCard,
1011
Artifact,
@@ -554,3 +555,51 @@ def test_transport_negotiation_both_fail() -> None:
554555
name="test-agent",
555556
agent_card=mock_agent_card,
556557
)
558+
559+
560+
def test_create_timeout_config_httpx_timeout() -> None:
561+
"""Test _create_timeout_config with httpx.Timeout object returns it unchanged."""
562+
agent = A2AAgent(name="Test Agent", client=MockA2AClient(), http_client=None)
563+
564+
custom_timeout = httpx.Timeout(connect=15.0, read=180.0, write=20.0, pool=8.0)
565+
timeout_config = agent._create_timeout_config(custom_timeout)
566+
567+
assert timeout_config is custom_timeout # Same object reference
568+
assert timeout_config.connect == 15.0
569+
assert timeout_config.read == 180.0
570+
assert timeout_config.write == 20.0
571+
assert timeout_config.pool == 8.0
572+
573+
574+
def test_create_timeout_config_invalid_type() -> None:
575+
"""Test _create_timeout_config with invalid type raises TypeError."""
576+
agent = A2AAgent(name="Test Agent", client=MockA2AClient(), http_client=None)
577+
578+
with raises(TypeError, match="Invalid timeout type: <class 'str'>. Expected float, httpx.Timeout, or None."):
579+
agent._create_timeout_config("invalid")
580+
581+
582+
def test_a2a_agent_initialization_with_timeout_parameter() -> None:
583+
"""Test A2AAgent initialization with timeout parameter."""
584+
# Test with URL to trigger httpx client creation
585+
with (
586+
patch("agent_framework_a2a._agent.httpx.AsyncClient") as mock_async_client,
587+
patch("agent_framework_a2a._agent.ClientFactory") as mock_factory,
588+
):
589+
# Mock the factory and client creation
590+
mock_client_instance = MagicMock()
591+
mock_factory.return_value.create.return_value = mock_client_instance
592+
593+
# Create agent with custom timeout
594+
A2AAgent(name="Test Agent", url="https://test-agent.example.com", timeout=120.0)
595+
596+
# Verify httpx.AsyncClient was called with the configured timeout
597+
mock_async_client.assert_called_once()
598+
call_args = mock_async_client.call_args
599+
600+
# Check that timeout parameter was passed
601+
assert "timeout" in call_args.kwargs
602+
timeout_arg = call_args.kwargs["timeout"]
603+
604+
# Verify it's an httpx.Timeout object with our custom timeout applied to all components
605+
assert isinstance(timeout_arg, httpx.Timeout)

0 commit comments

Comments
 (0)