Files
agent_management/app_new.py
T
2026-01-05 12:44:28 +00:00

1034 lines
36 KiB
Python

"""
FastAPI Web服务 - AI Agent管理服务
支持平台Agent和自定义Agent两种类型,集成数据库和配额管理
"""
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime
import logging
from k8s_manager import K8sManager
from database import (
get_db, Template, Agent, Quota, AgentMetric,
AgentType, AgentStatus, parse_resource_string
)
import os
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 创建FastAPI应用
app = FastAPI(
title="AI Agent Manager",
description="Kubernetes AI Agent管理服务 - 支持平台Agent和自定义Agent",
version="2.0.0"
)
# 初始化K8s管理器
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
KUBECONFIG_PATH = os.getenv("KUBECONFIG_PATH", None)
k8s_manager = K8sManager(namespace=NAMESPACE, kubeconfig_path=KUBECONFIG_PATH)
# ==================== 请求/响应模型 ====================
# Template Management Models
class CreateTemplateRequest(BaseModel):
"""创建模板请求"""
name: str = Field(..., min_length=1, max_length=100)
display_name: str
description: Optional[str] = None
agent_type: str = Field(..., description="platform or custom")
image: str
port: Optional[int] = None
env_requirements: Optional[Dict] = Field(default_factory=dict)
cpu_request: Optional[str] = None
cpu_limit: Optional[str] = None
memory_request: Optional[str] = None
memory_limit: Optional[str] = None
min_replicas: int = 1
max_replicas: int = 3
target_cpu_utilization: int = 80
class UpdateTemplateRequest(BaseModel):
"""更新模板请求"""
display_name: Optional[str] = None
description: Optional[str] = None
image: Optional[str] = None
port: Optional[int] = None
env_requirements: Optional[Dict] = None
cpu_request: Optional[str] = None
cpu_limit: Optional[str] = None
memory_request: Optional[str] = None
memory_limit: Optional[str] = None
min_replicas: Optional[int] = None
max_replicas: Optional[int] = None
target_cpu_utilization: Optional[int] = None
is_active: Optional[bool] = None
class TemplateResponse(BaseModel):
"""模板响应"""
id: int
name: str
display_name: str
description: Optional[str]
agent_type: str
image: str
port: Optional[int]
env_requirements: Dict
cpu_request: Optional[str]
cpu_limit: Optional[str]
memory_request: Optional[str]
memory_limit: Optional[str]
min_replicas: int
max_replicas: int
target_cpu_utilization: int
is_active: bool
created_at: datetime
class Config:
from_attributes = True
# Platform Agent Models
class CreatePlatformAgentRequest(BaseModel):
"""创建平台Agent请求"""
name: str = Field(..., min_length=1, max_length=63)
template_name: str
owner_id: str
channel_id: Optional[str] = None
tenant_id: Optional[str] = None
# Custom Agent Models
class ScalingConfig(BaseModel):
"""弹性伸缩配置"""
min_replicas: int = Field(1, ge=0)
max_replicas: int = Field(3, ge=1)
target_cpu_utilization: int = Field(80, ge=1, le=100)
class CreateCustomAgentRequest(BaseModel):
"""创建自定义Agent请求"""
name: str = Field(..., min_length=1, max_length=63)
template_name: str
owner_id: str
channel_id: Optional[str] = None
tenant_id: Optional[str] = None
environment_vars: Dict[str, str]
cpu_request: Optional[str] = None
cpu_limit: Optional[str] = None
memory_request: Optional[str] = None
memory_limit: Optional[str] = None
scaling_config: Optional[ScalingConfig] = None
class UpdateAgentEnvRequest(BaseModel):
"""更新Agent环境变量请求"""
environment_vars: Dict[str, str]
class UpdateScalingRequest(BaseModel):
"""更新伸缩配置请求"""
min_replicas: Optional[int] = None
max_replicas: Optional[int] = None
target_cpu_utilization: Optional[int] = None
# Unified Agent Response
class AgentResponseNew(BaseModel):
"""Agent响应"""
id: int
name: str
display_name: Optional[str]
template_name: str
agent_type: str
status: str
owner_id: str
channel_id: Optional[str]
tenant_id: Optional[str]
service_url: Optional[str]
current_replicas: int
min_replicas: int
max_replicas: int
created_at: datetime
last_accessed_at: Optional[datetime]
class Config:
from_attributes = True
class MessageResponse(BaseModel):
"""通用消息响应"""
status: str
message: str
# Quota Models
class QuotaResponse(BaseModel):
"""配额响应"""
owner_id: str
owner_type: str
platform_pod_quota: int
platform_pod_used: int
custom_cpu_quota: float
custom_cpu_used: float
custom_memory_quota: float
custom_memory_used: float
class Config:
from_attributes = True
# Statistics Models
class StatsOverviewResponse(BaseModel):
"""统计概览响应"""
total_agents: int
platform_agents: int
custom_agents: int
running_agents: int
total_cpu_used: float
total_memory_used: float
class TemplateStatsResponse(BaseModel):
"""模板统计响应"""
template_name: str
agent_count: int
total_replicas: int
class OwnerStatsResponse(BaseModel):
"""所有者统计响应"""
owner_id: str
agent_count: int
platform_agents: int
custom_agents: int
total_cpu_used: float
total_memory_used: float
# ==================== 辅助函数 ====================
def check_quota(db: Session, owner_id: str, agent_type: AgentType,
cpu_request: str = None, memory_request: str = None) -> bool:
"""检查配额是否足够"""
quota = db.query(Quota).filter(Quota.owner_id == owner_id).first()
if not quota:
# 如果没有配额记录,返回True(暂时允许,生产环境应该拒绝)
logger.warning(f"Owner {owner_id} 没有配额记录")
return True
if agent_type == AgentType.PLATFORM:
# 平台Agent检查Pod数量配额
return quota.platform_pod_used < quota.platform_pod_quota
else:
# 自定义Agent检查CPU和内存配额
cpu_needed = parse_resource_string(cpu_request) if cpu_request else 0
memory_needed = parse_resource_string(memory_request) if memory_request else 0
cpu_available = quota.custom_cpu_quota - quota.custom_cpu_used
memory_available = quota.custom_memory_quota - quota.custom_memory_used
return cpu_needed <= cpu_available and memory_needed <= memory_available
def update_quota_usage(db: Session, owner_id: str, agent_type: AgentType,
delta_pods: int = 0, delta_cpu: float = 0, delta_memory: float = 0):
"""更新配额使用量"""
quota = db.query(Quota).filter(Quota.owner_id == owner_id).first()
if not quota:
# 创建新的配额记录(使用默认值)
quota = Quota(
owner_id=owner_id,
owner_type="tenant",
platform_pod_quota=10, # 默认值
custom_cpu_quota=10.0,
custom_memory_quota=20480.0 # 20GB
)
db.add(quota)
if agent_type == AgentType.PLATFORM:
quota.platform_pod_used += delta_pods
else:
quota.custom_cpu_used += delta_cpu
quota.custom_memory_used += delta_memory
db.commit()
# ==================== API端点 ====================
@app.get("/")
async def root():
"""健康检查"""
return {
"service": "AI Agent Manager",
"version": "2.0.0",
"status": "running",
"namespace": NAMESPACE
}
@app.get("/health")
async def health_check():
"""健康检查端点"""
return {"status": "healthy"}
@app.get("/ready")
async def readiness_check(db: Session = Depends(get_db)):
"""就绪检查端点"""
try:
# 检查数据库连接
db.execute("SELECT 1")
return {"status": "ready"}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Service not ready: {str(e)}")
# ==================== 模板管理 API ====================
@app.post("/templates", response_model=TemplateResponse, status_code=201)
async def create_template(request: CreateTemplateRequest, db: Session = Depends(get_db)):
"""创建Agent模板"""
try:
# 检查模板是否已存在
existing = db.query(Template).filter(Template.name == request.name).first()
if existing:
raise HTTPException(status_code=409, detail=f"Template {request.name} already exists")
# 验证agent_type
if request.agent_type not in ["platform", "custom"]:
raise HTTPException(status_code=400, detail="agent_type must be 'platform' or 'custom'")
# 创建模板
template = Template(
name=request.name,
display_name=request.display_name,
description=request.description,
agent_type=AgentType(request.agent_type),
image=request.image,
port=request.port,
env_requirements=request.env_requirements,
cpu_request=request.cpu_request,
cpu_limit=request.cpu_limit,
memory_request=request.memory_request,
memory_limit=request.memory_limit,
min_replicas=request.min_replicas,
max_replicas=request.max_replicas,
target_cpu_utilization=request.target_cpu_utilization
)
db.add(template)
db.commit()
db.refresh(template)
logger.info(f"Created template: {template.name}")
return template
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to create template: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/templates", response_model=List[TemplateResponse])
async def list_templates(agent_type: Optional[str] = None, db: Session = Depends(get_db)):
"""列出所有模板"""
try:
query = db.query(Template).filter(Template.is_active == True)
if agent_type:
if agent_type not in ["platform", "custom"]:
raise HTTPException(status_code=400, detail="agent_type must be 'platform' or 'custom'")
query = query.filter(Template.agent_type == AgentType(agent_type))
templates = query.all()
return templates
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to list templates: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/templates/{template_name}", response_model=TemplateResponse)
async def get_template(template_name: str, db: Session = Depends(get_db)):
"""获取模板详情"""
template = db.query(Template).filter(Template.name == template_name).first()
if not template:
raise HTTPException(status_code=404, detail=f"Template {template_name} not found")
return template
@app.put("/templates/{template_name}", response_model=TemplateResponse)
async def update_template(template_name: str, request: UpdateTemplateRequest, db: Session = Depends(get_db)):
"""更新模板"""
try:
template = db.query(Template).filter(Template.name == template_name).first()
if not template:
raise HTTPException(status_code=404, detail=f"Template {template_name} not found")
# 更新字段
update_data = request.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(template, field, value)
template.updated_at = datetime.utcnow()
db.commit()
db.refresh(template)
logger.info(f"Updated template: {template_name}")
return template
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to update template: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/templates/{template_name}", response_model=MessageResponse)
async def delete_template(template_name: str, db: Session = Depends(get_db)):
"""删除模板(软删除)"""
try:
template = db.query(Template).filter(Template.name == template_name).first()
if not template:
raise HTTPException(status_code=404, detail=f"Template {template_name} not found")
# 检查是否有Agent使用此模板
agent_count = db.query(Agent).filter(Agent.template_id == template.id).count()
if agent_count > 0:
raise HTTPException(
status_code=409,
detail=f"Cannot delete template: {agent_count} agents are using it"
)
# 软删除
template.is_active = False
db.commit()
logger.info(f"Deleted template: {template_name}")
return MessageResponse(status="success", message=f"Template {template_name} deleted")
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to delete template: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# ==================== 平台Agent API ====================
@app.post("/platform-agents", response_model=AgentResponseNew, status_code=201)
async def create_platform_agent(request: CreatePlatformAgentRequest, db: Session = Depends(get_db)):
"""创建平台Agent"""
try:
# 查找模板
template = db.query(Template).filter(
Template.name == request.template_name,
Template.agent_type == AgentType.PLATFORM,
Template.is_active == True
).first()
if not template:
raise HTTPException(status_code=404, detail=f"Platform template {request.template_name} not found")
# 检查Agent名称是否已存在
existing = db.query(Agent).filter(Agent.name == request.name).first()
if existing:
raise HTTPException(status_code=409, detail=f"Agent {request.name} already exists")
# 检查配额
if not check_quota(db, request.owner_id, AgentType.PLATFORM):
raise HTTPException(status_code=429, detail="Platform agent quota exceeded")
# 创建Agent记录
agent = Agent(
name=request.name,
template_id=template.id,
owner_id=request.owner_id,
channel_id=request.channel_id,
tenant_id=request.tenant_id,
agent_type=AgentType.PLATFORM,
status=AgentStatus.PENDING,
deployment_name=f"{request.name}-deployment",
service_name=f"{request.name}-service",
namespace=NAMESPACE,
min_replicas=template.min_replicas,
max_replicas=template.max_replicas,
target_cpu_utilization=template.target_cpu_utilization
)
db.add(agent)
db.commit()
db.refresh(agent)
# 创建Kubernetes资源
try:
k8s_result = k8s_manager.create_deployment_and_service(
name=request.name,
template=template,
agent=agent,
env_vars={}
)
# 更新Agent状态和服务URL
agent.service_url = k8s_result.get("service_url")
agent.status = AgentStatus.RUNNING
db.commit()
except Exception as k8s_error:
logger.error(f"K8s deployment failed: {str(k8s_error)}")
agent.status = AgentStatus.FAILED
db.commit()
raise HTTPException(status_code=500, detail=f"Kubernetes deployment failed: {str(k8s_error)}")
# 更新配额使用
update_quota_usage(db, request.owner_id, AgentType.PLATFORM, delta_pods=1)
logger.info(f"Created platform agent: {agent.name}")
return AgentResponseNew(
id=agent.id,
name=agent.name,
display_name=agent.display_name,
template_name=template.name,
agent_type=agent.agent_type.value,
status=agent.status.value,
owner_id=agent.owner_id,
channel_id=agent.channel_id,
tenant_id=agent.tenant_id,
service_url=agent.service_url,
current_replicas=agent.current_replicas,
min_replicas=agent.min_replicas,
max_replicas=agent.max_replicas,
created_at=agent.created_at,
last_accessed_at=agent.last_accessed_at
)
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to create platform agent: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/platform-agents", response_model=List[AgentResponseNew])
async def list_platform_agents(owner_id: Optional[str] = None, db: Session = Depends(get_db)):
"""列出平台Agent"""
try:
query = db.query(Agent).filter(Agent.agent_type == AgentType.PLATFORM)
if owner_id:
query = query.filter(Agent.owner_id == owner_id)
agents = query.all()
result = []
for agent in agents:
result.append(AgentResponseNew(
id=agent.id,
name=agent.name,
display_name=agent.display_name,
template_name=agent.template.name,
agent_type=agent.agent_type.value,
status=agent.status.value,
owner_id=agent.owner_id,
channel_id=agent.channel_id,
tenant_id=agent.tenant_id,
service_url=agent.service_url,
current_replicas=agent.current_replicas,
min_replicas=agent.min_replicas,
max_replicas=agent.max_replicas,
created_at=agent.created_at,
last_accessed_at=agent.last_accessed_at
))
return result
except Exception as e:
logger.error(f"Failed to list platform agents: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/platform-agents/{agent_name}", response_model=MessageResponse)
async def delete_platform_agent(agent_name: str, db: Session = Depends(get_db)):
"""删除平台Agent"""
try:
agent = db.query(Agent).filter(
Agent.name == agent_name,
Agent.agent_type == AgentType.PLATFORM
).first()
if not agent:
raise HTTPException(status_code=404, detail=f"Platform agent {agent_name} not found")
# 删除Kubernetes资源
try:
k8s_manager.delete_deployment_and_service(agent.deployment_name, agent.service_name)
except Exception as k8s_error:
logger.error(f"K8s deletion failed: {str(k8s_error)}")
# 更新配额
update_quota_usage(db, agent.owner_id, AgentType.PLATFORM, delta_pods=-1)
# 删除数据库记录
db.delete(agent)
db.commit()
logger.info(f"Deleted platform agent: {agent_name}")
return MessageResponse(status="success", message=f"Platform agent {agent_name} deleted")
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to delete platform agent: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# ==================== 自定义Agent API ====================
@app.post("/custom-agents", response_model=AgentResponseNew, status_code=201)
async def create_custom_agent(request: CreateCustomAgentRequest, db: Session = Depends(get_db)):
"""创建自定义Agent"""
try:
# 查找模板
template = db.query(Template).filter(
Template.name == request.template_name,
Template.agent_type == AgentType.CUSTOM,
Template.is_active == True
).first()
if not template:
raise HTTPException(status_code=404, detail=f"Custom template {request.template_name} not found")
# 检查Agent名称是否已存在
existing = db.query(Agent).filter(Agent.name == request.name).first()
if existing:
raise HTTPException(status_code=409, detail=f"Agent {request.name} already exists")
# 验证必需的环境变量
required_env = template.env_requirements.get("required", {})
for key in required_env.keys():
if key not in request.environment_vars:
raise HTTPException(
status_code=400,
detail=f"Missing required environment variable: {key}"
)
# 确定资源配置(使用请求值或模板默认值)
cpu_request = request.cpu_request or template.cpu_request
memory_request = request.memory_request or template.memory_request
# 检查配额
if not check_quota(db, request.owner_id, AgentType.CUSTOM, cpu_request, memory_request):
raise HTTPException(status_code=429, detail="Custom agent resource quota exceeded")
# 创建Agent记录
scaling = request.scaling_config or ScalingConfig()
agent = Agent(
name=request.name,
template_id=template.id,
owner_id=request.owner_id,
channel_id=request.channel_id,
tenant_id=request.tenant_id,
agent_type=AgentType.CUSTOM,
status=AgentStatus.PENDING,
environment_vars=request.environment_vars,
cpu_request=cpu_request,
cpu_limit=request.cpu_limit or template.cpu_limit,
memory_request=memory_request,
memory_limit=request.memory_limit or template.memory_limit,
deployment_name=f"{request.name}-deployment",
service_name=f"{request.name}-service",
namespace=NAMESPACE,
min_replicas=scaling.min_replicas,
max_replicas=scaling.max_replicas,
target_cpu_utilization=scaling.target_cpu_utilization
)
db.add(agent)
db.commit()
db.refresh(agent)
# 创建Kubernetes资源
try:
k8s_result = k8s_manager.create_deployment_and_service(
name=request.name,
template=template,
agent=agent,
env_vars=request.environment_vars
)
agent.service_url = k8s_result.get("service_url")
agent.status = AgentStatus.RUNNING
db.commit()
except Exception as k8s_error:
logger.error(f"K8s deployment failed: {str(k8s_error)}")
agent.status = AgentStatus.FAILED
db.commit()
raise HTTPException(status_code=500, detail=f"Kubernetes deployment failed: {str(k8s_error)}")
# 更新配额使用
cpu_used = parse_resource_string(cpu_request)
memory_used = parse_resource_string(memory_request)
update_quota_usage(db, request.owner_id, AgentType.CUSTOM,
delta_cpu=cpu_used, delta_memory=memory_used)
logger.info(f"Created custom agent: {agent.name}")
return AgentResponseNew(
id=agent.id,
name=agent.name,
display_name=agent.display_name,
template_name=template.name,
agent_type=agent.agent_type.value,
status=agent.status.value,
owner_id=agent.owner_id,
channel_id=agent.channel_id,
tenant_id=agent.tenant_id,
service_url=agent.service_url,
current_replicas=agent.current_replicas,
min_replicas=agent.min_replicas,
max_replicas=agent.max_replicas,
created_at=agent.created_at,
last_accessed_at=agent.last_accessed_at
)
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to create custom agent: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/custom-agents", response_model=List[AgentResponseNew])
async def list_custom_agents(owner_id: Optional[str] = None, db: Session = Depends(get_db)):
"""列出自定义Agent"""
try:
query = db.query(Agent).filter(Agent.agent_type == AgentType.CUSTOM)
if owner_id:
query = query.filter(Agent.owner_id == owner_id)
agents = query.all()
result = []
for agent in agents:
result.append(AgentResponseNew(
id=agent.id,
name=agent.name,
display_name=agent.display_name,
template_name=agent.template.name,
agent_type=agent.agent_type.value,
status=agent.status.value,
owner_id=agent.owner_id,
channel_id=agent.channel_id,
tenant_id=agent.tenant_id,
service_url=agent.service_url,
current_replicas=agent.current_replicas,
min_replicas=agent.min_replicas,
max_replicas=agent.max_replicas,
created_at=agent.created_at,
last_accessed_at=agent.last_accessed_at
))
return result
except Exception as e:
logger.error(f"Failed to list custom agents: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.put("/custom-agents/{agent_name}/env", response_model=AgentResponseNew)
async def update_custom_agent_env(agent_name: str, request: UpdateAgentEnvRequest, db: Session = Depends(get_db)):
"""更新自定义Agent环境变量"""
try:
agent = db.query(Agent).filter(
Agent.name == agent_name,
Agent.agent_type == AgentType.CUSTOM
).first()
if not agent:
raise HTTPException(status_code=404, detail=f"Custom agent {agent_name} not found")
# 验证必需的环境变量
required_env = agent.template.env_requirements.get("required", {})
for key in required_env.keys():
if key not in request.environment_vars:
raise HTTPException(
status_code=400,
detail=f"Missing required environment variable: {key}"
)
# 更新环境变量
agent.environment_vars = request.environment_vars
agent.updated_at = datetime.utcnow()
# 更新Kubernetes Deployment
try:
k8s_manager.update_deployment_env(agent.deployment_name, request.environment_vars)
except Exception as k8s_error:
logger.error(f"K8s update failed: {str(k8s_error)}")
raise HTTPException(status_code=500, detail=f"Kubernetes update failed: {str(k8s_error)}")
db.commit()
db.refresh(agent)
logger.info(f"Updated custom agent env: {agent_name}")
return AgentResponseNew(
id=agent.id,
name=agent.name,
display_name=agent.display_name,
template_name=agent.template.name,
agent_type=agent.agent_type.value,
status=agent.status.value,
owner_id=agent.owner_id,
channel_id=agent.channel_id,
tenant_id=agent.tenant_id,
service_url=agent.service_url,
current_replicas=agent.current_replicas,
min_replicas=agent.min_replicas,
max_replicas=agent.max_replicas,
created_at=agent.created_at,
last_accessed_at=agent.last_accessed_at
)
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to update custom agent env: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/custom-agents/{agent_name}", response_model=MessageResponse)
async def delete_custom_agent(agent_name: str, db: Session = Depends(get_db)):
"""删除自定义Agent"""
try:
agent = db.query(Agent).filter(
Agent.name == agent_name,
Agent.agent_type == AgentType.CUSTOM
).first()
if not agent:
raise HTTPException(status_code=404, detail=f"Custom agent {agent_name} not found")
# 删除Kubernetes资源
try:
k8s_manager.delete_deployment_and_service(agent.deployment_name, agent.service_name)
except Exception as k8s_error:
logger.error(f"K8s deletion failed: {str(k8s_error)}")
# 更新配额
cpu_used = parse_resource_string(agent.cpu_request)
memory_used = parse_resource_string(agent.memory_request)
update_quota_usage(db, agent.owner_id, AgentType.CUSTOM,
delta_cpu=-cpu_used, delta_memory=-memory_used)
# 删除数据库记录
db.delete(agent)
db.commit()
logger.info(f"Deleted custom agent: {agent_name}")
return MessageResponse(status="success", message=f"Custom agent {agent_name} deleted")
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"Failed to delete custom agent: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# ==================== 统计API ====================
@app.get("/stats/overview", response_model=StatsOverviewResponse)
async def get_stats_overview(db: Session = Depends(get_db)):
"""获取统计概览"""
try:
total_agents = db.query(Agent).count()
platform_agents = db.query(Agent).filter(Agent.agent_type == AgentType.PLATFORM).count()
custom_agents = db.query(Agent).filter(Agent.agent_type == AgentType.CUSTOM).count()
running_agents = db.query(Agent).filter(Agent.status == AgentStatus.RUNNING).count()
# 计算总CPU和内存使用(仅自定义Agent)
custom_agent_list = db.query(Agent).filter(Agent.agent_type == AgentType.CUSTOM).all()
total_cpu = sum(parse_resource_string(a.cpu_request or "0") for a in custom_agent_list)
total_memory = sum(parse_resource_string(a.memory_request or "0") for a in custom_agent_list)
return StatsOverviewResponse(
total_agents=total_agents,
platform_agents=platform_agents,
custom_agents=custom_agents,
running_agents=running_agents,
total_cpu_used=total_cpu,
total_memory_used=total_memory
)
except Exception as e:
logger.error(f"Failed to get stats overview: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/stats/by-template", response_model=List[TemplateStatsResponse])
async def get_stats_by_template(db: Session = Depends(get_db)):
"""按模板统计"""
try:
results = db.query(
Template.name,
func.count(Agent.id).label("agent_count"),
func.sum(Agent.current_replicas).label("total_replicas")
).join(Agent, Template.id == Agent.template_id, isouter=True)\
.group_by(Template.name).all()
stats = []
for name, count, replicas in results:
stats.append(TemplateStatsResponse(
template_name=name,
agent_count=count or 0,
total_replicas=replicas or 0
))
return stats
except Exception as e:
logger.error(f"Failed to get stats by template: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/stats/by-owner", response_model=List[OwnerStatsResponse])
async def get_stats_by_owner(db: Session = Depends(get_db)):
"""按所有者统计"""
try:
# 按owner_id分组统计
owners = db.query(Agent.owner_id).distinct().all()
stats = []
for (owner_id,) in owners:
agents = db.query(Agent).filter(Agent.owner_id == owner_id).all()
agent_count = len(agents)
platform_count = sum(1 for a in agents if a.agent_type == AgentType.PLATFORM)
custom_count = sum(1 for a in agents if a.agent_type == AgentType.CUSTOM)
total_cpu = sum(parse_resource_string(a.cpu_request or "0") for a in agents if a.agent_type == AgentType.CUSTOM)
total_memory = sum(parse_resource_string(a.memory_request or "0") for a in agents if a.agent_type == AgentType.CUSTOM)
stats.append(OwnerStatsResponse(
owner_id=owner_id,
agent_count=agent_count,
platform_agents=platform_count,
custom_agents=custom_count,
total_cpu_used=total_cpu,
total_memory_used=total_memory
))
return stats
except Exception as e:
logger.error(f"Failed to get stats by owner: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# ==================== 配额管理 API ====================
@app.get("/quotas/{owner_id}", response_model=QuotaResponse)
async def get_quota(owner_id: str, db: Session = Depends(get_db)):
"""获取配额信息"""
quota = db.query(Quota).filter(Quota.owner_id == owner_id).first()
if not quota:
raise HTTPException(status_code=404, detail=f"Quota for owner {owner_id} not found")
return quota
# ==================== 日志管理 API ====================
@app.get("/platform-agents/{agent_name}/logs")
async def get_platform_agent_logs(agent_name: str, lines: int = 100, db: Session = Depends(get_db)):
"""获取平台Agent日志"""
try:
agent = db.query(Agent).filter(
Agent.name == agent_name,
Agent.agent_type == AgentType.PLATFORM
).first()
if not agent:
raise HTTPException(status_code=404, detail=f"Platform agent {agent_name} not found")
logs = k8s_manager.get_pod_logs(agent.deployment_name, lines)
return {
"agent_name": agent_name,
"deployment_name": agent.deployment_name,
"logs": logs,
"lines": lines
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get platform agent logs: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/custom-agents/{agent_name}/logs")
async def get_custom_agent_logs(agent_name: str, lines: int = 100, db: Session = Depends(get_db)):
"""获取自定义Agent日志"""
try:
agent = db.query(Agent).filter(
Agent.name == agent_name,
Agent.agent_type == AgentType.CUSTOM
).first()
if not agent:
raise HTTPException(status_code=404, detail=f"Custom agent {agent_name} not found")
logs = k8s_manager.get_pod_logs(agent.deployment_name, lines)
return {
"agent_name": agent_name,
"deployment_name": agent.deployment_name,
"logs": logs,
"lines": lines
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get custom agent logs: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
host = os.getenv("SERVICE_HOST", "0.0.0.0")
port = int(os.getenv("SERVICE_PORT", "8000"))
logger.info(f"启动AI Agent Manager服务 v2.0: {host}:{port}")
uvicorn.run(app, host=host, port=port)