更新超级管理员端

This commit is contained in:
zhanggangyong
2026-01-06 06:09:12 +00:00
parent 3a8e12657b
commit 991a5dd01e
12 changed files with 2598 additions and 597 deletions
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -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)
+238 -161
View File
@@ -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}")
+74 -10
View File
@@ -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:
+217 -55
View File
@@ -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
+33 -27
View File
@@ -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 '是否启用该模板';
+42
View File
@@ -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),
)
+1
View File
@@ -357,6 +357,7 @@ class TemplateListResponse(BaseModel):
"""模板列表响应"""
templates: List[TemplateInfo]
count: int
type: Optional[str] = None # platform, custom, 或 None(表示所有类型)
class AgentExecution(BaseModel):