From 8eb5d9d4217a56fd5ade5b95c3d75de21da6bd65 Mon Sep 17 00:00:00 2001 From: 0xrushi <0xrushi> Date: Fri, 23 May 2025 13:48:23 -0400 Subject: [PATCH 1/2] connect with external a2a --- ...mple_client_thirdparty_googlea2a_server.py | 170 ++++++++++++++++++ python_a2a/client/http.py | 21 ++- 2 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 examples/getting_started/simple_client_thirdparty_googlea2a_server.py diff --git a/examples/getting_started/simple_client_thirdparty_googlea2a_server.py b/examples/getting_started/simple_client_thirdparty_googlea2a_server.py new file mode 100644 index 0000000..97c8c27 --- /dev/null +++ b/examples/getting_started/simple_client_thirdparty_googlea2a_server.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +""" +Simple A2A Client Example + +This example shows how to use the A2A client to connect to a Google A2A-compatible agent +and send messages. It requires an external A2A server to be running. + +To run: + python simple_client_thirdparty_googlea2a_server.py --external URL + +Example: + python simple_client_thirdparty_googlea2a_server.py --external http://localhost:8000 + +Requirements: + pip install "python-a2a[server]" + +Note: + You need to run a Google A2A server first. You can find example servers at: + https://github.com/google/a2a-python/tree/main/examples/langgraph +""" + +import sys +import argparse +import socket +import time +import threading +import multiprocessing + +def check_dependencies(): + """Check if required dependencies are installed""" + missing_deps = [] + + try: + import python_a2a + except ImportError: + missing_deps.append("python-a2a") + + try: + import flask + except ImportError: + missing_deps.append("flask") + + if missing_deps: + print("āŒ Missing dependencies:") + for dep in missing_deps: + print(f" - {dep}") + + print("\nPlease install the required dependencies:") + print(" pip install \"python-a2a[server]\"") + print("\nThen run this example again.") + return False + + print("āœ… All dependencies are installed correctly!") + return True + +def parse_arguments(): + """Parse command line arguments""" + parser = argparse.ArgumentParser(description="Simple A2A Client Example") + parser.add_argument( + "--port", type=int, default=None, + help="Port to run the server on (default: auto-select an available port)" + ) + parser.add_argument( + "--external", type=str, default=None, + help="External A2A endpoint URL (if provided, won't start local server)" + ) + return parser.parse_args() + + +def main(): + # First, check dependencies + if not check_dependencies(): + return 1 + + # Parse command line arguments + args = parse_arguments() + + # Import after checking dependencies + from python_a2a import A2AClient + + # Handle external or local server + if args.external: + endpoint_url = args.external + print(f"\nšŸš€ Connecting to external A2A agent at: {endpoint_url}") + server_process = None + else: + print("\nāŒ No external A2A server specified!") + print("\nTo run this example, you need to:") + print("1. Clone and run a demo A2A server from: https://github.com/google/a2a-python/tree/main/examples/langgraph") + print("2. Run this client with the server URL using: --external URL") + raise ValueError("External Google A2A server URL is required") + + try: + # Create an A2A client and connect to the server + print(f"šŸ”Œ Connecting to A2A agent at: {endpoint_url}") + client = A2AClient(endpoint_url, google_a2a_compatible=True) + + # Try to get agent information + try: + print("\n=== Agent Information ===") + print(f"Name: {client.agent_card.name}") + print(f"Description: {client.agent_card.description}") + print(f"Version: {client.agent_card.version}") + + if client.agent_card.skills: + print("\nAvailable Skills:") + for skill in client.agent_card.skills: + print(f"- {skill.name}: {skill.description}") + if skill.examples: + print(f" Examples: {', '.join(skill.examples)}") + except Exception as e: + print(f"\nāš ļø Could not retrieve agent card: {e}") + print("The agent may not support the A2A discovery protocol.") + print("You can still send messages to the agent.") + + # Interactive message sending loop + print("\n=== Send Messages to the Agent ===") + print("Type your messages (or 'exit' to quit):") + + while True: + try: + user_input = input("\n> ") + if user_input.lower() in ["exit", "quit", "q"]: + break + + # Send the message and get the response + print("\nSending message to agent...") + response = client.ask(user_input) + + # Print the response + print("\nAgent response:") + print(f"{response}") + + except KeyboardInterrupt: + print("\nExiting...") + break + except Exception as e: + print(f"\nāŒ Error: {e}") + print("Try sending a different message or check your connection.") + + except Exception as e: + print(f"\nāŒ Error connecting to agent: {e}") + print("\nPossible reasons:") + print("- The endpoint URL is incorrect") + print("- The agent server is not running") + print("- Network connectivity issues") + print("\nPlease check the URL and try again.") + return 1 + finally: + # Clean up the server process if we started one + if server_process: + print("\nšŸ›‘ Stopping local server...") + server_process.terminate() + server_process.join(timeout=2) + print("āœ… Local server stopped") + + print("\n=== What's Next? ===") + print("1. Try 'simple_server.py' to create your own A2A server") + print("2. Try 'function_calling.py' to use function calling with A2A") + print("3. Try the other examples to explore more A2A features") + + print("\nšŸŽ‰ You've successfully used the A2A client! šŸŽ‰") + return 0 + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + print("\nāœ… Program interrupted by user") + sys.exit(0) \ No newline at end of file diff --git a/python_a2a/client/http.py b/python_a2a/client/http.py index 05378ab..a02de9d 100644 --- a/python_a2a/client/http.py +++ b/python_a2a/client/http.py @@ -42,7 +42,7 @@ def __init__(self, endpoint_url: str, headers: Optional[Dict[str, str]] = None, self.headers = headers or {} self.timeout = timeout self._use_google_a2a = google_a2a_compatible - self._protocol_detected = False # True after we've detected the protocol type + self._protocol_detected = google_a2a_compatible # True after we've detected the protocol type # Always include content type for JSON if "Content-Type" not in self.headers: @@ -437,10 +437,25 @@ def send_message(self, message: Message) -> Message: if self._use_google_a2a or self._protocol_detected: for endpoint in endpoints_to_try: try: - # Google A2A format + # Get the message in Google A2A format + message_data = message.to_google_a2a() + + # Ensure messageId is at the top level + if "metadata" in message_data and "message_id" in message_data["metadata"]: + message_data["messageId"] = message_data["metadata"]["message_id"] + + request_data = { + "jsonrpc": "2.0", + "id": 1, + "method": "message/send", + "params": { + "message": message_data + } + } + response = requests.post( endpoint, - json=message.to_google_a2a(), + json=request_data, headers=self.headers, timeout=self.timeout ) From d788baaa7088d1be2d6b896100b79089e69748fa Mon Sep 17 00:00:00 2001 From: 0xrushi <6279035+0xrushi@users.noreply.github.com> Date: Fri, 23 May 2025 14:07:45 -0400 Subject: [PATCH 2/2] line --- .../getting_started/simple_client_thirdparty_googlea2a_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/getting_started/simple_client_thirdparty_googlea2a_server.py b/examples/getting_started/simple_client_thirdparty_googlea2a_server.py index 97c8c27..51f7f4b 100644 --- a/examples/getting_started/simple_client_thirdparty_googlea2a_server.py +++ b/examples/getting_started/simple_client_thirdparty_googlea2a_server.py @@ -1,4 +1,5 @@ #!/usr/bin/env python + """ Simple A2A Client Example