1041 lines
32 KiB
Python
1041 lines
32 KiB
Python
"""
|
||
AI Agent Manager API 客户端
|
||
封装对 AI Agent Manager 服务的 HTTP 调用
|
||
|
||
适配 Agent Manager 实际接口(基于 agent-manager接口文档.md)
|
||
"""
|
||
|
||
import os
|
||
import httpx
|
||
import structlog
|
||
from typing import Dict, List, Optional, Any
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
|
||
logger = structlog.get_logger(__name__)
|
||
|
||
# 从环境变量获取 Agent Manager URL
|
||
AGENT_MANAGER_URL = os.getenv("AGENT_MANAGER_URL", "http://localhost:8000")
|
||
|
||
|
||
class AgentStatus(str, Enum):
|
||
"""Agent Pod 状态"""
|
||
PENDING = "Pending"
|
||
RUNNING = "Running"
|
||
SUCCEEDED = "Succeeded"
|
||
FAILED = "Failed"
|
||
UNKNOWN = "Unknown"
|
||
|
||
|
||
@dataclass
|
||
class AgentConfig:
|
||
"""
|
||
Agent 资源配置
|
||
|
||
适配 Agent Manager POST /agents 接口的 config 参数
|
||
"""
|
||
user_id: Optional[str] = None # 用户标识,用于多租户管理
|
||
cpu_request: Optional[str] = "100m"
|
||
cpu_limit: Optional[str] = "500m"
|
||
memory_request: Optional[str] = "128Mi"
|
||
memory_limit: Optional[str] = "512Mi"
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
"""转换为 API 请求格式"""
|
||
result = {}
|
||
if self.user_id:
|
||
result["user_id"] = self.user_id
|
||
if self.cpu_request:
|
||
result["cpu_request"] = self.cpu_request
|
||
if self.cpu_limit:
|
||
result["cpu_limit"] = self.cpu_limit
|
||
if self.memory_request:
|
||
result["memory_request"] = self.memory_request
|
||
if self.memory_limit:
|
||
result["memory_limit"] = self.memory_limit
|
||
return result
|
||
|
||
|
||
@dataclass
|
||
class TemplateInfo:
|
||
"""
|
||
模板信息
|
||
|
||
适配 Agent Manager GET /templates 接口响应
|
||
"""
|
||
template: str
|
||
port: Optional[int] = None
|
||
env_info: Dict[str, Any] = field(default_factory=dict)
|
||
template_type: Optional[str] = None # platform 或 custom
|
||
|
||
|
||
@dataclass
|
||
class AgentCreateResult:
|
||
"""
|
||
Agent 创建结果
|
||
|
||
适配 Agent Manager POST /agents 接口响应
|
||
"""
|
||
name: str
|
||
namespace: str
|
||
status: str
|
||
created_at: str
|
||
template: str
|
||
service_port: Optional[int] = None
|
||
access_info: Optional[Dict[str, Any]] = None
|
||
# 新增字段(适配实际接口响应)
|
||
pod_id: Optional[str] = None
|
||
pod_ip: Optional[str] = None
|
||
host_ip: Optional[str] = None
|
||
node_name: Optional[str] = None
|
||
owner_info: Optional[Dict[str, Any]] = None
|
||
|
||
|
||
@dataclass
|
||
class AgentStatusResult:
|
||
"""
|
||
Agent 状态结果
|
||
|
||
适配 Agent Manager GET /agents/{name}/status 接口响应
|
||
"""
|
||
name: str
|
||
namespace: str
|
||
status: str
|
||
created_at: Optional[str] = None
|
||
pod_ip: Optional[str] = None
|
||
node_name: Optional[str] = None
|
||
labels: Optional[Dict[str, str]] = None
|
||
# 兼容旧字段
|
||
template: Optional[str] = None
|
||
endpoints: Optional[Dict[str, str]] = None
|
||
|
||
|
||
@dataclass
|
||
class AgentMetricsResult:
|
||
"""
|
||
Agent 资源使用结果
|
||
|
||
适配 Agent Manager GET /agents/{name}/metrics 接口响应
|
||
|
||
Agent Manager 实际返回格式:
|
||
{
|
||
"name": "alice-echo",
|
||
"namespace": "ai-agents",
|
||
"requests": {
|
||
"cpu": "100m",
|
||
"memory": "128Mi"
|
||
},
|
||
"limits": {
|
||
"cpu": "500m",
|
||
"memory": "512Mi"
|
||
},
|
||
"usage": {
|
||
"cpu": "14502n",
|
||
"memory": "8704Ki"
|
||
},
|
||
"timestamp": "2026-01-06T05:01:04Z",
|
||
"metrics_available": null
|
||
}
|
||
|
||
字段说明:
|
||
- requests/limits: 资源配额(从 Pod spec 获取)
|
||
- usage: 实时资源使用(从 metrics-server 获取,需要集群安装 metrics-server)
|
||
- timestamp: metrics 数据的时间戳(ISO 8601 格式)
|
||
- metrics_available: metrics-server 是否可用
|
||
|
||
CPU 单位说明:
|
||
- n (nanocores): 1 核 = 1,000,000,000n
|
||
- m (millicores): 1 核 = 1,000m
|
||
- 例如: 14502n = 0.014502m ≈ 0.000014 核
|
||
|
||
内存单位说明:
|
||
- Ki (Kibibytes): 1024 字节
|
||
- Mi (Mebibytes): 1024 KiB
|
||
- 例如: 8704Ki ≈ 8.5 MiB
|
||
"""
|
||
name: str
|
||
namespace: str = "ai-agents"
|
||
requests: Dict[str, str] = field(default_factory=dict)
|
||
limits: Dict[str, str] = field(default_factory=dict)
|
||
# 新增:实时资源使用(从 metrics-server 获取)
|
||
usage: Optional[Dict[str, str]] = None
|
||
# 新增:metrics 数据时间戳
|
||
timestamp: Optional[str] = None
|
||
# 新增:metrics-server 是否可用
|
||
metrics_available: Optional[bool] = None
|
||
# 兼容旧格式
|
||
resources: Dict[str, Any] = field(default_factory=dict)
|
||
|
||
@staticmethod
|
||
def _parse_cpu_to_millicores(cpu_str: str) -> float:
|
||
"""
|
||
将 CPU 字符串转换为毫核(millicores)
|
||
|
||
支持的单位:
|
||
- n (nanocores): 1m = 1,000,000n
|
||
- m (millicores): 直接使用
|
||
- 无单位: 视为核数,乘以 1000
|
||
|
||
Args:
|
||
cpu_str: CPU 字符串,如 "100m", "14502n", "0.5"
|
||
|
||
Returns:
|
||
毫核数(float)
|
||
"""
|
||
if not cpu_str or cpu_str == "0":
|
||
return 0.0
|
||
|
||
cpu_str = str(cpu_str).strip()
|
||
|
||
try:
|
||
if cpu_str.endswith("n"):
|
||
# nanocores -> millicores: 1m = 1,000,000n
|
||
return float(cpu_str[:-1]) / 1_000_000
|
||
elif cpu_str.endswith("m"):
|
||
# millicores
|
||
return float(cpu_str[:-1])
|
||
else:
|
||
# 核数 -> millicores
|
||
return float(cpu_str) * 1000
|
||
except ValueError:
|
||
return 0.0
|
||
|
||
@staticmethod
|
||
def _parse_memory_to_mb(mem_str: str) -> float:
|
||
"""
|
||
将内存字符串转换为 MB
|
||
|
||
支持的单位:
|
||
- Ki (Kibibytes): 1 MiB = 1024 KiB
|
||
- Mi (Mebibytes): 直接使用
|
||
- Gi (Gibibytes): 1 GiB = 1024 MiB
|
||
- K (Kilobytes): 1 MB = 1000 KB
|
||
- M (Megabytes): 直接使用
|
||
- G (Gigabytes): 1 GB = 1000 MB
|
||
- 无单位: 视为字节
|
||
|
||
Args:
|
||
mem_str: 内存字符串,如 "128Mi", "8704Ki", "1Gi"
|
||
|
||
Returns:
|
||
MB 数(float)
|
||
"""
|
||
if not mem_str or mem_str == "0":
|
||
return 0.0
|
||
|
||
mem_str = str(mem_str).strip()
|
||
|
||
try:
|
||
# 二进制单位(IEC)
|
||
if mem_str.endswith("Ki"):
|
||
return float(mem_str[:-2]) / 1024
|
||
elif mem_str.endswith("Mi"):
|
||
return float(mem_str[:-2])
|
||
elif mem_str.endswith("Gi"):
|
||
return float(mem_str[:-2]) * 1024
|
||
elif mem_str.endswith("Ti"):
|
||
return float(mem_str[:-2]) * 1024 * 1024
|
||
# 十进制单位(SI)
|
||
elif mem_str.endswith("K"):
|
||
return float(mem_str[:-1]) / 1000
|
||
elif mem_str.endswith("M"):
|
||
return float(mem_str[:-1])
|
||
elif mem_str.endswith("G"):
|
||
return float(mem_str[:-1]) * 1000
|
||
elif mem_str.endswith("T"):
|
||
return float(mem_str[:-1]) * 1000 * 1000
|
||
else:
|
||
# 字节 -> MB
|
||
return float(mem_str) / (1024 * 1024)
|
||
except ValueError:
|
||
return 0.0
|
||
|
||
@property
|
||
def cpu_request(self) -> str:
|
||
"""获取 CPU 请求量(如 '100m')"""
|
||
return self.requests.get("cpu", "0")
|
||
|
||
@property
|
||
def memory_request(self) -> str:
|
||
"""获取内存请求量(如 '128Mi')"""
|
||
return self.requests.get("memory", "0")
|
||
|
||
@property
|
||
def cpu_limit(self) -> str:
|
||
"""获取 CPU 限制(如 '500m')"""
|
||
return self.limits.get("cpu", "0")
|
||
|
||
@property
|
||
def memory_limit(self) -> str:
|
||
"""获取内存限制(如 '512Mi')"""
|
||
return self.limits.get("memory", "0")
|
||
|
||
@property
|
||
def cpu_usage_current(self) -> str:
|
||
"""获取当前 CPU 使用量(如 '14502n')"""
|
||
if self.usage:
|
||
return self.usage.get("cpu", "0")
|
||
return "0"
|
||
|
||
@property
|
||
def memory_usage_current(self) -> str:
|
||
"""获取当前内存使用量(如 '8704Ki')"""
|
||
if self.usage:
|
||
return self.usage.get("memory", "0")
|
||
return "0"
|
||
|
||
# 兼容旧的 cpu_usage/memory_usage 属性名(使用 limits 作为显示值)
|
||
@property
|
||
def cpu_usage(self) -> str:
|
||
"""获取 CPU 限制(兼容旧属性名)"""
|
||
return self.cpu_limit
|
||
|
||
@property
|
||
def memory_usage(self) -> str:
|
||
"""获取内存限制(兼容旧属性名)"""
|
||
return self.memory_limit
|
||
|
||
@property
|
||
def available(self) -> bool:
|
||
"""是否可用(基于是否有 limits 配置)"""
|
||
return bool(self.limits.get("cpu") or self.limits.get("memory"))
|
||
|
||
@property
|
||
def has_realtime_metrics(self) -> bool:
|
||
"""是否有实时 metrics 数据"""
|
||
return self.usage is not None and bool(self.usage)
|
||
|
||
@property
|
||
def cpu_limit_millicores(self) -> float:
|
||
"""获取 CPU 限制(毫核,如 500m -> 500.0)"""
|
||
return self._parse_cpu_to_millicores(self.cpu_limit)
|
||
|
||
@property
|
||
def cpu_request_millicores(self) -> float:
|
||
"""获取 CPU 请求量(毫核)"""
|
||
return self._parse_cpu_to_millicores(self.cpu_request)
|
||
|
||
@property
|
||
def cpu_usage_current_millicores(self) -> float:
|
||
"""获取当前 CPU 使用量(毫核)"""
|
||
return self._parse_cpu_to_millicores(self.cpu_usage_current)
|
||
|
||
@property
|
||
def memory_limit_mb(self) -> float:
|
||
"""获取内存限制(MB)"""
|
||
return self._parse_memory_to_mb(self.memory_limit)
|
||
|
||
@property
|
||
def memory_request_mb(self) -> float:
|
||
"""获取内存请求量(MB)"""
|
||
return self._parse_memory_to_mb(self.memory_request)
|
||
|
||
@property
|
||
def memory_usage_current_mb(self) -> float:
|
||
"""获取当前内存使用量(MB)"""
|
||
return self._parse_memory_to_mb(self.memory_usage_current)
|
||
|
||
@property
|
||
def cpu_utilization_percent(self) -> Optional[float]:
|
||
"""
|
||
获取 CPU 使用率(相对于 limit 的百分比)
|
||
|
||
Returns:
|
||
使用率百分比,如果没有实时数据则返回 None
|
||
"""
|
||
if not self.has_realtime_metrics:
|
||
return None
|
||
|
||
limit = self.cpu_limit_millicores
|
||
if limit <= 0:
|
||
return None
|
||
|
||
usage = self.cpu_usage_current_millicores
|
||
return (usage / limit) * 100
|
||
|
||
@property
|
||
def memory_utilization_percent(self) -> Optional[float]:
|
||
"""
|
||
获取内存使用率(相对于 limit 的百分比)
|
||
|
||
Returns:
|
||
使用率百分比,如果没有实时数据则返回 None
|
||
"""
|
||
if not self.has_realtime_metrics:
|
||
return None
|
||
|
||
limit = self.memory_limit_mb
|
||
if limit <= 0:
|
||
return None
|
||
|
||
usage = self.memory_usage_current_mb
|
||
return (usage / limit) * 100
|
||
|
||
# 兼容旧属性名
|
||
@property
|
||
def cpu_usage_millicores(self) -> float:
|
||
"""获取 CPU 限制(毫核,兼容旧属性名)"""
|
||
return self.cpu_limit_millicores
|
||
|
||
@property
|
||
def memory_usage_mb(self) -> float:
|
||
"""获取内存限制(MB,兼容旧属性名)"""
|
||
return self.memory_limit_mb
|
||
|
||
|
||
@dataclass
|
||
class AgentListResult:
|
||
"""
|
||
Agent 列表结果
|
||
|
||
适配 Agent Manager GET /agents 接口响应
|
||
"""
|
||
agents: List[Dict[str, Any]]
|
||
count: int
|
||
|
||
|
||
class AgentManagerError(Exception):
|
||
"""Agent Manager API 错误"""
|
||
def __init__(self, message: str, status_code: int = 500, detail: Any = None):
|
||
self.message = message
|
||
self.status_code = status_code
|
||
self.detail = detail
|
||
super().__init__(message)
|
||
|
||
|
||
class AgentManagerClient:
|
||
"""
|
||
AI Agent Manager API 客户端
|
||
|
||
适配 Agent Manager 实际接口:
|
||
- POST /agents - 创建 Agent
|
||
- GET /agents - 获取所有 Agent
|
||
- GET /agents/{name}/status - 获取 Agent 状态
|
||
- GET /agents/{name}/metrics - 获取资源使用情况
|
||
- DELETE /agents/{name} - 删除 Agent
|
||
- GET /templates - 获取所有模板
|
||
- GET /templates/platform - 获取平台模板
|
||
- GET /templates/custom - 获取自定义模板
|
||
- GET /templates/{name} - 获取模板详情
|
||
- GET / - 健康检查
|
||
"""
|
||
|
||
def __init__(self, base_url: Optional[str] = None, timeout: float = 30.0):
|
||
"""
|
||
初始化客户端
|
||
|
||
Args:
|
||
base_url: API 基础 URL,默认从环境变量获取
|
||
timeout: 请求超时时间(秒)
|
||
"""
|
||
self.base_url = (base_url or AGENT_MANAGER_URL).rstrip("/")
|
||
self.timeout = timeout
|
||
self._client: Optional[httpx.AsyncClient] = None
|
||
|
||
async def _get_client(self) -> httpx.AsyncClient:
|
||
"""获取或创建 HTTP 客户端"""
|
||
if self._client is None or self._client.is_closed:
|
||
self._client = httpx.AsyncClient(
|
||
base_url=self.base_url,
|
||
timeout=self.timeout,
|
||
headers={"Content-Type": "application/json"}
|
||
)
|
||
return self._client
|
||
|
||
async def close(self):
|
||
"""关闭客户端"""
|
||
if self._client and not self._client.is_closed:
|
||
await self._client.aclose()
|
||
self._client = None
|
||
|
||
async def _request(
|
||
self,
|
||
method: str,
|
||
path: str,
|
||
json: Optional[Dict] = None,
|
||
params: Optional[Dict] = None
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
发送 HTTP 请求
|
||
|
||
Args:
|
||
method: HTTP 方法
|
||
path: API 路径
|
||
json: 请求体
|
||
params: 查询参数
|
||
|
||
Returns:
|
||
响应 JSON
|
||
|
||
Raises:
|
||
AgentManagerError: API 调用失败
|
||
"""
|
||
client = await self._get_client()
|
||
|
||
try:
|
||
response = await client.request(
|
||
method=method,
|
||
url=path,
|
||
json=json,
|
||
params=params
|
||
)
|
||
|
||
if response.status_code >= 400:
|
||
try:
|
||
detail = response.json()
|
||
except Exception:
|
||
detail = response.text
|
||
|
||
logger.error(
|
||
"agent_manager_api_error",
|
||
method=method,
|
||
path=path,
|
||
status_code=response.status_code,
|
||
detail=detail
|
||
)
|
||
|
||
raise AgentManagerError(
|
||
message=f"API 调用失败: {response.status_code}",
|
||
status_code=response.status_code,
|
||
detail=detail
|
||
)
|
||
|
||
return response.json()
|
||
|
||
except httpx.RequestError as e:
|
||
logger.error(
|
||
"agent_manager_connection_error",
|
||
method=method,
|
||
path=path,
|
||
error=str(e)
|
||
)
|
||
raise AgentManagerError(
|
||
message=f"连接 Agent Manager 失败: {str(e)}",
|
||
status_code=503,
|
||
detail={"error": "connection_error", "message": str(e)}
|
||
)
|
||
|
||
# ==================== 健康检查 ====================
|
||
|
||
async def health_check(self) -> Dict[str, Any]:
|
||
"""
|
||
检查 Agent Manager 服务状态
|
||
|
||
调用: GET /
|
||
|
||
Returns:
|
||
服务状态信息,包含:
|
||
- service: 服务名称
|
||
- version: 版本
|
||
- status: 状态
|
||
"""
|
||
return await self._request("GET", "/")
|
||
|
||
# ==================== 模板管理 ====================
|
||
|
||
async def list_templates(self) -> List[TemplateInfo]:
|
||
"""
|
||
获取所有可用模板
|
||
|
||
调用: GET /templates
|
||
|
||
Returns:
|
||
模板列表
|
||
"""
|
||
data = await self._request("GET", "/templates")
|
||
templates = []
|
||
for t in data.get("templates", []):
|
||
templates.append(TemplateInfo(
|
||
template=t["template"],
|
||
port=t.get("port"),
|
||
env_info=t.get("env_info", {})
|
||
))
|
||
return templates
|
||
|
||
async def list_platform_templates(self) -> List[TemplateInfo]:
|
||
"""
|
||
获取所有平台 Agent 模板
|
||
|
||
调用: GET /templates/platform
|
||
|
||
平台 Agent 模板是预定义的、由平台管理的 Agent 类型。
|
||
|
||
Returns:
|
||
平台模板列表
|
||
"""
|
||
data = await self._request("GET", "/templates/platform")
|
||
templates = []
|
||
for t in data.get("templates", []):
|
||
templates.append(TemplateInfo(
|
||
template=t["template"],
|
||
port=t.get("port"),
|
||
env_info=t.get("env_info", {}),
|
||
template_type="platform"
|
||
))
|
||
return templates
|
||
|
||
async def list_custom_templates(self) -> List[TemplateInfo]:
|
||
"""
|
||
获取所有自定义 Agent 模板
|
||
|
||
调用: GET /templates/custom
|
||
|
||
自定义 Agent 模板需要用户配置环境变量(如 API Key)。
|
||
|
||
Returns:
|
||
自定义模板列表,每个模板的 env_info 包含:
|
||
- required: 必需的环境变量
|
||
- optional: 可选的环境变量
|
||
"""
|
||
data = await self._request("GET", "/templates/custom")
|
||
templates = []
|
||
for t in data.get("templates", []):
|
||
templates.append(TemplateInfo(
|
||
template=t["template"],
|
||
port=t.get("port"),
|
||
env_info=t.get("env_info", {}),
|
||
template_type="custom"
|
||
))
|
||
return templates
|
||
|
||
async def get_template(self, template_name: str) -> TemplateInfo:
|
||
"""
|
||
获取模板详情
|
||
|
||
调用: GET /templates/{template_name}
|
||
|
||
Args:
|
||
template_name: 模板名称
|
||
|
||
Returns:
|
||
模板信息
|
||
"""
|
||
data = await self._request("GET", f"/templates/{template_name}")
|
||
return TemplateInfo(
|
||
template=data["template"],
|
||
port=data.get("port"),
|
||
env_info=data.get("env_info", {})
|
||
)
|
||
|
||
# ==================== Agent 管理 ====================
|
||
|
||
async def create_agent(
|
||
self,
|
||
name: str,
|
||
template: str,
|
||
config: Optional[AgentConfig] = None,
|
||
env: Optional[Dict[str, str]] = None
|
||
) -> AgentCreateResult:
|
||
"""
|
||
创建 Agent Pod
|
||
|
||
调用: POST /agents
|
||
|
||
这是统一的 Agent 创建接口,适用于平台 Agent 和自定义 Agent。
|
||
|
||
Args:
|
||
name: Agent 名称(1-63 字符,小写字母、数字、连字符)
|
||
template: 模板类型(如 echo_agent, mysql_agent)
|
||
config: 资源配置(包含 user_id, cpu, memory 等)
|
||
env: 环境变量(自定义 Agent 需要,如 API Key)
|
||
|
||
Returns:
|
||
创建结果
|
||
|
||
Example:
|
||
# 创建平台 Agent
|
||
result = await client.create_agent(
|
||
name="alice-echo",
|
||
template="echo_agent",
|
||
config=AgentConfig(user_id="alice")
|
||
)
|
||
|
||
# 创建自定义 Agent
|
||
result = await client.create_agent(
|
||
name="my-mysql-agent",
|
||
template="mysql_agent",
|
||
config=AgentConfig(user_id="alice"),
|
||
env={
|
||
"MYSQL_HOST": "mysql.example.com",
|
||
"MYSQL_USER": "root",
|
||
"MYSQL_PASSWORD": "password",
|
||
"MYSQL_DATABASE": "mydb",
|
||
"OPENAI_API_KEY": "sk-..."
|
||
}
|
||
)
|
||
"""
|
||
payload: Dict[str, Any] = {
|
||
"name": name,
|
||
"template": template
|
||
}
|
||
|
||
if config:
|
||
payload["config"] = config.to_dict()
|
||
|
||
if env:
|
||
payload["env"] = env
|
||
|
||
logger.info(
|
||
"creating_agent",
|
||
name=name,
|
||
template=template,
|
||
config=config.to_dict() if config else None,
|
||
has_env=bool(env)
|
||
)
|
||
|
||
data = await self._request("POST", "/agents", json=payload)
|
||
|
||
return AgentCreateResult(
|
||
name=data["name"],
|
||
namespace=data["namespace"],
|
||
status=data["status"],
|
||
created_at=data["created_at"],
|
||
template=data["template"],
|
||
service_port=data.get("service_port"),
|
||
access_info=data.get("access_info"),
|
||
pod_id=data.get("pod_id"),
|
||
pod_ip=data.get("pod_ip"),
|
||
host_ip=data.get("host_ip"),
|
||
node_name=data.get("node_name"),
|
||
owner_info=data.get("owner_info")
|
||
)
|
||
|
||
async def create_platform_agent(
|
||
self,
|
||
name: str,
|
||
template: str,
|
||
user_id: str,
|
||
config: Optional[AgentConfig] = None
|
||
) -> AgentCreateResult:
|
||
"""
|
||
创建平台 Agent 实例
|
||
|
||
这是 create_agent 的便捷方法,用于创建平台 Agent。
|
||
平台 Agent 使用平台预定义的镜像,用户无需配置环境变量。
|
||
|
||
Args:
|
||
name: Agent 实例名称
|
||
template: 平台模板名称(如 echo_agent, jina_search_agent)
|
||
user_id: 用户 ID(用于资源隔离和计费)
|
||
config: 资源配置(可选,使用模板默认值)
|
||
|
||
Returns:
|
||
创建结果
|
||
"""
|
||
if config is None:
|
||
config = AgentConfig(user_id=user_id)
|
||
else:
|
||
config.user_id = user_id
|
||
|
||
logger.info(
|
||
"creating_platform_agent",
|
||
name=name,
|
||
template=template,
|
||
user_id=user_id
|
||
)
|
||
|
||
return await self.create_agent(
|
||
name=name,
|
||
template=template,
|
||
config=config
|
||
)
|
||
|
||
async def create_custom_agent(
|
||
self,
|
||
name: str,
|
||
template: str,
|
||
user_id: str,
|
||
env_vars: Dict[str, str],
|
||
config: Optional[AgentConfig] = None
|
||
) -> AgentCreateResult:
|
||
"""
|
||
创建自定义 Agent 实例
|
||
|
||
这是 create_agent 的便捷方法,用于创建自定义 Agent。
|
||
自定义 Agent 需要用户提供环境变量(如 API Key、数据库连接信息)。
|
||
|
||
Args:
|
||
name: Agent 实例名称
|
||
template: 自定义模板名称(如 mysql_agent, postgresql_agent)
|
||
user_id: 用户 ID
|
||
env_vars: 环境变量(必须,包含 API Key 等敏感信息)
|
||
config: 资源配置(可选)
|
||
|
||
Returns:
|
||
创建结果
|
||
|
||
Example:
|
||
result = await client.create_custom_agent(
|
||
name="my-mysql-agent",
|
||
template="mysql_agent",
|
||
user_id="alice",
|
||
env_vars={
|
||
"MYSQL_HOST": "mysql.example.com",
|
||
"MYSQL_USER": "root",
|
||
"MYSQL_PASSWORD": "password",
|
||
"MYSQL_DATABASE": "mydb",
|
||
"OPENAI_API_KEY": "sk-..."
|
||
}
|
||
)
|
||
"""
|
||
if config is None:
|
||
config = AgentConfig(user_id=user_id)
|
||
else:
|
||
config.user_id = user_id
|
||
|
||
logger.info(
|
||
"creating_custom_agent",
|
||
name=name,
|
||
template=template,
|
||
user_id=user_id,
|
||
env_keys=list(env_vars.keys()) if env_vars else []
|
||
)
|
||
|
||
return await self.create_agent(
|
||
name=name,
|
||
template=template,
|
||
config=config,
|
||
env=env_vars
|
||
)
|
||
|
||
async def delete_agent(self, agent_name: str) -> Dict[str, Any]:
|
||
"""
|
||
删除 Agent Pod
|
||
|
||
调用: DELETE /agents/{agent_name}
|
||
|
||
Args:
|
||
agent_name: Agent 名称
|
||
|
||
Returns:
|
||
删除结果,包含 message 字段
|
||
"""
|
||
logger.info("deleting_agent", name=agent_name)
|
||
return await self._request("DELETE", f"/agents/{agent_name}")
|
||
|
||
async def get_agent_status(self, agent_name: str) -> AgentStatusResult:
|
||
"""
|
||
获取 Agent 状态
|
||
|
||
调用: GET /agents/{agent_name}/status
|
||
|
||
Args:
|
||
agent_name: Agent 名称
|
||
|
||
Returns:
|
||
Agent 状态信息
|
||
"""
|
||
data = await self._request("GET", f"/agents/{agent_name}/status")
|
||
|
||
return AgentStatusResult(
|
||
name=data["name"],
|
||
namespace=data["namespace"],
|
||
status=data["status"],
|
||
created_at=data.get("created_at"),
|
||
pod_ip=data.get("pod_ip"),
|
||
node_name=data.get("node_name"),
|
||
labels=data.get("labels"),
|
||
# 从 labels 中提取 template
|
||
template=data.get("labels", {}).get("template") if data.get("labels") else None
|
||
)
|
||
|
||
async def get_agent_metrics(self, agent_name: str) -> AgentMetricsResult:
|
||
"""
|
||
获取 Agent 资源使用情况
|
||
|
||
调用: GET /agents/{agent_name}/metrics
|
||
|
||
Agent Manager 返回格式:
|
||
{
|
||
"name": "alice-echo",
|
||
"namespace": "ai-agents",
|
||
"requests": {
|
||
"cpu": "100m",
|
||
"memory": "128Mi"
|
||
},
|
||
"limits": {
|
||
"cpu": "500m",
|
||
"memory": "512Mi"
|
||
},
|
||
"usage": {
|
||
"cpu": "14502n",
|
||
"memory": "8704Ki"
|
||
},
|
||
"timestamp": "2026-01-06T05:01:04Z",
|
||
"metrics_available": null
|
||
}
|
||
|
||
Args:
|
||
agent_name: Agent 名称
|
||
|
||
Returns:
|
||
资源使用信息,可通过属性访问:
|
||
|
||
配额信息:
|
||
- metrics.cpu_limit: CPU 限制字符串(如 "500m")
|
||
- metrics.memory_limit: 内存限制字符串(如 "512Mi")
|
||
- metrics.cpu_request: CPU 请求字符串(如 "100m")
|
||
- metrics.memory_request: 内存请求字符串(如 "128Mi")
|
||
- metrics.cpu_limit_millicores: CPU 限制(毫核)
|
||
- metrics.memory_limit_mb: 内存限制(MB)
|
||
|
||
实时使用(需要 metrics-server):
|
||
- metrics.usage: 实时资源使用字典
|
||
- metrics.cpu_usage_current: 当前 CPU 使用字符串(如 "14502n")
|
||
- metrics.memory_usage_current: 当前内存使用字符串(如 "8704Ki")
|
||
- metrics.cpu_usage_current_millicores: 当前 CPU 使用(毫核)
|
||
- metrics.memory_usage_current_mb: 当前内存使用(MB)
|
||
- metrics.cpu_utilization_percent: CPU 使用率百分比
|
||
- metrics.memory_utilization_percent: 内存使用率百分比
|
||
- metrics.has_realtime_metrics: 是否有实时数据
|
||
- metrics.timestamp: metrics 数据时间戳
|
||
- metrics.metrics_available: metrics-server 是否可用
|
||
|
||
兼容属性:
|
||
- metrics.available: 是否可用(基于 limits)
|
||
"""
|
||
data = await self._request("GET", f"/agents/{agent_name}/metrics")
|
||
|
||
return AgentMetricsResult(
|
||
name=data.get("name", agent_name),
|
||
namespace=data.get("namespace", "ai-agents"),
|
||
requests=data.get("requests", {}),
|
||
limits=data.get("limits", {}),
|
||
usage=data.get("usage"), # 新增:实时资源使用
|
||
timestamp=data.get("timestamp"), # 新增:metrics 时间戳
|
||
metrics_available=data.get("metrics_available"), # 新增:metrics-server 可用性
|
||
resources=data.get("resources", {}) # 兼容旧格式
|
||
)
|
||
|
||
async def list_agents(self, template: Optional[str] = None) -> AgentListResult:
|
||
"""
|
||
列出所有 Agent
|
||
|
||
调用: GET /agents
|
||
|
||
Args:
|
||
template: 按模板类型过滤(可选)
|
||
|
||
Returns:
|
||
Agent 列表结果
|
||
"""
|
||
params = {}
|
||
if template:
|
||
params["template"] = template
|
||
|
||
data = await self._request("GET", "/agents", params=params)
|
||
|
||
return AgentListResult(
|
||
agents=data.get("agents", []),
|
||
count=data.get("count", 0)
|
||
)
|
||
|
||
# ==================== 未实现的接口(Agent Manager 尚未提供)====================
|
||
# 以下方法调用的接口在 Agent Manager 中尚未实现
|
||
# 保留方法签名以便后续扩展,但调用时会抛出 NotImplementedError
|
||
|
||
async def scale_agent(
|
||
self,
|
||
agent_name: str,
|
||
replicas: Optional[int] = None,
|
||
cpu_request: Optional[str] = None,
|
||
cpu_limit: Optional[str] = None,
|
||
memory_request: Optional[str] = None,
|
||
memory_limit: Optional[str] = None
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
扩缩容 Agent(待实现)
|
||
|
||
预期调用: PATCH /agents/{agent_name}/scale
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 PATCH /agents/{name}/scale 接口"
|
||
)
|
||
|
||
async def get_agent_logs(
|
||
self,
|
||
agent_name: str,
|
||
tail_lines: int = 100,
|
||
since_seconds: Optional[int] = None
|
||
) -> str:
|
||
"""
|
||
获取 Agent 日志(待实现)
|
||
|
||
预期调用: GET /agents/{agent_name}/logs
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 GET /agents/{name}/logs 接口"
|
||
)
|
||
|
||
async def restart_agent(self, agent_name: str) -> Dict[str, Any]:
|
||
"""
|
||
重启 Agent(待实现)
|
||
|
||
预期调用: POST /agents/{agent_name}/restart
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 POST /agents/{name}/restart 接口"
|
||
)
|
||
|
||
async def get_resource_stats(self) -> Dict[str, Any]:
|
||
"""
|
||
获取资源统计(待实现)
|
||
|
||
预期调用: GET /resources/stats
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 GET /resources/stats 接口"
|
||
)
|
||
|
||
async def get_user_resources(self, user_id: str) -> Dict[str, Any]:
|
||
"""
|
||
获取用户资源使用统计(待实现)
|
||
|
||
预期调用: GET /resources/stats/by-user/{user_id}
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 GET /resources/stats/by-user/{user_id} 接口"
|
||
)
|
||
|
||
async def get_channel_resources(self, channel_id: str) -> Dict[str, Any]:
|
||
"""
|
||
获取渠道资源使用统计(待实现)
|
||
|
||
预期调用: GET /resources/stats/by-channel/{channel_id}
|
||
|
||
注意: Agent Manager 尚未实现此接口
|
||
"""
|
||
raise NotImplementedError(
|
||
"Agent Manager 尚未实现 GET /resources/stats/by-channel/{channel_id} 接口"
|
||
)
|
||
|
||
|
||
# 全局客户端实例
|
||
_agent_manager_client: Optional[AgentManagerClient] = None
|
||
|
||
|
||
def get_agent_manager_client() -> AgentManagerClient:
|
||
"""获取全局 Agent Manager 客户端实例"""
|
||
global _agent_manager_client
|
||
if _agent_manager_client is None:
|
||
_agent_manager_client = AgentManagerClient()
|
||
return _agent_manager_client
|
||
|
||
|
||
async def close_agent_manager_client():
|
||
"""关闭全局客户端"""
|
||
global _agent_manager_client
|
||
if _agent_manager_client:
|
||
await _agent_manager_client.close()
|
||
_agent_manager_client = None
|