-
Notifications
You must be signed in to change notification settings - Fork 325
Description
What happened?
Describe the Bug
When using A2AClient.get_client_from_agent_card_url to initialize a client for an A2A agent, the method sometimes returns an A2AClient instance that does not have the agent_card attribute set (or its value is None). This occurs even when the underlying HTTP GET request to fetch the agent card's JSON from the target agent's /.well-known/agent.json endpoint returns a 200 OK status and valid JSON. Consequently, attempting to access client_instance.agent_card leads to an AttributeError.
Steps to Reproduce
- Have a specialist A2A agent running and serving a valid
AgentCardJSON at its/.well-known/agent.jsonendpoint. An example of suchAgentCardJSON is provided below. - From a separate orchestrator or client process, attempt to initialize an
A2AClientfor this specialist agent using the following pattern:import asyncio import httpx import logging from a2a.client import A2AClient from a2a.client.errors import A2AClientHTTPError, A2AClientError # For exception handling from a2a.types import AgentCard # Ensure AgentCard is imported # Configure basic logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger("sdk_test") async def attempt_to_get_client(specialist_url: str): async with httpx.AsyncClient(timeout=15.0) as http_client: try: logger.info(f"Attempting to fetch and process agent card from: {specialist_url}") # Attempt to get the client client_instance = await A2AClient.get_client_from_agent_card_url( http_client, specialist_url ) logger.info(f"Call to get_client_from_agent_card_url for '{specialist_url}' returned: {client_instance}") logger.info(f"Type of returned object: {type(client_instance)}") if isinstance(client_instance, A2AClient): logger.info(f"Instance attributes (__dict__): {client_instance.__dict__}") if hasattr(client_instance, 'agent_card') and client_instance.agent_card: logger.info(f"Successfully obtained client with agent_card. Name: {client_instance.agent_card.name}") # Further validation of client_instance.agent_card (e.g., type) could be added if isinstance(client_instance.agent_card, AgentCard): logger.info("client_instance.agent_card is an instance of AgentCard.") else: logger.warning(f"client_instance.agent_card is NOT an instance of AgentCard. Type: {type(client_instance.agent_card)}") else: logger.error("ERROR: A2AClient instance IS missing 'agent_card' attribute or agent_card is None.") else: logger.error(f"ERROR: Call to get_client_from_agent_card_url did not return an A2AClient instance. Got: {type(client_instance)}") except A2AClientHTTPError as e: logger.error(f"A2AClientHTTPError for {specialist_url}: {e}") except A2AClientError as e: # Catch other specific A2A client errors logger.error(f"A2AClientError for {specialist_url}: {e}", exc_info=True) except Exception as e: logger.error(f"Unexpected error for {specialist_url}: {e}", exc_info=True) async def main(): # Replace with the actual URL of your specialist agent serving the card specialist_agent_url = "http://localhost:9998" await attempt_to_get_client(specialist_agent_url) if __name__ == "__main__": asyncio.run(main())
- Observe the logs.
Example Specialist AgentCard Content
The following AgentCard JSON is successfully fetched (HTTP 200 OK) from http://localhost:9998/.well-known/agent.json but still leads to the issue:
{
"capabilities": {
"pushNotifications": false,
"streaming": false,
"functionCalling": true
},
"defaultInputModes": [
"text"
],
"defaultOutputModes": [
"text"
],
"description": "An AI assistant using LangChain for tool integration.",
"name": "LangChain Tool-Powered AI",
"skills": [
{
"description": "Can chat, answer questions, check leap years, and perform calculations using integrated tools.",
"examples": [
"Is 2024 a leap year?",
"Calculate (100 / 5) + 3",
"What's the weather like?"
],
"id": "chat_and_lc_tools",
"name": "Chat & LangChain Tools",
"tags": [
"general",
"tools",
"langchain",
"calculator",
"leap year"
]
}
],
"url": "http://localhost:9998/",
"version": "1.0.0"
}Log Output Example
Logs from the client code above, demonstrating the issue:
INFO:root:Attempting to fetch and process agent card from: http://localhost:9998
INFO:httpx:HTTP Request: GET http://localhost:9998/.well-known/agent.json "HTTP/1.1 200 OK"
INFO:root:Call to get_client_from_agent_card_url for 'http://localhost:9998' returned: <a2a.client.client.A2AClient object at 0x...>
INFO:root:Type of returned object: <class 'a2a.client.client.A2AClient'>
INFO:root:Instance attributes (__dict__): {'url': 'http://localhost:9998/', 'httpx_client': <httpx.AsyncClient object at 0x...>}
ERROR:root:ERROR: A2AClient instance IS missing 'agent_card' attribute or agent_card is None.
Expected Behavior
After A2AClient.get_client_from_agent_card_url successfully fetches and presumably parses the agent card (indicated by the HTTP 200 OK and no immediate parsing exceptions from the SDK), the returned A2AClient instance should consistently have a populated agent_card attribute. This attribute should hold the AgentCard object created from the fetched JSON data.
Actual Behavior
The HTTP GET request to fetch the card from the specialist agent is successful. An A2AClient object is returned. However, this A2AClient instance does not have an agent_card attribute in its __dict__ (or agent_card is None), as confirmed by logging client_instance.__dict__ and hasattr(client_instance, 'agent_card'). The instance only has 'url': 'http://specialist-agent:9998/' and 'httpx_client': <httpx.AsyncClient object at 0x7f426eb11c10>
Relevant log output
Code of Conduct
- I agree to follow this project's Code of Conduct