今天要用一個專案架構,直接用實作入門:如何用 Google Agent Development Kit (ADK) 和 A2A (Agent-to-Agent) 框架,打造並運行一個簡單的 Reimbursement Agent。
來源參考並整合自: (不知道為何這個 repo 為何寫得這麼難入門, 只好自己改寫)
原本 A2A Sample 架構示意圖
Client / Agent 架構
專案結構
agent/
├── __main__.py # 代理人主程式 & 技能定義
├── reimbursement_agent.py # 報銷管理核心邏輯
└── reimbursement_records.json # 報銷紀錄資料
client/
└── __main__.py # 使用者 CLI 客戶端
How to run
Clone
git clone https://github.com/AgentWorkshop/sample-agent-single cd sample-agent-single/
Install dependency or use make install
$ make install
uv is already installed
Start agent
$ make run-agent
PYTHONPATH=. uv run agent INFO: Started server process [85335] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://localhost:10002 (Press CTRL+C to quit) INFO: ::1:59835 - "GET /.well-known/agent.json HTTP/1.1" 200 OK
Start Client CLI, open another termina
$ make run-client
PYTHONPATH=. uv run client --agent http://localhost:10002 ======= Agent Card ======== {"name":"Reimbursement Agent","description":"This agent handles the reimbursement process for the employees given the amount and purpose of the reimbursement, and can also retrieve past reimbursement records.","url":"http://localhost:10002/","version":"1.0.0","capabilities":{"streaming":true,"pushNotifications":false,"stateTransitionHistory":false},"defaultInputModes":["text","text/plain"],"defaultOutputModes":["text","text/plain"],"skills":[{"id":"process_reimbursement","name":"Process Reimbursement Tool","description":"Helps with the reimbursement process for users given the amount and purpose of the reimbursement.","tags":["reimbursement"],"examples":["2025-04-24, amount: 300, lunch with Client-Google","2025-04-24, amount: 300, dinner with Client-Microsoft","Can you reimburse me $20 for my lunch with the clients?"]},{"id":"retrieve_reimbursements","name":"Retrieve Reimbursement Records","description":"Allows users to retrieve their past reimbursement requests and statuses, optionally filtered by date, amount, or purpose.","tags":["reimbursement","history","query"],"examples":["Show all my past reimbursements","List reimbursements for 2025-04-24","Get all reimbursements for lunch with Client-Google","Show reimbursements with amount 300"]}]} ========= starting a new task ======== What do you want to send to the agent? (:q or quit to exit):
Type some example for reimbursement. ex.
2025-04-23, amount: 300, Play Golf with client-A
2025-04-23, amount: 2300, dinner with Trump
2025-04-24, amount: 300, lunch with Client-Google
2025-04-24, amount: 300, dinner with Client-Microsoft
example response
What do you want to send to the agent? (:q or quit to exit): 2025-04-23, amount: 300, Play Golf with client-A Select a file path to attach? (press enter to skip): stream event => {"jsonrpc":"2.0","id":"2b188ad7ffc242b6856ea315fd510dde","result":{"id":"eaabea7771224ae5a184a8cef80441a4","status":{"state":"input-required","message":{"role":"agent","parts":[{"type":"data","data":{"type":"form","form":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Date of expense","title":"Date"},"amount":{"type":"string","format":"number","description":"Amount of expense","title":"Amount"},"purpose":{"type":"string","description":"Purpose of expense","title":"Purpose"},"request_id":{"type":"string","description":"Request id","title":"Request ID"}},"required":["amount","request_id","date","purpose"]},"form_data":{"amount":"300","request_id":"request_id_4256641","date":"2025-04-23","purpose":"Play Golf with client-A"},"instructions":null}}]},"timestamp":"2025-04-24T23:07:17.703261"},"final":false}} stream event => {"jsonrpc":"2.0","id":"2b188ad7ffc242b6856ea315fd510dde","result":{"id":"eaabea7771224ae5a184a8cef80441a4","artifact":{"parts":[{"type":"data","data":{"type":"form","form":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Date of expense","title":"Date"},"amount":{"type":"string","format":"number","description":"Amount of expense","title":"Amount"},"purpose":{"type":"string","description":"Purpose of expense","title":"Purpose"},"request_id":{"type":"string","description":"Request id","title":"Request ID"}},"required":["amount","request_id","date","purpose"]},"form_data":{"amount":"300","request_id":"request_id_4256641","date":"2025-04-23","purpose":"Play Golf with client-A"},"instructions":null}}],"index":0,"append":false}}} stream event => {"jsonrpc":"2.0","id":"2b188ad7ffc242b6856ea315fd510dde","result":{"id":"eaabea7771224ae5a184a8cef80441a4","status":{"state":"input-required","timestamp":"2025-04-24T23:07:17.704118"},"final":true}}
Type some example question about reimbursement. ex.
Show all my past reimbursements
example response
What do you want to send to the agent? (:q or quit to exit): Show all my past reimbursements Select a file path to attach? (press enter to skip): stream event => {"jsonrpc":"2.0","id":"79c2e1ab145e4bbea32aebb7d914f6eb","result":{"id":"3ba7b5b72bbc4cd195db5ad91571ef41","status":{"state":"completed","message":{"role":"agent","parts":[{"type":"text","text":"Here are all your past reimbursements: {'amount': 'unknown', 'date': 'unknown', 'purpose': 'unknown', 'request_id': 'request_id_7878693', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8147694', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_2342613', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-23', 'purpose': 'Play Golf with client-A', 'request_id': 'request_id_5341285', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-23', 'purpose': 'Play Golf with client-A', 'request_id': 'request_id_4256641', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8472778', 'status': 'approved'}.\n"}]},"timestamp":"2025-04-24T23:08:59.555867"},"final":false}} stream event => {"jsonrpc":"2.0","id":"79c2e1ab145e4bbea32aebb7d914f6eb","result":{"id":"3ba7b5b72bbc4cd195db5ad91571ef41","artifact":{"parts":[{"type":"text","text":"Here are all your past reimbursements: {'amount': 'unknown', 'date': 'unknown', 'purpose': 'unknown', 'request_id': 'request_id_7878693', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8147694', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_2342613', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-23', 'purpose': 'Play Golf with client-A', 'request_id': 'request_id_5341285', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-23', 'purpose': 'Play Golf with client-A', 'request_id': 'request_id_4256641', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8472778', 'status': 'approved'}.\n"}],"index":0,"append":false}}} stream event => {"jsonrpc":"2.0","id":"79c2e1ab145e4bbea32aebb7d914f6eb","result":{"id":"3ba7b5b72bbc4cd195db5ad91571ef41","status":{"state":"completed","timestamp":"2025-04-24T23:08:59.556415"},"final":true}} ========= starting a new task ======== What do you want to send to the agent? (:q or quit to exit):
Show today’s reimbursements
List reimbursements for 2025-04-24
example response
What do you want to send to the agent? (:q or quit to exit): List reimbursements for 2025-04-24 Select a file path to attach? (press enter to skip): stream event => {"jsonrpc":"2.0","id":"4889b6dd49fe45a69522815ffeded030","result":{"id":"5a7a40ff3b9d4a168279386f773270ff","status":{"state":"completed","message":{"role":"agent","parts":[{"type":"text","text":"Here are the reimbursements for 2025-04-24: {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8147694', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_2342613', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8472778', 'status': 'approved'}."}]},"timestamp":"2025-04-24T23:10:18.075354"},"final":false}} stream event => {"jsonrpc":"2.0","id":"4889b6dd49fe45a69522815ffeded030","result":{"id":"5a7a40ff3b9d4a168279386f773270ff","artifact":{"parts":[{"type":"text","text":"Here are the reimbursements for 2025-04-24: {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8147694', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_2342613', 'status': 'approved'}, {'amount': '300', 'date': '2025-04-24', 'purpose': 'lunch with Client-Google', 'request_id': 'request_id_8472778', 'status': 'approved'}."}],"index":0,"append":false}}} stream event => {"jsonrpc":"2.0","id":"4889b6dd49fe45a69522815ffeded030","result":{"id":"5a7a40ff3b9d4a168279386f773270ff","status":{"state":"completed","timestamp":"2025-04-24T23:10:18.076195"},"final":true}} ========= starting a new task ======== What do you want to send to the agent? (:q or quit to exit):
reimbursement_records.json
[
{
"request_id": "request_id_8147694",
"date": "2025-04-24",
"amount": "300",
"purpose": "lunch with Client-Google",
"status": "approved"
},
{
"request_id": "request_id_2342613",
"date": "2025-04-24",
"amount": "300",
"purpose": "lunch with Client-Google",
"status": "approved"
},
{
"request_id": "request_id_5341285",
"date": "2025-04-23",
"amount": "300",
"purpose": "Play Golf with client-A",
"status": "approved"
},
{
"request_id": "request_id_4256641",
"date": "2025-04-23",
"amount": "300",
"purpose": "Play Golf with client-A",
"status": "approved"
},
{
"request_id": "request_id_8472778",
"date": "2025-04-24",
"amount": "300",
"purpose": "lunch with Client-Google",
"status": "approved"
}
]
小結
這就是 Part 1 的全部內容!後續還會介紹多代理人架構與進階應用。歡迎留言分享你的成果!
---
Happy hacking! 🚀