Python: [Breaking] removed pydantic from types and workflows (#917)

* removed pydantic from types

* fix test

* fix test

* fix tests

* fix assistants client

* Remove Pydantic usage from workflow code.

* updated pydantic removal

* updated lock and test fixes

* fix mypy

* updated build system

* updated chat client parsing

* fix broken test

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
This commit is contained in:
Eduard van Valkenburg
2025-09-29 23:19:58 +02:00
committed by GitHub
Unverified
parent 647db9635a
commit b4ebafa9b1
56 changed files with 3881 additions and 1735 deletions
@@ -68,9 +68,6 @@ class A2AAgent(BaseAgent):
Can be initialized with a URL, AgentCard, or existing A2A Client instance.
"""
client: Client
_http_client: httpx.AsyncClient | None = None
def __init__(
self,
*,
@@ -81,6 +78,7 @@ class A2AAgent(BaseAgent):
url: str | None = None,
client: Client | None = None,
http_client: httpx.AsyncClient | None = None,
**kwargs: Any,
) -> None:
"""Initialize the A2AAgent.
@@ -92,42 +90,40 @@ class A2AAgent(BaseAgent):
url: The URL for the A2A server.
client: The A2A client for the agent.
http_client: Optional httpx.AsyncClient to use.
kwargs: any additional properties, passed to BaseAgent.
"""
if client is None:
if agent_card is None:
if url is None:
raise ValueError("Either agent_card or url must be provided")
# Create minimal agent card from URL
agent_card = minimal_agent_card(url, [TransportProtocol.jsonrpc])
super().__init__(id=id, name=name, description=description, **kwargs)
self._http_client: httpx.AsyncClient | None = http_client
if client is not None:
self.client = client
self._close_http_client = True
return
if agent_card is None:
if url is None:
raise ValueError("Either agent_card or url must be provided")
# Create minimal agent card from URL
agent_card = minimal_agent_card(url, [TransportProtocol.jsonrpc])
# Create or use provided httpx client
if http_client is None:
timeout = httpx.Timeout(
connect=10.0, # 10 seconds to establish connection
read=60.0, # 60 seconds to read response (A2A operations can take time)
write=10.0, # 10 seconds to send request
pool=5.0, # 5 seconds to get connection from pool
)
headers = prepend_agent_framework_to_user_agent()
http_client = httpx.AsyncClient(timeout=timeout, headers=headers)
self._http_client = http_client # Store for cleanup
# Create A2A client using factory
config = ClientConfig(
httpx_client=http_client,
supported_transports=[TransportProtocol.jsonrpc],
# Create or use provided httpx client
if http_client is None:
timeout = httpx.Timeout(
connect=10.0, # 10 seconds to establish connection
read=60.0, # 60 seconds to read response (A2A operations can take time)
write=10.0, # 10 seconds to send request
pool=5.0, # 5 seconds to get connection from pool
)
factory = ClientFactory(config)
client = factory.create(agent_card)
headers = prepend_agent_framework_to_user_agent()
http_client = httpx.AsyncClient(timeout=timeout, headers=headers)
self._http_client = http_client # Store for cleanup
self._close_http_client = True
args: dict[str, Any] = {"client": client}
if name:
args["name"] = name
if id:
args["id"] = id
if description:
args["description"] = description
super().__init__(**args)
# Create A2A client using factory
config = ClientConfig(
httpx_client=http_client,
supported_transports=[TransportProtocol.jsonrpc],
)
factory = ClientFactory(config)
self.client = factory.create(agent_card)
async def __aenter__(self) -> "A2AAgent":
"""Async context manager entry."""
@@ -141,7 +137,7 @@ class A2AAgent(BaseAgent):
) -> None:
"""Async context manager exit with httpx client cleanup."""
# Close our httpx client if we created it
if self._http_client is not None:
if self._http_client is not None and self._close_http_client:
await self._http_client.aclose()
async def run(
+12 -11
View File
@@ -90,14 +90,14 @@ def mock_a2a_client() -> MockA2AClient:
@fixture
def a2a_agent(mock_a2a_client: MockA2AClient) -> A2AAgent:
"""Fixture that provides an A2AAgent with a mock client."""
return A2AAgent.model_construct(name="Test Agent", id="test-agent", client=mock_a2a_client, _http_client=None)
return A2AAgent(name="Test Agent", id="test-agent", client=mock_a2a_client, http_client=None)
def test_a2a_agent_initialization_with_client(mock_a2a_client: MockA2AClient) -> None:
"""Test A2AAgent initialization with provided client."""
# Use model_construct to bypass Pydantic validation for mock objects
agent = A2AAgent.model_construct(
name="Test Agent", id="test-agent-123", description="A test agent", client=mock_a2a_client, _http_client=None
agent = A2AAgent(
name="Test Agent", id="test-agent-123", description="A test agent", client=mock_a2a_client, http_client=None
)
assert agent.name == "Test Agent"
@@ -266,7 +266,7 @@ def test_get_uri_data_invalid_uri() -> None:
def test_a2a_parts_to_contents_conversion(a2a_agent: A2AAgent) -> None:
"""Test A2A parts to contents conversion."""
agent = A2AAgent.model_construct(name="Test Agent", client=MockA2AClient(), _http_client=None)
agent = A2AAgent(name="Test Agent", client=MockA2AClient(), _http_client=None)
# Create A2A parts
parts = [Part(root=TextPart(text="First part")), Part(root=TextPart(text="Second part"))]
@@ -369,7 +369,8 @@ async def test_context_manager_cleanup() -> None:
mock_http_client = AsyncMock()
mock_a2a_client = MagicMock()
agent = A2AAgent.model_construct(client=mock_a2a_client, _http_client=mock_http_client)
agent = A2AAgent(client=mock_a2a_client)
agent._http_client = mock_http_client
# Test context manager cleanup
async with agent:
@@ -384,7 +385,7 @@ async def test_context_manager_no_cleanup_when_no_http_client() -> None:
mock_a2a_client = MagicMock()
agent = A2AAgent.model_construct(client=mock_a2a_client, _http_client=None)
agent = A2AAgent(client=mock_a2a_client, _http_client=None)
# This should not raise any errors
async with agent:
@@ -394,7 +395,7 @@ async def test_context_manager_no_cleanup_when_no_http_client() -> None:
def test_chat_message_to_a2a_message_with_multiple_contents() -> None:
"""Test conversion of ChatMessage with multiple contents."""
agent = A2AAgent.model_construct(client=MagicMock(), _http_client=None)
agent = A2AAgent(client=MagicMock(), _http_client=None)
# Create message with multiple content types
message = ChatMessage(
@@ -422,7 +423,7 @@ def test_chat_message_to_a2a_message_with_multiple_contents() -> None:
def test_a2a_parts_to_contents_with_data_part() -> None:
"""Test conversion of A2A DataPart."""
agent = A2AAgent.model_construct(client=MagicMock(), _http_client=None)
agent = A2AAgent(client=MagicMock(), _http_client=None)
# Create DataPart
data_part = Part(root=DataPart(data={"key": "value", "number": 42}, metadata={"source": "test"}))
@@ -438,7 +439,7 @@ def test_a2a_parts_to_contents_with_data_part() -> None:
def test_a2a_parts_to_contents_unknown_part_kind() -> None:
"""Test error handling for unknown A2A part kind."""
agent = A2AAgent.model_construct(client=MagicMock(), _http_client=None)
agent = A2AAgent(client=MagicMock(), _http_client=None)
# Create a mock part with unknown kind
mock_part = MagicMock()
@@ -451,7 +452,7 @@ def test_a2a_parts_to_contents_unknown_part_kind() -> None:
def test_chat_message_to_a2a_message_with_hosted_file() -> None:
"""Test conversion of ChatMessage with HostedFileContent to A2A message."""
agent = A2AAgent.model_construct(client=MagicMock(), _http_client=None)
agent = A2AAgent(client=MagicMock(), _http_client=None)
# Create message with hosted file content
message = ChatMessage(
@@ -477,7 +478,7 @@ def test_chat_message_to_a2a_message_with_hosted_file() -> None:
def test_a2a_parts_to_contents_with_hosted_file_uri() -> None:
"""Test conversion of A2A FilePart with hosted file URI back to UriContent."""
agent = A2AAgent.model_construct(client=MagicMock(), _http_client=None)
agent = A2AAgent(client=MagicMock(), _http_client=None)
# Create FilePart with hosted file URI (simulating what A2A would send back)
file_part = Part(