ADK-gateway/backend/app/services/relay.py
2026-08-05 23:24:57 +08:00

117 lines
5.9 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.

"""通信中转服务:正向下发任务指令、反向回传结果/日志RequestID+AgentID 关联。"""
import logging
import httpx
import redis.asyncio as aioredis
from app.config import settings
from app.constants import AgentStatus, TaskStatus
from app.models.schemas import TaskResult
from app.repository.agent_repo import AgentRepo
from app.repository.task_repo import TaskRepo
from app.services.notifier import TaskNotifier
logger = logging.getLogger(__name__)
class RelayService:
"""网关作为唯一通信中枢:正向把任务指令推送到 Agent 端点,反向接收结果回传。"""
def __init__(self, redis: aioredis.Redis, task_repo: TaskRepo | None = None):
self.redis = redis
self.task_repo = task_repo or TaskRepo(redis)
self.agent_repo = AgentRepo(redis)
async def dispatch_command(self, agent_id: str, request_id: str, payload: dict) -> tuple[bool, str]:
"""正向:向 Agent 真实 HTTP 推送任务指令POST {agent.endpoint}/tasks/{request_id})。
返回 (ok, reason):成功时 (True, "")Agent 不存在 / 端点缺失 / 网络失败 /
非 202 均返回 (False, 原因描述),由调度器回退任务状态并释放负载。
"""
agent = await self.agent_repo.get(agent_id)
if not agent:
logger.warning("relay dispatch failed: agent not found agent=%s request=%s", agent_id, request_id)
return False, "Agent 不存在或已注销"
if not agent.endpoint:
logger.warning("relay dispatch failed: endpoint empty agent=%s request=%s", agent_id, request_id)
return False, "Agent 未配置 endpoint"
task = await self.task_repo.get(request_id)
url = f"{agent.endpoint.rstrip('/')}/tasks/{request_id}"
body = {
"auth": settings.gateway_auth,
"request_id": request_id,
"payload": payload,
"cli_session_id": getattr(task, "cli_session_id", None) if task else None,
"task_type": getattr(task, "task_type", None) if task else None,
"task_tags": getattr(task, "task_tags", None) if task else None,
"timeout": getattr(task, "timeout", None) if task else None,
}
try:
async with httpx.AsyncClient(timeout=5.0) as client:
resp = await client.post(url, json=body)
except httpx.HTTPError as e:
logger.error("relay dispatch network error agent=%s request=%s err=%s", agent_id, request_id, e)
await self._log(agent_id, request_id, "dispatch", f"push failed: {e}")
return False, f"推送失败: {e}"
if resp.status_code != 202:
logger.warning("relay dispatch http %s agent=%s request=%s body=%s",
resp.status_code, agent_id, request_id, resp.text[:200])
await self._log(agent_id, request_id, "dispatch", f"push failed http {resp.status_code}")
return False, f"推送失败: Agent 返回 HTTP {resp.status_code}(期望 202"
await self._log(agent_id, request_id, "dispatch", f"command pushed to {agent.endpoint}")
logger.info("relay dispatch ok agent=%s request=%s url=%s", agent_id, request_id, url)
return True, ""
_TERMINAL = {TaskStatus.SUCCESS, TaskStatus.FAILED}
async def on_result(self, result: TaskResult) -> bool:
"""反向Agent 回传结果,更新任务状态并记录日志。
终态保护:任务已处于终态(如已取消置 failed忽略本次任务状态覆盖
但匹配的 Agent 仍会释放负载并置回 ready。
"""
task = await self.task_repo.get(result.request_id)
if not task:
logger.warning("result for unknown task request=%s", result.request_id)
return False
if task.agent_id and task.agent_id != result.agent_id:
logger.warning("result agent mismatch request=%s expected=%s got=%s",
result.request_id, task.agent_id, result.agent_id)
return False
already_terminal = task.status in self._TERMINAL
if not already_terminal:
await self.task_repo.update(
result.request_id,
status=result.status,
progress=result.progress,
result=result.result,
error_info=result.error_info,
)
await self.task_repo.mark_finished(result.request_id)
# 释放 Agent 算力(任务确实绑定在该 Agent 时才释放)
if task.agent_id == result.agent_id:
await self.agent_repo.adjust_load(result.agent_id, -1)
# Agent 空闲,置回就绪(若已被手动置为不可用则保持不可用,不会复活)
if task.agent_id:
await self.agent_repo.mark_idle(task.agent_id)
await self._log(result.agent_id, result.request_id, "result", f"result received status={result.status.value}")
# 任务到达终态,通知对应 CLI 会话(若携带了 cli_session_id
if not already_terminal and task.cli_session_id:
finished = await self.task_repo.get(result.request_id)
if finished:
await TaskNotifier(self.redis).publish_task_done(finished)
logger.info("task result request=%s agent=%s status=%s", result.request_id, result.agent_id, result.status.value)
return True
async def on_progress(self, request_id: str, agent_id: str, progress: int) -> bool:
await self.task_repo.update(request_id, progress=progress)
await self._log(agent_id, request_id, "progress", f"progress {progress}%")
return True
async def _log(self, agent_id: str, request_id: str, action: str, message: str) -> None:
from app.repository.log_repo import LogRepo
await LogRepo(self.redis).append(
source="gateway", scope="task", message=message,
request_id=request_id, agent_id=agent_id,
)