mirror of
https://github.com/aljazceru/Tutorial-Codebase-Knowledge.git
synced 2025-12-19 15:34:23 +01:00
265 lines
14 KiB
Markdown
265 lines
14 KiB
Markdown
---
|
||
layout: default
|
||
title: "A2A Client Implementation"
|
||
parent: "Google A2A"
|
||
nav_order: 5
|
||
---
|
||
|
||
# Chapter 5: A2A Client Implementation
|
||
|
||
In the [previous chapter](04_a2a_server_implementation.md), we learned how to build the "embassy" for our AI agent – the **A2A Server**. This server listens for incoming requests, acting as the official entry point for our agent according to the A2A protocol rules.
|
||
|
||
But how does someone actually *visit* this embassy and make a request? If you build a fantastic translation agent server, how does your chat application, or another AI agent, actually *use* it to translate text?
|
||
|
||
This chapter tackles that problem: **How do we build the component that *initiates* communication with an A2A agent server?**
|
||
|
||
This is the job of the **A2A Client Implementation**.
|
||
|
||
## What is an A2A Client? The Agent's Customer
|
||
|
||
Think about how you use the web:
|
||
|
||
* You want to visit a website (like `google.com`).
|
||
* You open your **web browser** (like Chrome or Firefox).
|
||
* You type the website's address into the browser.
|
||
* The browser sends a request to the website's server.
|
||
* The server sends back the webpage content.
|
||
* Your browser receives the content and displays it to you.
|
||
|
||
In this scenario, your **web browser** is the **client**. It *starts* the conversation, knows how to format the request (using HTTP), sends it to the right address, and understands the server's response.
|
||
|
||
Similarly, an **A2A Client** is the software component that acts like that web browser, but specifically for talking to A2A agents:
|
||
|
||
1. **Knows the Agent's Address:** It needs the URL of the agent's A2A server (which it might get from the agent's [Agent Card](01_agent_card.md)).
|
||
2. **Speaks the Language:** It knows how to format requests according to the [A2A Protocol & Core Types](03_a2a_protocol___core_types.md), using JSON-RPC for commands like `tasks/send`.
|
||
3. **Initiates the Call:** It sends these requests over the network (usually via HTTP POST) to the agent's server.
|
||
4. **Understands the Reply:** It receives the server's JSON-RPC response, checks for success or errors, and parses the results (like the initial `Task` object or streaming updates).
|
||
|
||
Essentially, the A2A Client is the part of your application (or another agent) that *consumes* the services offered by an A2A agent server.
|
||
|
||
## Why Do We Need It?
|
||
|
||
Your application's core logic (e.g., the chat interface, the document summarizer UI) shouldn't need to worry about the messy details of JSON-RPC formatting, HTTP headers, or handling network connections.
|
||
|
||
The A2A Client acts as an **intermediary** or **adapter**. It provides a cleaner, simpler way for your application code to interact with a remote A2A agent. Your application can just say, "Client, please send this message to the agent," and the client handles all the protocol details.
|
||
|
||
## Using an A2A Client Library
|
||
|
||
Just like we used `A2AServer` libraries to simplify building the server in [Chapter 4](04_a2a_server_implementation.md), the `Google A2A` project provides `A2AClient` libraries to make building the client side easier.
|
||
|
||
Let's see how we might use these libraries in JavaScript and Python to talk to the "Echo Agent" server we discussed previously.
|
||
|
||
### JavaScript Example (using `A2AClient` from the library)
|
||
|
||
Imagine we're building a simple command-line tool (`cli.ts`) that lets a user chat with our Echo Agent running at `http://localhost:4000`.
|
||
|
||
```typescript
|
||
// File: samples/js/src/cli.ts (Simplified Snippet)
|
||
import { A2AClient } from "./client/client.js"; // The client library
|
||
import { TaskSendParams } from "./schema.js"; // Types for request parameters
|
||
import crypto from "node:crypto"; // To generate IDs
|
||
|
||
// Agent's address (replace with your agent's URL)
|
||
const serverUrl = "http://localhost:4000";
|
||
|
||
// 1. Create a client instance pointing to the agent's server
|
||
const client = new A2AClient(serverUrl);
|
||
|
||
// User input from the command line
|
||
const userInput = "Hello Echo Agent!";
|
||
|
||
// 2. Prepare the parameters for the 'tasks/sendSubscribe' request
|
||
const taskId = crypto.randomUUID(); // Generate a unique ID for this task
|
||
const params: TaskSendParams = {
|
||
id: taskId,
|
||
message: {
|
||
role: "user",
|
||
parts: [{ type: "text", text: userInput }], // The user's message
|
||
},
|
||
};
|
||
|
||
// 3. Send the request and handle the streaming response
|
||
async function sendMessage() {
|
||
console.log(`Sending task ${taskId} to ${serverUrl}...`);
|
||
try {
|
||
// Use sendTaskSubscribe for agents that support streaming
|
||
const stream = client.sendTaskSubscribe(params);
|
||
|
||
// Loop through the events received from the server
|
||
for await (const event of stream) {
|
||
console.log("Received Agent Event:", event);
|
||
// (In a real app, you'd parse 'event' which could be
|
||
// TaskStatusUpdateEvent or TaskArtifactUpdateEvent)
|
||
}
|
||
console.log("Agent stream finished.");
|
||
|
||
} catch (error: any) {
|
||
console.error("Error talking to agent:", error.message || error);
|
||
}
|
||
}
|
||
|
||
sendMessage();
|
||
```
|
||
|
||
**Explanation:**
|
||
|
||
1. **Create Client:** We import `A2AClient` and create an instance, telling it the URL of the agent server we want to talk to.
|
||
2. **Prepare Request:** We gather the necessary information for our request: a unique `taskId` and the `message` containing the user's input, formatted according to the A2A `TaskSendParams` structure ([Chapter 3](03_a2a_protocol___core_types.md)).
|
||
3. **Send & Handle Stream:** We call `client.sendTaskSubscribe(params)`. This method handles formatting the JSON-RPC request, sending the HTTP POST, and processing the Server-Sent Events (SSE) stream from the server ([Chapter 7: Streaming Communication (SSE)](07_streaming_communication__sse_.md)). We use a `for await...of` loop to process each event as it arrives from the agent.
|
||
|
||
**Example Output (Conceptual):**
|
||
|
||
```
|
||
Sending task abc-123 to http://localhost:4000...
|
||
Received Agent Event: { status: { state: 'working', message: { role: 'agent', parts: [ { text: 'Echoing...' } ] } } }
|
||
Received Agent Event: { status: { state: 'completed', message: { role: 'agent', parts: [ { text: 'You said: Hello Echo Agent!' } ] } } }
|
||
Agent stream finished.
|
||
```
|
||
|
||
The client library takes care of the underlying network communication and event parsing.
|
||
|
||
### Python Example (using `A2AClient` from the library)
|
||
|
||
Let's create a similar command-line tool in Python (`cli/__main__.py`) talking to an agent at `http://localhost:5000`.
|
||
|
||
```python
|
||
# File: samples/python/hosts/cli/__main__.py (Simplified Snippet)
|
||
import asyncio
|
||
from uuid import uuid4
|
||
from common.client import A2AClient # The client library
|
||
# Assume 'card' is the AgentCard fetched previously (see Chapter 1)
|
||
# card = A2ACardResolver("http://localhost:5000").get_agent_card()
|
||
|
||
# 1. Create a client instance using the agent's card or URL
|
||
# client = A2AClient(agent_card=card)
|
||
client = A2AClient(url="http://localhost:5000") # Or directly use URL
|
||
|
||
# User input
|
||
user_input = "Hi Python Agent!"
|
||
|
||
# 2. Prepare the payload (parameters) for the request
|
||
task_id = uuid4().hex # Generate a unique Task ID
|
||
payload = {
|
||
"id": task_id,
|
||
"message": {
|
||
"role": "user",
|
||
"parts": [{"type": "text", "text": user_input}],
|
||
},
|
||
}
|
||
|
||
# 3. Send the request and handle the response
|
||
async def send_message():
|
||
print(f"Sending task {task_id} to {client.url}...")
|
||
try:
|
||
# Use send_task_streaming if agent supports it (check card.capabilities.streaming)
|
||
# Assuming streaming is supported here:
|
||
response_stream = client.send_task_streaming(payload)
|
||
async for result in response_stream:
|
||
# result is already parsed SendTaskStreamingResponse object
|
||
print(f"Received Agent Event: {result.model_dump_json(exclude_none=True)}")
|
||
|
||
print("Agent stream finished.")
|
||
|
||
# If NOT streaming, you'd use send_task:
|
||
# task_result = await client.send_task(payload)
|
||
# print(f"Received Agent Response: {task_result.model_dump_json(exclude_none=True)}")
|
||
|
||
except Exception as e:
|
||
print(f"Error talking to agent: {e}")
|
||
|
||
asyncio.run(send_message())
|
||
```
|
||
|
||
**Explanation:**
|
||
|
||
1. **Create Client:** We import `A2AClient` and create an instance, providing the agent's `url`.
|
||
2. **Prepare Payload:** We create a Python dictionary `payload` containing the `id` and `message` parameters for the `tasks/send` or `tasks/sendSubscribe` method.
|
||
3. **Send & Handle Stream:** We call `client.send_task_streaming(payload)`. Similar to the JS version, this handles the JSON-RPC formatting, HTTP POST, and returns an asynchronous iterator. We loop through it using `async for` to get parsed response objects (like `SendTaskStreamingResponse`) for each event. The library hides the complexity of parsing the SSE stream. If the agent didn't support streaming, we would call `client.send_task(payload)` instead, which would return the final `Task` object directly after the agent finishes.
|
||
|
||
**Example Output (Conceptual, streaming):**
|
||
|
||
```
|
||
Sending task def-456 to http://localhost:5000...
|
||
Received Agent Event: {"jsonrpc": "2.0", "result": {"status": {"state": "working", "message": {"role": "agent", "parts": [{"type": "text", "text": "Echoing..."}]}}}}
|
||
Received Agent Event: {"jsonrpc": "2.0", "result": {"status": {"state": "completed", "message": {"role": "agent", "parts": [{"type": "text", "text": "You said: Hi Python Agent!"}]}}}}
|
||
Agent stream finished.
|
||
```
|
||
|
||
In both examples, the `A2AClient` library provides a high-level interface (`sendTaskSubscribe`, `send_task_streaming`, `sendTask`, `send_task`) that simplifies the process of communicating with an A2A server.
|
||
|
||
## Under the Hood: How the Client Works
|
||
|
||
What's happening inside the `A2AClient` library when you call a method like `sendTaskSubscribe`?
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant App as Your Application (e.g., CLI)
|
||
participant Lib as A2AClient Library
|
||
participant Net as Network (HTTP)
|
||
participant Srv as A2A Agent Server
|
||
|
||
App->>Lib: Call client.sendTaskSubscribe(params)
|
||
Note right of Lib: Generates JSON-RPC ID, Method='tasks/sendSubscribe'
|
||
Lib->>Lib: Format JSON-RPC Request Body (using params)
|
||
Note right of Lib: {jsonrpc:"2.0", id:"req-1", method:"...", params:{...}}
|
||
|
||
Lib->>Net: Send HTTP POST Request to Agent URL
|
||
Note over Net,Srv: Request travels over the internet
|
||
|
||
Net->>Srv: Delivers HTTP POST Request
|
||
Note right of Srv: Server receives request, parses JSON-RPC
|
||
|
||
Srv->>Srv: Processes Task (Starts internal logic)
|
||
Note right of Srv: Switches to streaming mode (SSE)
|
||
|
||
Srv-->>Net: Send HTTP Response (Status 200 OK, Content-Type: text/event-stream)
|
||
Srv-->>Net: Send SSE Event 1 (e.g., 'working' status)
|
||
Srv-->>Net: Send SSE Event 2 (e.g., 'completed' status)
|
||
Note right of Srv: Stream ends
|
||
|
||
Net-->>Lib: Delivers HTTP Response & SSE Events
|
||
Note right of Lib: Receives streaming response
|
||
|
||
Lib->>Lib: Parse SSE Events (Extract JSON data from 'data:' lines)
|
||
Lib-->>App: Yield Parsed Event 1 (as object)
|
||
Lib-->>App: Yield Parsed Event 2 (as object)
|
||
Note left of App: Application processes each event in the loop
|
||
|
||
App->>App: Loop finishes when stream ends
|
||
```
|
||
|
||
**Steps:**
|
||
|
||
1. **Application Call:** Your code calls a method on the `A2AClient` instance (e.g., `sendTaskSubscribe`).
|
||
2. **Format Request:** The library takes your parameters (`params`), generates a unique request ID, and constructs the full JSON-RPC request payload (a JSON object).
|
||
3. **Send HTTP Request:** The library uses an underlying HTTP client (like `fetch` in browsers/Node.js or `httpx` in Python) to send an HTTP POST request to the agent server's URL. It sets the correct headers (`Content-Type: application/json`, `Accept: text/event-stream` for streaming).
|
||
4. **Server Processing:** The A2A server receives the request, processes it (as described in [Chapter 4](04_a2a_server_implementation.md)), and starts sending back a response. For streaming, this is an HTTP response with a `text/event-stream` content type, followed by individual Server-Sent Events (SSE).
|
||
5. **Receive Response:** The client library's HTTP client receives the response.
|
||
6. **Parse Response/Stream:**
|
||
* **Non-streaming (`sendTask`):** It waits for the full response, parses the JSON body, checks for JSON-RPC level errors, and extracts the `result` field (e.g., the final `Task` object).
|
||
* **Streaming (`sendTaskSubscribe`):** It processes the incoming SSE stream, parsing the `data:` lines from each event, converting the JSON text into objects, and yielding these objects back to your application code via the async iterator.
|
||
7. **Return/Yield Result:** The library returns the parsed result (for non-streaming) or yields the parsed events (for streaming) to your application code.
|
||
|
||
The client libraries (like `samples/js/src/client/client.ts` and `samples/python/common/client/client.py`) contain internal helper functions (e.g., `_makeHttpRequest`, `_handleJsonResponse`, `_handleStreamingResponse` in the JS client; `_send_request` in the Python client) to manage these steps.
|
||
|
||
## Conclusion
|
||
|
||
The **A2A Client** is the component that *initiates* conversations with A2A agent servers. It acts on behalf of your application or another agent, translating simple method calls (like "send this message") into correctly formatted A2A protocol requests (JSON-RPC over HTTP).
|
||
|
||
It handles the complexities of:
|
||
|
||
* Knowing the agent's address (`url`).
|
||
* Formatting requests (`tasks/send`, `tasks/sendSubscribe`).
|
||
* Sending them over the network.
|
||
* Parsing responses (JSON results or streaming SSE events).
|
||
* Handling errors.
|
||
|
||
By using the provided `A2AClient` libraries, you can easily integrate A2A communication into your applications without needing deep knowledge of the underlying protocol mechanics. You create a client, prepare your data, and call the appropriate method.
|
||
|
||
Now that we've seen both the server ([Chapter 4](04_a2a_server_implementation.md)) and the client side of the A2A interaction, let's dive deeper into how the *server* actually processes the tasks it receives from the client.
|
||
|
||
**Next:** [Chapter 6: Task Handling Logic (Server-side)](06_task_handling_logic__server_side_.md)
|
||
|
||
---
|
||
|
||
Generated by [AI Codebase Knowledge Builder](https://github.com/The-Pocket/Tutorial-Codebase-Knowledge) |