ADK-agents/gateway_client.py

141 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""A2A 网关接入客户端Agent 服务启动后自动注册 + 心跳保活 + 注销。
用法(在 api_server.py 的 main() 中):
from gateway_client import register_agent, start_heartbeat, unregister_agent
if register_agent(dev_app.name, f"http://127.0.0.1:{PORT}"):
start_heartbeat(dev_app.name)
环境变量:
GATEWAY_URL 网关地址,默认 http://127.0.0.1:8000
GATEWAY_AUTH 网关认证密码,需与网关 GATEWAY_AUTH 一致
AGENT_TAGS 能力标签,逗号分隔,默认 code
AGENT_MAX_CONCURRENT 最大并发数,默认 1
HEARTBEAT_INTERVAL 心跳间隔秒,默认 10
"""
import logging
import os
import threading
import time
import httpx
logger = logging.getLogger(__name__)
GATEWAY_URL = os.getenv("GATEWAY_URL", "http://127.0.0.1:8000").rstrip("/")
GATEWAY_AUTH = os.getenv("GATEWAY_AUTH", "dev-gateway-auth")
HEARTBEAT_INTERVAL = int(os.getenv("HEARTBEAT_INTERVAL", "10"))
_client = httpx.Client(timeout=5.0)
def _parse_tags() -> list[str]:
raw = os.getenv("AGENT_TAGS", "code")
return [t.strip() for t in raw.split(",") if t.strip()]
def register_agent(agent_id: str, endpoint: str, agent_tags: list[str] | None = None,
max_concurrent: int | None = None) -> bool:
"""向网关注册本 Agent。成功返回 True失败网关未启动/认证失败)返回 False。"""
tags = agent_tags if agent_tags is not None else _parse_tags()
max_conc = max_concurrent or int(os.getenv("AGENT_MAX_CONCURRENT", "1"))
body = {
"auth": GATEWAY_AUTH,
"agent_id": agent_id,
"endpoint": endpoint,
"agent_tags": tags,
"max_concurrent": max_conc,
"current_load": 0,
}
try:
resp = _client.post(f"{GATEWAY_URL}/api/agent/register", json=body)
if resp.status_code == 200:
logger.info("registered to gateway agent=%s endpoint=%s tags=%s", agent_id, endpoint, tags)
return True
logger.error("register failed agent=%s status=%s body=%s", agent_id, resp.status_code, resp.text)
except httpx.HTTPError as e:
logger.error("register network error agent=%s err=%s", agent_id, e)
return False
def _heartbeat_once(agent_id: str, current_load: int) -> bool:
try:
resp = _client.post(
f"{GATEWAY_URL}/api/agent/heartbeat",
json={"agent_id": agent_id, "current_load": current_load},
)
if resp.status_code == 200:
return True
logger.warning("heartbeat failed status=%s body=%s", resp.status_code, resp.text)
except httpx.HTTPError as e:
logger.warning("heartbeat network error err=%s", e)
return False
def _heartbeat_loop(agent_id: str) -> None:
while True:
try:
load = get_current_load()
except Exception:
load = 0
_heartbeat_once(agent_id, load)
time.sleep(HEARTBEAT_INTERVAL)
def start_heartbeat(agent_id: str) -> threading.Thread:
"""启动后台心跳线程daemon随进程退出"""
t = threading.Thread(target=_heartbeat_loop, args=(agent_id,), name=f"gateway-hb-{agent_id}", daemon=True)
t.start()
logger.info("heartbeat started agent=%s interval=%ss", agent_id, HEARTBEAT_INTERVAL)
return t
def unregister_agent(agent_id: str) -> bool:
"""向网关注销本 Agent。"""
try:
resp = _client.post(f"{GATEWAY_URL}/api/agent/unregister", json={"agent_id": agent_id})
if resp.status_code in (200, 404):
logger.info("unregistered from gateway agent=%s", agent_id)
return True
logger.warning("unregister failed status=%s body=%s", resp.status_code, resp.text)
except httpx.HTTPError as e:
logger.error("unregister network error agent=%s err=%s", agent_id, e)
return False
def report_result(request_id: str, agent_id: str, status: str = "success",
progress: int = 100, result: dict | None = None,
error_info: str | None = None) -> bool:
"""任务执行完成后将结果回传给网关POST /api/agent/result
Args:
request_id: 网关分配的任务 ID
agent_id: 本 Agent ID
status: success / failed
progress: 进度百分比
result: 结果字典(可选)
error_info: 错误信息(失败时必填)
"""
body = {
"request_id": request_id,
"agent_id": agent_id,
"status": status,
"progress": progress,
"result": result,
"error_info": error_info,
}
try:
resp = _client.post(f"{GATEWAY_URL}/api/agent/result", json=body)
if resp.status_code == 200:
logger.info("result reported request=%s status=%s", request_id, status)
return True
logger.error("report result failed request=%s status=%s body=%s", request_id, resp.status_code, resp.text)
except httpx.HTTPError as e:
logger.error("report result network error request=%s err=%s", request_id, e)
return False
def get_current_load() -> int:
"""Agent 当前负载。子类/调用方可覆写以报告真实并发数。"""
return 0