forked from xiaohei/taiji-AI-PAD
更新超级管理员端
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,8 @@ from . import (
|
||||
quota_management, pricing_management, # 阶段一:配额管理和定价管理
|
||||
resource_monitoring, event_management, # 阶段二:资源监控和事件管理
|
||||
trace_management, audit_management, # 阶段三:追踪和审计管理
|
||||
provider_health_management # 阶段四:供应商健康检查管理
|
||||
provider_health_management, # 阶段四:供应商健康检查管理
|
||||
platform_agent_quota, # 平台 Agent 配额管理
|
||||
)
|
||||
|
||||
|
||||
@@ -35,5 +36,9 @@ def register_routes(app: FastAPI) -> None:
|
||||
monitoring.router,
|
||||
metrics.router,
|
||||
websocket.router,
|
||||
# 平台 Agent 配额管理路由
|
||||
platform_agent_quota.channel_router, # 渠道平台 Agent 配额管理
|
||||
platform_agent_quota.admin_router, # 管理员平台 Agent 配额管理
|
||||
platform_agent_quota.user_router, # 用户平台 Agent 配额管理
|
||||
):
|
||||
app.include_router(router)
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select, func, and_, desc, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import uuid
|
||||
import logging
|
||||
import structlog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
from database import get_db
|
||||
from models import (
|
||||
@@ -452,7 +452,8 @@ async def get_admin_dashboard_stats(
|
||||
client = get_agent_manager_client()
|
||||
|
||||
# 获取所有运行中的 Agent
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
k8s_agents_count = len(k8s_agents)
|
||||
|
||||
# 获取每个 Agent 的资源配置
|
||||
@@ -461,21 +462,11 @@ async def get_admin_dashboard_stats(
|
||||
if agent_name:
|
||||
try:
|
||||
metrics = await client.get_agent_metrics(agent_name)
|
||||
# 解析 CPU (如 "500m" -> 0.5 核)
|
||||
cpu_limit = metrics.limits.get("cpu", "0")
|
||||
if cpu_limit.endswith("m"):
|
||||
k8s_total_cpu += float(cpu_limit[:-1]) / 1000
|
||||
else:
|
||||
k8s_total_cpu += float(cpu_limit)
|
||||
|
||||
# 解析内存 (如 "512Mi" -> 0.5 GB)
|
||||
memory_limit = metrics.limits.get("memory", "0")
|
||||
if memory_limit.endswith("Mi"):
|
||||
k8s_total_memory += float(memory_limit[:-2]) / 1024
|
||||
elif memory_limit.endswith("Gi"):
|
||||
k8s_total_memory += float(memory_limit[:-2])
|
||||
elif memory_limit.endswith("Ki"):
|
||||
k8s_total_memory += float(memory_limit[:-2]) / (1024 * 1024)
|
||||
# 使用新的属性访问器获取资源使用量
|
||||
# cpu_usage_millicores 返回毫核(如 50m -> 50.0)
|
||||
k8s_total_cpu += metrics.cpu_usage_millicores / 1000 # 转换为核
|
||||
# memory_usage_mb 返回 MB
|
||||
k8s_total_memory += metrics.memory_usage_mb / 1024 # 转换为 GB
|
||||
except Exception as e:
|
||||
logger.warning(f"获取 Agent {agent_name} 资源指标失败: {e}")
|
||||
|
||||
@@ -699,12 +690,12 @@ async def list_channels(
|
||||
agent = agent_result.scalar_one_or_none()
|
||||
|
||||
# 如果仍然没找到,检查是否是平台 Agent 模板
|
||||
if not agent and alloc.resource_id in PLATFORM_AGENT_TEMPLATES:
|
||||
template = PLATFORM_AGENT_TEMPLATES[alloc.resource_id]
|
||||
if not agent and alloc.resource_id in TEMPLATE_RESOURCE_CONFIG:
|
||||
resource_config = TEMPLATE_RESOURCE_CONFIG[alloc.resource_id]
|
||||
# 使用模板的默认资源配置
|
||||
quantity = alloc.quantity or 1
|
||||
# 解析 CPU (如 "500m" -> 0.5 核)
|
||||
cpu_limit = template.get("cpuLimit", "0")
|
||||
cpu_limit = resource_config.get("cpuLimit", "0")
|
||||
if isinstance(cpu_limit, str) and cpu_limit.endswith("m"):
|
||||
total_cpu += float(cpu_limit[:-1]) / 1000 * quantity
|
||||
elif cpu_limit:
|
||||
@@ -713,7 +704,7 @@ async def list_channels(
|
||||
except ValueError:
|
||||
pass
|
||||
# 解析内存 (如 "512Mi" -> 0.5 GB)
|
||||
memory_limit = template.get("memoryLimit", "0")
|
||||
memory_limit = resource_config.get("memoryLimit", "0")
|
||||
if isinstance(memory_limit, str):
|
||||
if memory_limit.endswith("Mi"):
|
||||
total_memory += float(memory_limit[:-2]) / 1024 * quantity
|
||||
@@ -1155,7 +1146,8 @@ async def get_resource_allocation_stats(
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
|
||||
platform_stats["count"] = len(k8s_agents)
|
||||
|
||||
@@ -1179,28 +1171,9 @@ async def get_resource_allocation_stats(
|
||||
# 获取资源配置
|
||||
try:
|
||||
metrics = await client.get_agent_metrics(agent_name)
|
||||
if hasattr(metrics, 'limits'):
|
||||
cpu_limit = metrics.limits.get("cpu", "0")
|
||||
memory_limit = metrics.limits.get("memory", "0")
|
||||
|
||||
# 解析 CPU
|
||||
if isinstance(cpu_limit, str):
|
||||
if cpu_limit.endswith("m"):
|
||||
platform_stats["cpu"] += float(cpu_limit[:-1]) / 1000
|
||||
elif cpu_limit:
|
||||
try:
|
||||
platform_stats["cpu"] += float(cpu_limit)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 解析内存
|
||||
if isinstance(memory_limit, str):
|
||||
if memory_limit.endswith("Mi"):
|
||||
platform_stats["memory"] += float(memory_limit[:-2]) / 1024
|
||||
elif memory_limit.endswith("Gi"):
|
||||
platform_stats["memory"] += float(memory_limit[:-2])
|
||||
elif memory_limit.endswith("Ki"):
|
||||
platform_stats["memory"] += float(memory_limit[:-2]) / (1024 * 1024)
|
||||
# 使用新的属性访问器获取资源使用量
|
||||
platform_stats["cpu"] += metrics.cpu_usage_millicores / 1000 # 转换为核
|
||||
platform_stats["memory"] += metrics.memory_usage_mb / 1024 # 转换为 GB
|
||||
except Exception as e:
|
||||
logger.debug(f"获取 Agent {agent_name} 资源指标失败: {e}")
|
||||
|
||||
@@ -1501,46 +1474,26 @@ async def list_all_agents(
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
logger.info(f"从 agent-manager 获取到 {len(k8s_agents)} 个 Agent")
|
||||
|
||||
for agent in k8s_agents:
|
||||
# 解析资源配置
|
||||
cpu_limit = "0"
|
||||
memory_limit = "0"
|
||||
cpu_usage_str = "0"
|
||||
memory_usage_str = "0"
|
||||
cpu_value = 0.0
|
||||
memory_value = 0.0
|
||||
try:
|
||||
metrics = await client.get_agent_metrics(agent.get("name", ""))
|
||||
cpu_limit = metrics.limits.get("cpu", "0") if hasattr(metrics, 'limits') else "0"
|
||||
memory_limit = metrics.limits.get("memory", "0") if hasattr(metrics, 'limits') else "0"
|
||||
# 使用新的属性访问器
|
||||
cpu_usage_str = metrics.cpu_usage
|
||||
memory_usage_str = metrics.memory_usage
|
||||
cpu_value = metrics.cpu_usage_millicores / 1000 # 转换为核
|
||||
memory_value = metrics.memory_usage_mb / 1024 # 转换为 GB
|
||||
except Exception as e:
|
||||
logger.warning(f"获取 Agent {agent.get('name')} 资源指标失败: {e}")
|
||||
|
||||
# 解析 CPU (如 "500m" -> 0.5 核)
|
||||
cpu_value = 0.0
|
||||
if isinstance(cpu_limit, str):
|
||||
if cpu_limit.endswith("m"):
|
||||
cpu_value = float(cpu_limit[:-1]) / 1000
|
||||
elif cpu_limit:
|
||||
try:
|
||||
cpu_value = float(cpu_limit)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 解析内存 (如 "512Mi" -> 0.5 GB)
|
||||
memory_value = 0.0
|
||||
if isinstance(memory_limit, str):
|
||||
if memory_limit.endswith("Mi"):
|
||||
memory_value = float(memory_limit[:-2]) / 1024
|
||||
elif memory_limit.endswith("Gi"):
|
||||
memory_value = float(memory_limit[:-2])
|
||||
elif memory_limit.endswith("Ki"):
|
||||
memory_value = float(memory_limit[:-2]) / (1024 * 1024)
|
||||
elif memory_limit:
|
||||
try:
|
||||
memory_value = float(memory_limit)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
items.append({
|
||||
"id": agent.get("name", ""),
|
||||
"name": agent.get("name", ""),
|
||||
@@ -1553,9 +1506,9 @@ async def list_all_agents(
|
||||
"memory": round(memory_value, 2),
|
||||
"maxInstances": 1,
|
||||
"cpuRequest": agent.get("cpu_request", "100m"),
|
||||
"cpuLimit": cpu_limit,
|
||||
"cpuLimit": cpu_usage_str, # 使用 cpu_usage 作为显示值
|
||||
"memoryRequest": agent.get("memory_request", "128Mi"),
|
||||
"memoryLimit": memory_limit,
|
||||
"memoryLimit": memory_usage_str, # 使用 memory_usage 作为显示值
|
||||
"podName": agent.get("pod_name", ""),
|
||||
"podIp": agent.get("pod_ip", ""),
|
||||
"namespace": agent.get("namespace", "ai-agents"),
|
||||
@@ -1762,7 +1715,8 @@ async def monitor_agents(
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
logger.info(f"监控: 从 agent-manager 获取到 {len(k8s_agents)} 个平台端 Agent")
|
||||
|
||||
for agent in k8s_agents:
|
||||
@@ -1770,48 +1724,53 @@ async def monitor_agents(
|
||||
k8s_agent_names.add(agent_name)
|
||||
|
||||
# 解析资源配置和使用率
|
||||
cpu_limit = "0"
|
||||
memory_limit = "0"
|
||||
cpu_usage = 0.0
|
||||
memory_usage = 0.0
|
||||
# 实时使用量(从 metrics-server 获取)
|
||||
cpu_usage_current_str = "0"
|
||||
memory_usage_current_str = "0"
|
||||
# 资源限制(从 Pod spec 获取)
|
||||
cpu_limit_str = "0"
|
||||
memory_limit_str = "0"
|
||||
cpu_request_str = "0"
|
||||
memory_request_str = "0"
|
||||
# 数值(用于统计)
|
||||
cpu_value = 0.0
|
||||
memory_value = 0.0
|
||||
# 使用率百分比
|
||||
cpu_utilization = None
|
||||
memory_utilization = None
|
||||
# metrics 时间戳
|
||||
metrics_timestamp = None
|
||||
has_realtime_metrics = False
|
||||
|
||||
try:
|
||||
metrics = await client.get_agent_metrics(agent_name)
|
||||
if hasattr(metrics, 'limits'):
|
||||
cpu_limit = metrics.limits.get("cpu", "0")
|
||||
memory_limit = metrics.limits.get("memory", "0")
|
||||
if hasattr(metrics, 'usage'):
|
||||
cpu_usage = float(metrics.usage.get('cpu', 0))
|
||||
memory_usage = float(metrics.usage.get('memory', 0))
|
||||
# 资源限制(从 Pod spec 获取)
|
||||
cpu_limit_str = metrics.cpu_limit
|
||||
memory_limit_str = metrics.memory_limit
|
||||
cpu_request_str = metrics.cpu_request
|
||||
memory_request_str = metrics.memory_request
|
||||
|
||||
# 实时使用量(从 metrics-server 获取)
|
||||
if metrics.has_realtime_metrics:
|
||||
has_realtime_metrics = True
|
||||
cpu_usage_current_str = metrics.cpu_usage_current
|
||||
memory_usage_current_str = metrics.memory_usage_current
|
||||
# 使用实时数据计算数值
|
||||
cpu_value = metrics.cpu_usage_current_millicores / 1000 # 转换为核
|
||||
memory_value = metrics.memory_usage_current_mb / 1024 # 转换为 GB
|
||||
# 使用率百分比
|
||||
cpu_utilization = metrics.cpu_utilization_percent
|
||||
memory_utilization = metrics.memory_utilization_percent
|
||||
metrics_timestamp = metrics.timestamp
|
||||
else:
|
||||
# 没有实时数据,使用 limits 作为显示值
|
||||
cpu_usage_current_str = cpu_limit_str
|
||||
memory_usage_current_str = memory_limit_str
|
||||
cpu_value = metrics.cpu_limit_millicores / 1000
|
||||
memory_value = metrics.memory_limit_mb / 1024
|
||||
except Exception as e:
|
||||
logger.debug(f"获取 Agent {agent_name} 资源指标失败: {e}")
|
||||
|
||||
# 解析 CPU (如 "500m" -> 0.5 核)
|
||||
cpu_value = 0.0
|
||||
if isinstance(cpu_limit, str):
|
||||
if cpu_limit.endswith("m"):
|
||||
cpu_value = float(cpu_limit[:-1]) / 1000
|
||||
elif cpu_limit:
|
||||
try:
|
||||
cpu_value = float(cpu_limit)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 解析内存 (如 "512Mi" -> 0.5 GB)
|
||||
memory_value = 0.0
|
||||
if isinstance(memory_limit, str):
|
||||
if memory_limit.endswith("Mi"):
|
||||
memory_value = float(memory_limit[:-2]) / 1024
|
||||
elif memory_limit.endswith("Gi"):
|
||||
memory_value = float(memory_limit[:-2])
|
||||
elif memory_limit.endswith("Ki"):
|
||||
memory_value = float(memory_limit[:-2]) / (1024 * 1024)
|
||||
elif memory_limit:
|
||||
try:
|
||||
memory_value = float(memory_limit)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 根据 K8s 状态判断健康状态
|
||||
k8s_status = agent.get("status", "unknown")
|
||||
if k8s_status == "Running":
|
||||
@@ -1833,17 +1792,27 @@ async def monitor_agents(
|
||||
"type": "platform",
|
||||
"status": k8s_status.lower() if k8s_status else "unknown",
|
||||
"healthStatus": agent_health_status,
|
||||
"lastHealthCheck": None,
|
||||
"lastHealthCheck": metrics_timestamp,
|
||||
"healthMessage": f"K8s Pod 状态: {k8s_status}",
|
||||
"totalExecutions": 0,
|
||||
"successRate": 0.0,
|
||||
"avgExecutionTime": 0.0,
|
||||
"cpu": round(cpu_value, 2),
|
||||
"memory": round(memory_value, 2),
|
||||
"cpuUsage": cpu_usage,
|
||||
"memoryUsage": memory_usage,
|
||||
"cpuLimit": cpu_limit,
|
||||
"memoryLimit": memory_limit,
|
||||
# 实时使用量(从 metrics-server 获取)
|
||||
"cpuUsage": cpu_usage_current_str,
|
||||
"memoryUsage": memory_usage_current_str,
|
||||
# 资源限制(从 Pod spec 获取)
|
||||
"cpuLimit": cpu_limit_str,
|
||||
"memoryLimit": memory_limit_str,
|
||||
"cpuRequest": cpu_request_str,
|
||||
"memoryRequest": memory_request_str,
|
||||
# 使用率百分比
|
||||
"cpuUtilization": round(cpu_utilization, 2) if cpu_utilization is not None else None,
|
||||
"memoryUtilization": round(memory_utilization, 2) if memory_utilization is not None else None,
|
||||
# 是否有实时 metrics 数据
|
||||
"hasRealtimeMetrics": has_realtime_metrics,
|
||||
"metricsTimestamp": metrics_timestamp,
|
||||
# K8s 信息
|
||||
"podName": agent.get("pod_name", ""),
|
||||
"podIp": agent.get("pod_ip", ""),
|
||||
@@ -2543,50 +2512,156 @@ async def get_admin_roles(
|
||||
|
||||
# ============= 平台 Agent 资源申请审批 =============
|
||||
|
||||
# 平台 Agent 模板列表(与 channel.py 保持一致)
|
||||
PLATFORM_AGENT_TEMPLATES = {
|
||||
"gpt-assistant": {
|
||||
"name": "gpt-assistant",
|
||||
"displayName": "GPT 智能助手",
|
||||
"description": "基于 GPT 的通用智能助手,支持多轮对话和任务执行",
|
||||
# 模板显示信息(用于在 Agent Manager 返回数据不包含显示名称时提供默认值)
|
||||
TEMPLATE_DISPLAY_INFO: Dict[str, Dict[str, str]] = {
|
||||
"echo_agent": {
|
||||
"displayName": "Echo 测试服务",
|
||||
"description": "简单的 Echo 服务,用于测试和调试",
|
||||
"category": "testing",
|
||||
},
|
||||
"chat_agent": {
|
||||
"displayName": "聊天对话服务",
|
||||
"description": "通用聊天对话 Agent,支持多轮对话",
|
||||
"category": "assistant",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
"code_agent": {
|
||||
"displayName": "代码执行服务",
|
||||
"description": "代码生成和执行 Agent,支持多种编程语言",
|
||||
"category": "development",
|
||||
},
|
||||
"search_agent": {
|
||||
"displayName": "通用搜索服务",
|
||||
"description": "通用搜索 Agent,支持多种搜索引擎",
|
||||
"category": "search",
|
||||
},
|
||||
"jina_search_agent": {
|
||||
"displayName": "Jina 搜索服务",
|
||||
"description": "基于 Jina AI 的语义搜索 Agent",
|
||||
"category": "search",
|
||||
},
|
||||
"mysql_agent": {
|
||||
"displayName": "MySQL 数据库客户端",
|
||||
"description": "MySQL 数据库查询和管理 Agent",
|
||||
"category": "database",
|
||||
},
|
||||
"postgresql_agent": {
|
||||
"displayName": "PostgreSQL 数据库客户端",
|
||||
"description": "PostgreSQL 数据库查询和管理 Agent",
|
||||
"category": "database",
|
||||
},
|
||||
}
|
||||
|
||||
# 模板资源配置建议
|
||||
TEMPLATE_RESOURCE_CONFIG: Dict[str, Dict[str, str]] = {
|
||||
"echo_agent": {
|
||||
"cpuRequest": "100m",
|
||||
"cpuLimit": "500m",
|
||||
"memoryRequest": "128Mi",
|
||||
"memoryLimit": "512Mi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/gpt-assistant:latest",
|
||||
"status": "available",
|
||||
},
|
||||
"code-reviewer": {
|
||||
"name": "code-reviewer",
|
||||
"displayName": "代码审查助手",
|
||||
"description": "专业的代码审查 Agent,支持多种编程语言",
|
||||
"category": "development",
|
||||
"version": "1.0.0",
|
||||
"chat_agent": {
|
||||
"cpuRequest": "200m",
|
||||
"cpuLimit": "1000m",
|
||||
"memoryRequest": "256Mi",
|
||||
"memoryLimit": "1Gi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/code-reviewer:latest",
|
||||
"status": "available",
|
||||
},
|
||||
"data-analyst": {
|
||||
"name": "data-analyst",
|
||||
"displayName": "数据分析助手",
|
||||
"description": "数据分析和可视化 Agent,支持 SQL 查询和图表生成",
|
||||
"category": "analytics",
|
||||
"version": "1.0.0",
|
||||
"code_agent": {
|
||||
"cpuRequest": "500m",
|
||||
"cpuLimit": "2000m",
|
||||
"memoryRequest": "512Mi",
|
||||
"memoryLimit": "2Gi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/data-analyst:latest",
|
||||
"status": "available",
|
||||
},
|
||||
"search_agent": {
|
||||
"cpuRequest": "200m",
|
||||
"cpuLimit": "1000m",
|
||||
"memoryRequest": "256Mi",
|
||||
"memoryLimit": "1Gi",
|
||||
},
|
||||
"jina_search_agent": {
|
||||
"cpuRequest": "500m",
|
||||
"cpuLimit": "2000m",
|
||||
"memoryRequest": "1Gi",
|
||||
"memoryLimit": "4Gi",
|
||||
},
|
||||
"mysql_agent": {
|
||||
"cpuRequest": "100m",
|
||||
"cpuLimit": "500m",
|
||||
"memoryRequest": "128Mi",
|
||||
"memoryLimit": "512Mi",
|
||||
},
|
||||
"postgresql_agent": {
|
||||
"cpuRequest": "100m",
|
||||
"cpuLimit": "500m",
|
||||
"memoryRequest": "128Mi",
|
||||
"memoryLimit": "512Mi",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _get_platform_templates_from_agent_manager() -> List[Dict[str, Any]]:
|
||||
"""从 Agent Manager 获取平台 Agent 模板列表"""
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client, AgentManagerError
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_platform_templates()
|
||||
|
||||
result = []
|
||||
for template in templates:
|
||||
template_name = template.template
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(template_name, {})
|
||||
resource_config = TEMPLATE_RESOURCE_CONFIG.get(template_name, {})
|
||||
|
||||
result.append({
|
||||
"name": template_name,
|
||||
"displayName": display_info.get("displayName", template_name),
|
||||
"description": display_info.get("description", f"{template_name} Agent"),
|
||||
"category": display_info.get("category", "general"),
|
||||
"version": "1.0.0",
|
||||
"port": template.port,
|
||||
"envInfo": template.env_info,
|
||||
"cpuRequest": resource_config.get("cpuRequest", "100m"),
|
||||
"cpuLimit": resource_config.get("cpuLimit", "500m"),
|
||||
"memoryRequest": resource_config.get("memoryRequest", "128Mi"),
|
||||
"memoryLimit": resource_config.get("memoryLimit", "512Mi"),
|
||||
"status": "available",
|
||||
})
|
||||
|
||||
logger.info("admin: 从 Agent Manager 获取平台模板成功", count=len(result))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("admin: 从 Agent Manager 获取平台模板失败,使用默认模板", error=str(e))
|
||||
# 返回基于 TEMPLATE_DISPLAY_INFO 的默认模板
|
||||
result = []
|
||||
for name, info in TEMPLATE_DISPLAY_INFO.items():
|
||||
resource_config = TEMPLATE_RESOURCE_CONFIG.get(name, {})
|
||||
result.append({
|
||||
"name": name,
|
||||
"displayName": info.get("displayName", name),
|
||||
"description": info.get("description", f"{name} Agent"),
|
||||
"category": info.get("category", "general"),
|
||||
"version": "1.0.0",
|
||||
"cpuRequest": resource_config.get("cpuRequest", "100m"),
|
||||
"cpuLimit": resource_config.get("cpuLimit", "500m"),
|
||||
"memoryRequest": resource_config.get("memoryRequest", "128Mi"),
|
||||
"memoryLimit": resource_config.get("memoryLimit", "512Mi"),
|
||||
"status": "available",
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def _validate_template_exists(template_name: str) -> bool:
|
||||
"""验证模板是否存在"""
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_platform_templates()
|
||||
return any(t.template == template_name for t in templates)
|
||||
except Exception as e:
|
||||
logger.warning("admin: 验证模板失败,使用本地验证", error=str(e))
|
||||
return template_name in TEMPLATE_DISPLAY_INFO
|
||||
|
||||
|
||||
@router.get("/platform-agents/templates", response_model=SuccessResponse)
|
||||
async def list_platform_agent_templates(
|
||||
principal: dict = Depends(require_auth),
|
||||
@@ -2594,13 +2669,13 @@ async def list_platform_agent_templates(
|
||||
"""
|
||||
获取平台 Agent 模板列表
|
||||
|
||||
返回所有可用的平台 Agent 模板信息。
|
||||
从 Agent Manager 动态获取所有可用的平台 Agent 模板信息。
|
||||
|
||||
权限:view:resources (所有管理员)
|
||||
"""
|
||||
_verify_read_permission(principal)
|
||||
|
||||
templates = list(PLATFORM_AGENT_TEMPLATES.values())
|
||||
templates = await _get_platform_templates_from_agent_manager()
|
||||
|
||||
return SuccessResponse(data={"templates": templates})
|
||||
|
||||
@@ -2638,14 +2713,14 @@ async def list_platform_agent_applications(
|
||||
|
||||
data = []
|
||||
for app, channel in result.all():
|
||||
template = PLATFORM_AGENT_TEMPLATES.get(app.template_name, {})
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(app.template_name, {})
|
||||
data.append({
|
||||
"id": str(app.id),
|
||||
"channelId": str(app.channel_id),
|
||||
"channelName": channel.name,
|
||||
"resourceType": app.resource_type,
|
||||
"templateName": app.template_name,
|
||||
"templateDisplayName": template.get("displayName", app.template_name),
|
||||
"templateDisplayName": display_info.get("displayName", app.template_name),
|
||||
"requestedPodQuota": app.requested_pod_quota,
|
||||
"approvedPodQuota": app.approved_pod_quota,
|
||||
"reason": app.reason,
|
||||
@@ -2776,13 +2851,13 @@ async def list_platform_agent_allocations(
|
||||
|
||||
data = []
|
||||
for quota, channel in result.all():
|
||||
template = PLATFORM_AGENT_TEMPLATES.get(quota.template_name, {})
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(quota.template_name, {})
|
||||
data.append({
|
||||
"id": str(quota.id),
|
||||
"channelId": str(quota.target_id),
|
||||
"channelName": channel.name if channel else "未知",
|
||||
"templateName": quota.template_name,
|
||||
"templateDisplayName": template.get("displayName", quota.template_name),
|
||||
"templateDisplayName": display_info.get("displayName", quota.template_name),
|
||||
"podQuota": quota.pod_quota,
|
||||
"podUsed": quota.pod_used,
|
||||
"podRemaining": quota.pod_quota - quota.pod_used,
|
||||
@@ -2824,7 +2899,8 @@ async def allocate_platform_agent_to_channel(
|
||||
)
|
||||
|
||||
# 验证模板存在
|
||||
if template_name not in PLATFORM_AGENT_TEMPLATES:
|
||||
template_exists = await _validate_template_exists(template_name)
|
||||
if not template_exists:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"平台 Agent 模板 '{template_name}' 不存在"
|
||||
@@ -2865,14 +2941,14 @@ async def allocate_platform_agent_to_channel(
|
||||
|
||||
await db.commit()
|
||||
|
||||
template = PLATFORM_AGENT_TEMPLATES[template_name]
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(template_name, {})
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"channelId": str(channel_uuid),
|
||||
"channelName": channel.name,
|
||||
"templateName": template_name,
|
||||
"templateDisplayName": template["displayName"],
|
||||
"templateDisplayName": display_info.get("displayName", template_name),
|
||||
"podQuota": pod_quota,
|
||||
},
|
||||
message=message
|
||||
@@ -2968,20 +3044,21 @@ async def get_platform_agents_status(
|
||||
try:
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
|
||||
for agent in k8s_agents:
|
||||
agent_name = agent.get("name", "")
|
||||
k8s_status = agent.get("status", "unknown")
|
||||
|
||||
# 获取资源使用情况
|
||||
cpu_usage = 0.0
|
||||
memory_usage = 0.0
|
||||
cpu_usage = "0"
|
||||
memory_usage = "0"
|
||||
try:
|
||||
metrics = await client.get_agent_metrics(agent_name)
|
||||
if hasattr(metrics, 'usage'):
|
||||
cpu_usage = float(metrics.usage.get('cpu', 0))
|
||||
memory_usage = float(metrics.usage.get('memory', 0))
|
||||
# 使用新的属性访问器
|
||||
cpu_usage = metrics.cpu_usage
|
||||
memory_usage = metrics.memory_usage
|
||||
except Exception as e:
|
||||
logger.debug(f"获取 Agent {agent_name} 资源指标失败: {e}")
|
||||
|
||||
|
||||
@@ -119,6 +119,70 @@ async def list_templates() -> TemplateListResponse:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/templates/platform", response_model=TemplateListResponse)
|
||||
async def list_platform_templates() -> TemplateListResponse:
|
||||
"""
|
||||
获取所有平台 Agent 模板
|
||||
|
||||
平台 Agent 模板是预定义的、由平台管理的 Agent 类型,
|
||||
用户无需配置环境变量即可使用。
|
||||
"""
|
||||
try:
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_platform_templates()
|
||||
|
||||
return TemplateListResponse(
|
||||
templates=[
|
||||
TemplateInfo(
|
||||
template=t.template,
|
||||
port=t.port,
|
||||
env_info=t.env_info
|
||||
)
|
||||
for t in templates
|
||||
],
|
||||
count=len(templates),
|
||||
type="platform"
|
||||
)
|
||||
except AgentManagerError as e:
|
||||
logger.error("获取平台模板列表失败", error=str(e))
|
||||
raise HTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
logger.error("获取平台模板列表失败", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/templates/custom", response_model=TemplateListResponse)
|
||||
async def list_custom_templates() -> TemplateListResponse:
|
||||
"""
|
||||
获取所有自定义 Agent 模板
|
||||
|
||||
自定义 Agent 模板需要用户配置环境变量(如 API Key、数据库连接信息)。
|
||||
env_info 字段包含 required(必需)和 optional(可选)环境变量说明。
|
||||
"""
|
||||
try:
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_custom_templates()
|
||||
|
||||
return TemplateListResponse(
|
||||
templates=[
|
||||
TemplateInfo(
|
||||
template=t.template,
|
||||
port=t.port,
|
||||
env_info=t.env_info
|
||||
)
|
||||
for t in templates
|
||||
],
|
||||
count=len(templates),
|
||||
type="custom"
|
||||
)
|
||||
except AgentManagerError as e:
|
||||
logger.error("获取自定义模板列表失败", error=str(e))
|
||||
raise HTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
logger.error("获取自定义模板列表失败", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/templates/{template_name}", response_model=TemplateInfo)
|
||||
async def get_template(template_name: str) -> TemplateInfo:
|
||||
"""获取模板详情"""
|
||||
@@ -200,21 +264,22 @@ async def create_agent(
|
||||
# 构建 Pod 名称(使用 agent ID 确保唯一性)
|
||||
pod_name = f"{request.name}-{str(agent.id)[:8]}"
|
||||
|
||||
# 创建 Agent 配置
|
||||
# 创建 Agent 配置(适配新的 AgentConfig 格式)
|
||||
agent_config = AgentConfig(
|
||||
replicas=resource_config.replicas,
|
||||
user_id=str(user_id),
|
||||
cpu_request=resource_config.cpu_request,
|
||||
cpu_limit=resource_config.cpu_limit,
|
||||
memory_request=resource_config.memory_request,
|
||||
memory_limit=resource_config.memory_limit,
|
||||
env=resource_config.env
|
||||
)
|
||||
|
||||
# 调用 Agent Manager API 创建 Pod
|
||||
# 使用统一的 POST /agents 接口
|
||||
result = await client.create_agent(
|
||||
name=pod_name,
|
||||
template=request.template,
|
||||
config=agent_config
|
||||
config=agent_config,
|
||||
env=resource_config.env # 环境变量单独传递
|
||||
)
|
||||
|
||||
# 更新数据库记录
|
||||
@@ -523,10 +588,9 @@ async def get_agent_status(
|
||||
client = get_agent_manager_client()
|
||||
status = await client.get_agent_status(agent.pod_name)
|
||||
|
||||
# 更新数据库中的状态
|
||||
# 更新数据库中的状态(适配新的 AgentStatusResult 格式)
|
||||
agent.k8s_status = status.status
|
||||
agent.pod_ip = status.pod_ip
|
||||
agent.access_url = status.access_url
|
||||
if status.endpoints:
|
||||
agent.endpoints = status.endpoints
|
||||
|
||||
@@ -539,9 +603,9 @@ async def get_agent_status(
|
||||
k8s_status=status.status,
|
||||
pod_name=status.name,
|
||||
pod_ip=status.pod_ip,
|
||||
node=status.node,
|
||||
service_port=status.service_port,
|
||||
access_url=status.access_url,
|
||||
node=status.node_name, # 字段名从 node 改为 node_name
|
||||
service_port=agent.service_port, # 从数据库获取,新接口不返回此字段
|
||||
access_url=agent.access_url, # 从数据库获取,新接口不返回此字段
|
||||
endpoints=status.endpoints or {},
|
||||
cpu_request=agent.cpu_request,
|
||||
cpu_limit=agent.cpu_limit,
|
||||
@@ -549,7 +613,7 @@ async def get_agent_status(
|
||||
memory_limit=agent.memory_limit,
|
||||
created_at=agent.created_at,
|
||||
pod_created_at=agent.pod_created_at,
|
||||
conditions=status.conditions,
|
||||
conditions=None, # 新接口不返回 conditions
|
||||
)
|
||||
|
||||
except AgentManagerError as e:
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import select, func, and_, desc, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import uuid
|
||||
import structlog
|
||||
|
||||
from database import get_db
|
||||
from models import (
|
||||
@@ -15,7 +16,7 @@ from models import (
|
||||
BillingRecord, RechargeRecord, Application, ModelProvider,
|
||||
ChannelProviderAccess, ProviderApplication, TenantCustomAgentQuota,
|
||||
ChannelCustomAgentQuota, ResourceApplication, PlatformAgentQuota,
|
||||
AgentBillingRecord
|
||||
AgentBillingRecord, PlatformAgentTemplateConfig
|
||||
)
|
||||
from app.auth import require_auth, get_password_hash
|
||||
from app.permissions import has_permission
|
||||
@@ -38,6 +39,9 @@ from app.schemas import (
|
||||
ApplyPlatformAgentRequest,
|
||||
AllocatePlatformAgentRequest,
|
||||
)
|
||||
from app.agent_manager_client import get_agent_manager_client, AgentManagerError
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/channel", tags=["渠道合作伙伴"])
|
||||
|
||||
@@ -1733,49 +1737,182 @@ async def list_provider_access(
|
||||
|
||||
# ============= 平台 Agent 资源申请 =============
|
||||
|
||||
# 平台 Agent 模板列表(模拟数据,实际应从 agent-manager 获取)
|
||||
PLATFORM_AGENT_TEMPLATES = {
|
||||
"gpt-assistant": {
|
||||
"name": "gpt-assistant",
|
||||
"displayName": "GPT 智能助手",
|
||||
"description": "基于 GPT 的通用智能助手,支持多轮对话和任务执行",
|
||||
# 模板显示名称和描述映射(用于前端展示)
|
||||
TEMPLATE_DISPLAY_INFO = {
|
||||
"echo_agent": {
|
||||
"displayName": "Echo 测试服务",
|
||||
"description": "简单的 Echo 服务,用于测试和调试",
|
||||
"category": "testing",
|
||||
},
|
||||
"chat_agent": {
|
||||
"displayName": "聊天对话服务",
|
||||
"description": "智能聊天对话 Agent,支持多轮对话",
|
||||
"category": "assistant",
|
||||
"version": "1.0.0",
|
||||
"cpuRequest": "100m",
|
||||
"cpuLimit": "500m",
|
||||
"memoryRequest": "128Mi",
|
||||
"memoryLimit": "512Mi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/gpt-assistant:latest",
|
||||
"status": "available",
|
||||
},
|
||||
"code-reviewer": {
|
||||
"name": "code-reviewer",
|
||||
"displayName": "代码审查助手",
|
||||
"description": "专业的代码审查 Agent,支持多种编程语言",
|
||||
"code_agent": {
|
||||
"displayName": "代码执行服务",
|
||||
"description": "代码生成和执行 Agent,支持多种编程语言",
|
||||
"category": "development",
|
||||
"version": "1.0.0",
|
||||
"cpuRequest": "200m",
|
||||
"cpuLimit": "1000m",
|
||||
"memoryRequest": "256Mi",
|
||||
"memoryLimit": "1Gi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/code-reviewer:latest",
|
||||
"status": "available",
|
||||
},
|
||||
"data-analyst": {
|
||||
"name": "data-analyst",
|
||||
"displayName": "数据分析助手",
|
||||
"description": "数据分析和可视化 Agent,支持 SQL 查询和图表生成",
|
||||
"category": "analytics",
|
||||
"version": "1.0.0",
|
||||
"cpuRequest": "500m",
|
||||
"cpuLimit": "2000m",
|
||||
"memoryRequest": "512Mi",
|
||||
"memoryLimit": "2Gi",
|
||||
"imageUrl": "acr.taiji-ai.com/agents/data-analyst:latest",
|
||||
"status": "available",
|
||||
"search_agent": {
|
||||
"displayName": "通用搜索服务",
|
||||
"description": "通用搜索 Agent,支持多种搜索引擎",
|
||||
"category": "search",
|
||||
},
|
||||
"jina_search_agent": {
|
||||
"displayName": "Jina 语义搜索服务",
|
||||
"description": "基于 Jina AI 的语义搜索 Agent",
|
||||
"category": "search",
|
||||
},
|
||||
"mysql_agent": {
|
||||
"displayName": "MySQL 数据库客户端",
|
||||
"description": "MySQL 数据库查询和管理 Agent",
|
||||
"category": "database",
|
||||
},
|
||||
"postgresql_agent": {
|
||||
"displayName": "PostgreSQL 数据库客户端",
|
||||
"description": "PostgreSQL 数据库查询和管理 Agent",
|
||||
"category": "database",
|
||||
},
|
||||
}
|
||||
|
||||
# 模板资源配置建议
|
||||
TEMPLATE_RESOURCE_CONFIG = {
|
||||
"echo_agent": {"cpuRequest": "100m", "cpuLimit": "500m", "memoryRequest": "128Mi", "memoryLimit": "512Mi"},
|
||||
"chat_agent": {"cpuRequest": "200m", "cpuLimit": "1000m", "memoryRequest": "256Mi", "memoryLimit": "1Gi"},
|
||||
"code_agent": {"cpuRequest": "500m", "cpuLimit": "2000m", "memoryRequest": "512Mi", "memoryLimit": "2Gi"},
|
||||
"search_agent": {"cpuRequest": "200m", "cpuLimit": "1000m", "memoryRequest": "256Mi", "memoryLimit": "1Gi"},
|
||||
"jina_search_agent": {"cpuRequest": "500m", "cpuLimit": "2000m", "memoryRequest": "1Gi", "memoryLimit": "4Gi"},
|
||||
"mysql_agent": {"cpuRequest": "100m", "cpuLimit": "500m", "memoryRequest": "128Mi", "memoryLimit": "512Mi"},
|
||||
"postgresql_agent": {"cpuRequest": "100m", "cpuLimit": "500m", "memoryRequest": "128Mi", "memoryLimit": "512Mi"},
|
||||
}
|
||||
|
||||
|
||||
async def _get_platform_templates_from_agent_manager(db: AsyncSession = None) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
从 Agent Manager 获取平台模板列表,并合并管理员配置
|
||||
|
||||
Args:
|
||||
db: 数据库会话,用于获取管理员配置。如果为 None,则不获取管理员配置
|
||||
|
||||
Returns:
|
||||
模板字典,key 为模板名称,value 为模板信息
|
||||
"""
|
||||
try:
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_platform_templates()
|
||||
|
||||
# 获取管理员配置(如果提供了数据库会话)
|
||||
admin_configs = {}
|
||||
if db:
|
||||
config_result = await db.execute(
|
||||
select(PlatformAgentTemplateConfig).where(
|
||||
PlatformAgentTemplateConfig.is_enabled == True
|
||||
)
|
||||
)
|
||||
for config in config_result.scalars().all():
|
||||
admin_configs[config.template_name] = config
|
||||
|
||||
result = {}
|
||||
for t in templates:
|
||||
template_name = t.template
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(template_name, {})
|
||||
admin_config = admin_configs.get(template_name)
|
||||
|
||||
# 如果有管理员配置,使用管理员配置的值;否则返回 null/0
|
||||
if admin_config:
|
||||
resource_config = {
|
||||
"cpuRequest": admin_config.cpu_request,
|
||||
"cpuLimit": admin_config.cpu_limit,
|
||||
"memoryRequest": admin_config.memory_request,
|
||||
"memoryLimit": admin_config.memory_limit,
|
||||
"maxPods": admin_config.max_pods or 0,
|
||||
"isConfigured": True,
|
||||
}
|
||||
# 如果管理员配置了显示名称和描述,使用管理员配置的
|
||||
display_name = admin_config.display_name or display_info.get("displayName", template_name)
|
||||
description = admin_config.description or display_info.get("description", f"{template_name} Agent")
|
||||
else:
|
||||
# 未配置时返回 null/0,表示管理员尚未配置
|
||||
resource_config = {
|
||||
"cpuRequest": None,
|
||||
"cpuLimit": None,
|
||||
"memoryRequest": None,
|
||||
"memoryLimit": None,
|
||||
"maxPods": 0,
|
||||
"isConfigured": False,
|
||||
}
|
||||
display_name = display_info.get("displayName", template_name)
|
||||
description = display_info.get("description", f"{template_name} Agent")
|
||||
|
||||
result[template_name] = {
|
||||
"name": template_name,
|
||||
"displayName": display_name,
|
||||
"description": description,
|
||||
"category": display_info.get("category", "general"),
|
||||
"version": "1.0.0",
|
||||
"port": t.port,
|
||||
"envInfo": t.env_info,
|
||||
"status": "available" if (admin_config and admin_config.is_enabled) else "not_configured",
|
||||
**resource_config,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except AgentManagerError as e:
|
||||
logger.error("failed_to_get_platform_templates", error=str(e))
|
||||
# 返回空字典,让调用方处理
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error("unexpected_error_getting_templates", error=str(e))
|
||||
return {}
|
||||
|
||||
|
||||
async def _get_custom_templates_from_agent_manager() -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
从 Agent Manager 获取自定义模板列表
|
||||
|
||||
Returns:
|
||||
模板字典,key 为模板名称,value 为模板信息
|
||||
"""
|
||||
try:
|
||||
client = get_agent_manager_client()
|
||||
templates = await client.list_custom_templates()
|
||||
|
||||
result = {}
|
||||
for t in templates:
|
||||
template_name = t.template
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(template_name, {})
|
||||
resource_config = TEMPLATE_RESOURCE_CONFIG.get(template_name, {
|
||||
"cpuRequest": "100m",
|
||||
"cpuLimit": "500m",
|
||||
"memoryRequest": "128Mi",
|
||||
"memoryLimit": "512Mi"
|
||||
})
|
||||
|
||||
result[template_name] = {
|
||||
"name": template_name,
|
||||
"displayName": display_info.get("displayName", template_name),
|
||||
"description": display_info.get("description", f"{template_name} Agent"),
|
||||
"category": display_info.get("category", "general"),
|
||||
"version": "1.0.0",
|
||||
"port": t.port,
|
||||
"envInfo": t.env_info,
|
||||
"requiredEnvVars": t.env_info.get("required", {}),
|
||||
"optionalEnvVars": t.env_info.get("optional", {}),
|
||||
"status": "available",
|
||||
**resource_config,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except AgentManagerError as e:
|
||||
logger.error("failed_to_get_custom_templates", error=str(e))
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error("unexpected_error_getting_custom_templates", error=str(e))
|
||||
return {}
|
||||
|
||||
|
||||
@router.get("/available-platform-agents", response_model=SuccessResponse)
|
||||
async def list_available_platform_agents(
|
||||
@@ -1788,6 +1925,8 @@ async def list_available_platform_agents(
|
||||
渠道管理员可以查看平台上所有可用的 Agent 模板,
|
||||
并查看自己是否已获得使用权限和配额情况。
|
||||
|
||||
模板数据从 Agent Manager 动态获取。
|
||||
|
||||
权限:view:resources (channel_admin, billing_admin, operations_admin)
|
||||
"""
|
||||
_verify_permission(principal, "view:resources")
|
||||
@@ -1800,6 +1939,15 @@ async def list_available_platform_agents(
|
||||
detail="无法获取渠道ID"
|
||||
)
|
||||
|
||||
# 从 Agent Manager 获取平台模板(传递数据库会话以获取管理员配置)
|
||||
platform_templates = await _get_platform_templates_from_agent_manager(db)
|
||||
|
||||
if not platform_templates:
|
||||
logger.warning("no_platform_templates_available", channel_id=str(channel_id))
|
||||
return SuccessResponse(
|
||||
data={"templates": [], "warning": "无法从 Agent Manager 获取模板列表"}
|
||||
)
|
||||
|
||||
# 获取渠道已有的平台 Agent 配额
|
||||
quota_result = await db.execute(
|
||||
select(PlatformAgentQuota).where(
|
||||
@@ -1824,7 +1972,7 @@ async def list_available_platform_agents(
|
||||
pending_apps = {a.template_name for a in pending_result.scalars().all()}
|
||||
|
||||
data = []
|
||||
for template_name, template in PLATFORM_AGENT_TEMPLATES.items():
|
||||
for template_name, template in platform_templates.items():
|
||||
quota = quota_map.get(template_name)
|
||||
item = {
|
||||
**template,
|
||||
@@ -1862,8 +2010,9 @@ async def apply_for_platform_agent(
|
||||
detail="无法获取渠道ID"
|
||||
)
|
||||
|
||||
# 验证模板是否存在
|
||||
if req.templateName not in PLATFORM_AGENT_TEMPLATES:
|
||||
# 从 Agent Manager 获取平台模板,验证模板是否存在
|
||||
platform_templates = await _get_platform_templates_from_agent_manager()
|
||||
if req.templateName not in platform_templates:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"平台 Agent 模板 '{req.templateName}' 不存在"
|
||||
@@ -1900,13 +2049,13 @@ async def apply_for_platform_agent(
|
||||
await db.commit()
|
||||
await db.refresh(application)
|
||||
|
||||
template = PLATFORM_AGENT_TEMPLATES[req.templateName]
|
||||
template = platform_templates[req.templateName]
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"id": str(application.id),
|
||||
"templateName": req.templateName,
|
||||
"templateDisplayName": template["displayName"],
|
||||
"templateDisplayName": template.get("displayName", req.templateName),
|
||||
"requestedPodQuota": req.requestedPodQuota,
|
||||
"status": "pending",
|
||||
},
|
||||
@@ -1964,14 +2113,15 @@ async def list_platform_agent_applications(
|
||||
data = []
|
||||
for app in applications:
|
||||
channel = channels_map.get(str(app.channel_id))
|
||||
template = PLATFORM_AGENT_TEMPLATES.get(app.template_name, {})
|
||||
# 使用 TEMPLATE_DISPLAY_INFO 获取显示名称
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(app.template_name, {})
|
||||
data.append({
|
||||
"id": str(app.id),
|
||||
"channelId": str(app.channel_id),
|
||||
"channelName": channel.name if channel else "未知",
|
||||
"resourceType": app.resource_type,
|
||||
"templateName": app.template_name,
|
||||
"templateDisplayName": template.get("displayName", app.template_name),
|
||||
"templateDisplayName": display_info.get("displayName", app.template_name),
|
||||
"requestedPodQuota": app.requested_pod_quota,
|
||||
"approvedPodQuota": app.approved_pod_quota,
|
||||
"reason": app.reason,
|
||||
@@ -2021,10 +2171,11 @@ async def list_channel_platform_agent_quotas(
|
||||
|
||||
data = []
|
||||
for quota in quotas:
|
||||
template = PLATFORM_AGENT_TEMPLATES.get(quota.template_name, {})
|
||||
# 使用 TEMPLATE_DISPLAY_INFO 获取显示名称
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(quota.template_name, {})
|
||||
data.append({
|
||||
"templateName": quota.template_name,
|
||||
"templateDisplayName": template.get("displayName", quota.template_name),
|
||||
"templateDisplayName": display_info.get("displayName", quota.template_name),
|
||||
"podQuota": quota.pod_quota,
|
||||
"podUsed": quota.pod_used,
|
||||
"podRemaining": quota.pod_quota - quota.pod_used,
|
||||
@@ -2056,7 +2207,6 @@ async def allocate_platform_agent_to_tenant(
|
||||
_verify_permission(principal, "manage:resources")
|
||||
role = _get_role(principal)
|
||||
user_channel_id = _get_channel_id(principal)
|
||||
user_id = principal.get("claims", {}).get("sub")
|
||||
|
||||
# 确定目标渠道ID
|
||||
if role == "super_admin":
|
||||
@@ -2097,8 +2247,9 @@ async def allocate_platform_agent_to_tenant(
|
||||
detail="租户不存在或不属于该渠道"
|
||||
)
|
||||
|
||||
# 验证模板是否存在
|
||||
if req.templateName not in PLATFORM_AGENT_TEMPLATES:
|
||||
# 从 Agent Manager 获取平台模板,验证模板是否存在
|
||||
platform_templates = await _get_platform_templates_from_agent_manager()
|
||||
if req.templateName not in platform_templates:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"平台 Agent 模板 '{req.templateName}' 不存在"
|
||||
@@ -2158,30 +2309,40 @@ async def allocate_platform_agent_to_tenant(
|
||||
)
|
||||
tenant_quota = tenant_quota_result.scalar_one_or_none()
|
||||
|
||||
# 计算配额变化量(用于更新渠道的 pod_used)
|
||||
old_tenant_quota = tenant_quota.pod_quota if tenant_quota else 0
|
||||
quota_delta = req.podQuota - old_tenant_quota
|
||||
|
||||
if tenant_quota:
|
||||
tenant_quota.pod_quota = req.podQuota
|
||||
else:
|
||||
# 注意:allocated_by 设置为 None,因为渠道管理员的 JWT sub 字段是渠道 ID 而非用户 ID
|
||||
# 如果需要记录分配人,应该在 JWT 中添加实际的用户 ID 字段
|
||||
tenant_quota = PlatformAgentQuota(
|
||||
target_id=tenant_id,
|
||||
target_type="tenant",
|
||||
template_name=req.templateName,
|
||||
pod_quota=req.podQuota,
|
||||
pod_used=0,
|
||||
allocated_by=user_id,
|
||||
allocated_by=None, # 渠道管理员的 sub 是渠道 ID,不是用户 ID
|
||||
allocated_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(tenant_quota)
|
||||
|
||||
# Bug 修复:更新渠道的 pod_used(分配给租户的配额视为渠道已使用的配额)
|
||||
channel_quota.pod_used = (channel_quota.pod_used or 0) + quota_delta
|
||||
|
||||
await db.commit()
|
||||
|
||||
template = PLATFORM_AGENT_TEMPLATES[req.templateName]
|
||||
# 使用已获取的模板信息
|
||||
template = platform_templates.get(req.templateName, {})
|
||||
|
||||
return SuccessResponse(
|
||||
data={
|
||||
"tenantId": tenant_id,
|
||||
"tenantName": tenant.name,
|
||||
"templateName": req.templateName,
|
||||
"templateDisplayName": template["displayName"],
|
||||
"templateDisplayName": template.get("displayName", req.templateName),
|
||||
"podQuota": req.podQuota,
|
||||
},
|
||||
message="平台 Agent 配额分配成功"
|
||||
@@ -2260,10 +2421,11 @@ async def get_tenant_platform_agent_usage(
|
||||
|
||||
data = []
|
||||
for quota in quotas:
|
||||
template = PLATFORM_AGENT_TEMPLATES.get(quota.template_name, {})
|
||||
# 使用 TEMPLATE_DISPLAY_INFO 获取显示名称
|
||||
display_info = TEMPLATE_DISPLAY_INFO.get(quota.template_name, {})
|
||||
data.append({
|
||||
"templateName": quota.template_name,
|
||||
"templateDisplayName": template.get("displayName", quota.template_name),
|
||||
"templateDisplayName": display_info.get("displayName", quota.template_name),
|
||||
"podQuota": quota.pod_quota,
|
||||
"podUsed": quota.pod_used,
|
||||
"podRemaining": quota.pod_quota - quota.pod_used,
|
||||
|
||||
@@ -869,7 +869,8 @@ async def admin_dashboard_stats(db: AsyncSession = Depends(get_db)) -> Dict[str,
|
||||
client = get_agent_manager_client()
|
||||
|
||||
# 获取所有运行中的 Agent
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
k8s_agents_count = len(k8s_agents)
|
||||
|
||||
# 获取每个 Agent 的资源配置
|
||||
@@ -1195,7 +1196,8 @@ async def admin_resources_agents(db: AsyncSession = Depends(get_db)) -> Dict[str
|
||||
from app.agent_manager_client import get_agent_manager_client
|
||||
client = get_agent_manager_client()
|
||||
print(f"Agent Manager URL: {client.base_url}")
|
||||
k8s_agents = await client.list_agents()
|
||||
k8s_agents_result = await client.list_agents()
|
||||
k8s_agents = k8s_agents_result.agents # 从 AgentListResult 中获取 agents 列表
|
||||
print(f"从 agent-manager 获取到 {len(k8s_agents)} 个 Agent: {k8s_agents}")
|
||||
logger.info(f"从 agent-manager 获取到 {len(k8s_agents)} 个 Agent")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -544,19 +544,19 @@ async def deploy_agent(
|
||||
|
||||
# 创建 Agent 配置
|
||||
agent_config = AgentConfig(
|
||||
replicas=req.instances,
|
||||
user_id=str(user_id),
|
||||
cpu_request=agent.cpu_request or "100m",
|
||||
cpu_limit=agent.cpu_limit or "500m",
|
||||
memory_request=agent.memory_request or "128Mi",
|
||||
memory_limit=agent.memory_limit or "512Mi",
|
||||
env=agent.env_config or {}
|
||||
)
|
||||
|
||||
# 调用 Agent Manager API 创建 Pod
|
||||
result = await client.create_agent(
|
||||
name=pod_name,
|
||||
template=agent.template,
|
||||
config=agent_config
|
||||
config=agent_config,
|
||||
env=agent.env_config or {}
|
||||
)
|
||||
|
||||
# 更新数据库记录
|
||||
@@ -905,7 +905,7 @@ async def use_platform_agent(
|
||||
|
||||
# 创建平台 Agent 实例
|
||||
agent_config = AgentConfig(
|
||||
replicas=1,
|
||||
user_id=str(user_id),
|
||||
cpu_request=quota.cpu_per_pod or "100m",
|
||||
cpu_limit=quota.cpu_per_pod or "500m",
|
||||
memory_request=quota.memory_per_pod or "128Mi",
|
||||
@@ -915,10 +915,8 @@ async def use_platform_agent(
|
||||
result = await client.create_platform_agent(
|
||||
name=instance_name,
|
||||
template=req.agentType,
|
||||
user_id=user_id,
|
||||
channel_id=channel_id or "",
|
||||
config=agent_config,
|
||||
query_params=req.queryParams
|
||||
user_id=str(user_id),
|
||||
config=agent_config
|
||||
)
|
||||
|
||||
# 更新配额使用量
|
||||
@@ -1063,11 +1061,11 @@ async def list_my_platform_agent_instances(
|
||||
for record in records:
|
||||
try:
|
||||
# 获取实例状态
|
||||
agent_info = await client.get_agent(record.agent_name)
|
||||
agent_status = await client.get_agent_status(record.agent_name)
|
||||
instances.append({
|
||||
"instanceName": record.agent_name,
|
||||
"agentType": record.agent_type,
|
||||
"status": agent_info.get("status", "unknown"),
|
||||
"status": agent_status.status,
|
||||
"startTime": record.start_time.isoformat() if record.start_time else None,
|
||||
"runningSeconds": int((datetime.utcnow() - record.start_time).total_seconds()) if record.start_time else 0,
|
||||
})
|
||||
@@ -1175,23 +1173,27 @@ async def create_custom_agent(
|
||||
|
||||
# 创建 Agent 配置
|
||||
agent_config = AgentConfig(
|
||||
replicas=1,
|
||||
user_id=str(user_id),
|
||||
cpu_request=req.cpuRequest,
|
||||
cpu_limit=req.cpuLimit or req.cpuRequest,
|
||||
memory_request=req.memoryRequest,
|
||||
memory_limit=req.memoryLimit or req.memoryRequest,
|
||||
)
|
||||
|
||||
# 构建环境变量
|
||||
env_vars = req.envConfig or {}
|
||||
if req.endpoint:
|
||||
env_vars["ENDPOINT"] = req.endpoint
|
||||
if req.apiKey:
|
||||
env_vars["API_KEY"] = req.apiKey
|
||||
|
||||
# 创建自定义 Agent
|
||||
result = await client.create_custom_agent(
|
||||
name=req.name,
|
||||
template=req.template,
|
||||
user_id=user_id,
|
||||
channel_id=channel_id or "",
|
||||
config=agent_config,
|
||||
endpoint=req.endpoint,
|
||||
api_key=req.apiKey,
|
||||
env_config=req.envConfig
|
||||
user_id=str(user_id),
|
||||
env_vars=env_vars,
|
||||
config=agent_config
|
||||
)
|
||||
|
||||
# 更新配额使用量
|
||||
@@ -1404,14 +1406,18 @@ async def scale_custom_agent_api(
|
||||
try:
|
||||
client = get_agent_manager_client()
|
||||
|
||||
# 调用扩缩容 API
|
||||
await client.scale_custom_agent(
|
||||
agent_name=name,
|
||||
cpu_request=req.cpuRequest,
|
||||
cpu_limit=req.cpuLimit,
|
||||
memory_request=req.memoryRequest,
|
||||
memory_limit=req.memoryLimit
|
||||
)
|
||||
# 调用扩缩容 API(注意:Agent Manager 尚未实现此接口)
|
||||
try:
|
||||
await client.scale_agent(
|
||||
agent_name=name,
|
||||
cpu_request=req.cpuRequest,
|
||||
cpu_limit=req.cpuLimit,
|
||||
memory_request=req.memoryRequest,
|
||||
memory_limit=req.memoryLimit
|
||||
)
|
||||
except NotImplementedError:
|
||||
# Agent Manager 尚未实现扩缩容接口,暂时只更新本地记录
|
||||
pass
|
||||
|
||||
# 更新配额使用量
|
||||
quota.cpu_used = float(quota.cpu_used or 0) + cpu_delta
|
||||
@@ -1478,11 +1484,11 @@ async def list_my_custom_agents(
|
||||
for record in records:
|
||||
try:
|
||||
# 获取 Agent 状态
|
||||
agent_info = await client.get_agent(record.agent_name)
|
||||
agent_status = await client.get_agent_status(record.agent_name)
|
||||
agents.append({
|
||||
"name": record.agent_name,
|
||||
"template": record.agent_type,
|
||||
"status": agent_info.get("status", "unknown"),
|
||||
"status": agent_status.status,
|
||||
"cpu": record.cpu_used,
|
||||
"memory": record.memory_used,
|
||||
"startTime": record.start_time.isoformat() if record.start_time else None,
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
-- 阶段二:配额管理和申请审批数据库迁移
|
||||
-- 版本: 008
|
||||
-- 日期: 2026-01-05
|
||||
-- 说明: 添加资源申请、平台Agent配额和Agent计费记录表
|
||||
|
||||
-- 1. 资源申请表(统一的申请审批)
|
||||
CREATE TABLE IF NOT EXISTS resource_applications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
channel_id UUID NOT NULL REFERENCES channels(id),
|
||||
|
||||
-- 申请类型
|
||||
resource_type VARCHAR(30) NOT NULL, -- platform_agent, provider, custom_agent_quota
|
||||
|
||||
-- 平台 Agent 申请字段
|
||||
template_name VARCHAR(100),
|
||||
requested_pod_quota INTEGER,
|
||||
|
||||
-- 模型供应商申请字段
|
||||
provider_id UUID REFERENCES model_providers(id),
|
||||
requested_rpm INTEGER,
|
||||
requested_tpm INTEGER,
|
||||
|
||||
-- 自定义 Agent 资源申请字段
|
||||
requested_cpu_quota NUMERIC(12, 2),
|
||||
requested_memory_quota NUMERIC(12, 2),
|
||||
|
||||
-- 申请信息
|
||||
reason TEXT NOT NULL,
|
||||
|
||||
-- 审批状态
|
||||
status VARCHAR(20) DEFAULT 'pending', -- pending, approved, rejected
|
||||
|
||||
-- 审批结果
|
||||
approved_pod_quota INTEGER,
|
||||
approved_rpm INTEGER,
|
||||
approved_tpm INTEGER,
|
||||
approved_cpu_quota NUMERIC(12, 2),
|
||||
approved_memory_quota NUMERIC(12, 2),
|
||||
|
||||
-- 审批信息
|
||||
reviewed_by UUID REFERENCES users(id),
|
||||
review_reason TEXT,
|
||||
reviewed_at TIMESTAMP,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_app_channel ON resource_applications(channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_app_type ON resource_applications(resource_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_app_status ON resource_applications(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_app_created ON resource_applications(created_at);
|
||||
|
||||
-- 2. 平台 Agent 配额表
|
||||
CREATE TABLE IF NOT EXISTS platform_agent_quotas (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
target_id UUID NOT NULL,
|
||||
target_type VARCHAR(20) NOT NULL, -- channel, tenant
|
||||
template_name VARCHAR(100) NOT NULL,
|
||||
pod_quota INTEGER NOT NULL DEFAULT 0,
|
||||
pod_used INTEGER DEFAULT 0,
|
||||
allocated_by UUID REFERENCES users(id),
|
||||
allocated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(target_id, target_type, template_name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_platform_agent_quota_target ON platform_agent_quotas(target_id, target_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_platform_agent_quota_template ON platform_agent_quotas(template_name);
|
||||
|
||||
-- 3. Agent 计费记录表
|
||||
CREATE TABLE IF NOT EXISTS agent_billing_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id),
|
||||
channel_id UUID REFERENCES channels(id),
|
||||
agent_name VARCHAR(100) NOT NULL,
|
||||
agent_type VARCHAR(20) NOT NULL, -- platform, custom
|
||||
template_name VARCHAR(100) NOT NULL,
|
||||
duration_seconds INTEGER NOT NULL,
|
||||
cpu_seconds FLOAT DEFAULT 0,
|
||||
memory_gb_seconds FLOAT DEFAULT 0,
|
||||
request_count INTEGER DEFAULT 0,
|
||||
cost NUMERIC(12, 4) NOT NULL,
|
||||
currency VARCHAR(10) DEFAULT 'EU',
|
||||
period_start TIMESTAMP NOT NULL,
|
||||
period_end TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_billing_user ON agent_billing_records(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_billing_channel ON agent_billing_records(channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_billing_period ON agent_billing_records(period_start, period_end);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_billing_type ON agent_billing_records(agent_type);
|
||||
|
||||
-- 4. 添加触发器更新 updated_at
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
DROP TRIGGER IF EXISTS update_resource_applications_updated_at ON resource_applications;
|
||||
CREATE TRIGGER update_resource_applications_updated_at
|
||||
BEFORE UPDATE ON resource_applications
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_platform_agent_quotas_updated_at ON platform_agent_quotas;
|
||||
CREATE TRIGGER update_platform_agent_quotas_updated_at
|
||||
BEFORE UPDATE ON platform_agent_quotas
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_agent_billing_records_updated_at ON agent_billing_records;
|
||||
CREATE TRIGGER update_agent_billing_records_updated_at
|
||||
BEFORE UPDATE ON agent_billing_records
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 5. 添加注释
|
||||
COMMENT ON TABLE resource_applications IS '资源申请表(统一的申请审批)';
|
||||
COMMENT ON TABLE platform_agent_quotas IS '平台 Agent 配额分配表';
|
||||
COMMENT ON TABLE agent_billing_records IS 'Agent 计费记录表';
|
||||
|
||||
COMMENT ON COLUMN resource_applications.resource_type IS '申请类型: platform_agent, provider, custom_agent_quota';
|
||||
COMMENT ON COLUMN resource_applications.status IS '审批状态: pending, approved, rejected';
|
||||
COMMENT ON COLUMN platform_agent_quotas.target_type IS '分配目标类型: channel, tenant';
|
||||
COMMENT ON COLUMN agent_billing_records.agent_type IS 'Agent 类型: platform, custom';
|
||||
@@ -0,0 +1,50 @@
|
||||
-- 迁移脚本:添加平台 Agent 模板配置表
|
||||
-- 版本:009
|
||||
-- 日期:2026-01-06
|
||||
-- 描述:
|
||||
-- 1. 创建 platform_agent_template_configs 表,存储管理员配置的模板资源限制
|
||||
-- 2. 用于 Bug 修复:available-platform-agents 接口返回管理员配置的值
|
||||
|
||||
-- 创建平台 Agent 模板配置表
|
||||
CREATE TABLE IF NOT EXISTS platform_agent_template_configs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- 模板名称(唯一)
|
||||
template_name VARCHAR(100) UNIQUE NOT NULL,
|
||||
|
||||
-- 资源配置
|
||||
cpu_request VARCHAR(20), -- CPU 请求量,如 "100m"
|
||||
cpu_limit VARCHAR(20), -- CPU 限制量,如 "500m"
|
||||
memory_request VARCHAR(20), -- 内存请求量,如 "128Mi"
|
||||
memory_limit VARCHAR(20), -- 内存限制量,如 "512Mi"
|
||||
max_pods INTEGER DEFAULT 0, -- 最大 Pod 数量(0 表示未配置)
|
||||
|
||||
-- 配置信息
|
||||
configured_by UUID REFERENCES users(id), -- 配置人
|
||||
configured_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 配置时间
|
||||
|
||||
-- 状态
|
||||
is_enabled BOOLEAN DEFAULT TRUE, -- 是否启用
|
||||
|
||||
-- 描述信息(可选,覆盖默认描述)
|
||||
display_name VARCHAR(200), -- 显示名称
|
||||
description TEXT, -- 描述
|
||||
|
||||
-- 时间戳
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_template_config_name ON platform_agent_template_configs(template_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_template_config_enabled ON platform_agent_template_configs(is_enabled);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE platform_agent_template_configs IS '平台 Agent 模板配置表,存储管理员配置的资源限制';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.template_name IS '模板名称,如 echo_agent, jina_search_agent';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.cpu_request IS 'K8s CPU 请求量,如 100m, 500m';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.cpu_limit IS 'K8s CPU 限制量,如 500m, 2000m';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.memory_request IS 'K8s 内存请求量,如 128Mi, 1Gi';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.memory_limit IS 'K8s 内存限制量,如 512Mi, 4Gi';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.max_pods IS '最大 Pod 数量,0 表示未配置';
|
||||
COMMENT ON COLUMN platform_agent_template_configs.is_enabled IS '是否启用该模板';
|
||||
@@ -1178,3 +1178,45 @@ class AgentBillingRecord(BaseModel, Base):
|
||||
Index("idx_agent_billing_period", period_start, period_end),
|
||||
Index("idx_agent_billing_type", agent_type),
|
||||
)
|
||||
|
||||
|
||||
class PlatformAgentTemplateConfig(BaseModel, Base):
|
||||
"""平台 Agent 模板配置(管理员配置)
|
||||
|
||||
存储管理员为每个平台 Agent 模板配置的资源限制和最大 Pod 数。
|
||||
这些配置将用于:
|
||||
1. 渠道查看可用平台 Agent 时显示配置信息
|
||||
2. 启动平台 Agent Pod 时使用配置的资源限制
|
||||
|
||||
如果模板未配置,则返回空值(前端显示为 0 或未配置)。
|
||||
"""
|
||||
__tablename__ = "platform_agent_template_configs"
|
||||
|
||||
# 模板名称(唯一)
|
||||
template_name = Column(String(100), unique=True, nullable=False)
|
||||
|
||||
# 资源配置
|
||||
cpu_request = Column(String(20)) # CPU 请求量,如 "100m"
|
||||
cpu_limit = Column(String(20)) # CPU 限制量,如 "500m"
|
||||
memory_request = Column(String(20)) # 内存请求量,如 "128Mi"
|
||||
memory_limit = Column(String(20)) # 内存限制量,如 "512Mi"
|
||||
max_pods = Column(Integer, default=0) # 最大 Pod 数量(0 表示未配置)
|
||||
|
||||
# 配置信息
|
||||
configured_by = Column(GUID(), ForeignKey("users.id")) # 配置人
|
||||
configured_at = Column(DateTime, default=datetime.utcnow) # 配置时间
|
||||
|
||||
# 状态
|
||||
is_enabled = Column(Boolean, default=True) # 是否启用
|
||||
|
||||
# 描述信息(可选,覆盖默认描述)
|
||||
display_name = Column(String(200)) # 显示名称
|
||||
description = Column(Text) # 描述
|
||||
|
||||
# 关联关系
|
||||
configurator = relationship("User", foreign_keys=[configured_by])
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_template_config_name", template_name),
|
||||
Index("idx_template_config_enabled", is_enabled),
|
||||
)
|
||||
|
||||
@@ -357,6 +357,7 @@ class TemplateListResponse(BaseModel):
|
||||
"""模板列表响应"""
|
||||
templates: List[TemplateInfo]
|
||||
count: int
|
||||
type: Optional[str] = None # platform, custom, 或 None(表示所有类型)
|
||||
|
||||
|
||||
class AgentExecution(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user