init
This commit is contained in:
@@ -0,0 +1,516 @@
|
||||
"""
|
||||
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 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管理服务",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# 初始化K8s管理器
|
||||
NAMESPACE = os.getenv("NAMESPACE", "ai-agents")
|
||||
KUBECONFIG_PATH = os.getenv("KUBECONFIG_PATH", None) # 可选:指定kubeconfig路径
|
||||
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
|
||||
query_params: Optional[Dict] = Field(default_factory=dict)
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# Legacy Models (for backward compatibility)
|
||||
class CreateAgentRequest(BaseModel):
|
||||
"""创建Agent请求(旧版)"""
|
||||
name: str = Field(..., description="Agent名称", min_length=1, max_length=63)
|
||||
template: str = Field(..., description="模板类型")
|
||||
config: Dict = Field(default_factory=dict, description="配置信息")
|
||||
env: Optional[Dict[str, str]] = Field(default_factory=dict, description="环境变量")
|
||||
|
||||
|
||||
class AgentResponse(BaseModel):
|
||||
"""Agent响应"""
|
||||
name: str
|
||||
namespace: str
|
||||
status: str
|
||||
created_at: Optional[str] = None
|
||||
template: Optional[str] = None
|
||||
service_port: Optional[int] = None
|
||||
access_info: Optional[Dict] = None
|
||||
pod_id: Optional[str] = None
|
||||
pod_ip: Optional[str] = None
|
||||
host_ip: Optional[str] = None
|
||||
node_name: Optional[str] = None
|
||||
owner_info: Optional[Dict] = None
|
||||
|
||||
|
||||
class ResourceUsage(BaseModel):
|
||||
"""资源使用情况"""
|
||||
cpu: Optional[str] = None
|
||||
memory: Optional[str] = None
|
||||
available: Optional[bool] = None
|
||||
reason: Optional[str] = None
|
||||
|
||||
|
||||
class ResourceInfo(BaseModel):
|
||||
"""资源信息(配额和使用情况)"""
|
||||
requests: Optional[Dict] = None
|
||||
limits: Optional[Dict] = None
|
||||
usage: Optional[ResourceUsage] = None
|
||||
|
||||
|
||||
class PodStatusResponse(BaseModel):
|
||||
"""Pod状态响应"""
|
||||
name: str
|
||||
namespace: str
|
||||
status: str
|
||||
template: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
node: Optional[str] = None
|
||||
pod_ip: Optional[str] = None
|
||||
resources: Optional[ResourceInfo] = None
|
||||
service_port: Optional[int] = None
|
||||
access_url: Optional[str] = None
|
||||
endpoints: Optional[Dict] = None
|
||||
conditions: Optional[List[Dict]] = None
|
||||
|
||||
|
||||
class PodMetricsResponse(BaseModel):
|
||||
"""Pod资源使用响应"""
|
||||
name: str
|
||||
requests: Dict
|
||||
limits: Dict
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""通用消息响应"""
|
||||
status: str
|
||||
message: str
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""健康检查"""
|
||||
return {
|
||||
"service": "AI Agent Manager",
|
||||
"status": "running",
|
||||
"namespace": NAMESPACE
|
||||
}
|
||||
|
||||
|
||||
@app.post("/agents", response_model=AgentResponse)
|
||||
async def create_agent(request: CreateAgentRequest):
|
||||
"""
|
||||
创建AI Agent Pod
|
||||
|
||||
Args:
|
||||
request: 创建请求(name, template, config, user_id可选)
|
||||
|
||||
Returns:
|
||||
创建的Agent信息包括pod_id
|
||||
"""
|
||||
try:
|
||||
logger.info(f"收到创建Agent请求: {request.name}, 模板: {request.template}")
|
||||
|
||||
# 验证模板类型
|
||||
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent"]
|
||||
if request.template not in valid_templates:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"无效的模板类型。支持的模板: {', '.join(valid_templates)}"
|
||||
)
|
||||
|
||||
# 合并环境变量到config
|
||||
config_data = request.config.copy()
|
||||
if request.env:
|
||||
config_data["env"] = request.env
|
||||
logger.info(f"环境变量: {list(request.env.keys())}")
|
||||
|
||||
# 添加 user_id 标签
|
||||
user_id = config_data.get("user_id", "default")
|
||||
if "labels" not in config_data:
|
||||
config_data["labels"] = {}
|
||||
config_data["labels"]["user-id"] = user_id
|
||||
config_data["labels"]["managed-by"] = "agent-manager"
|
||||
|
||||
# 创建Pod
|
||||
result = k8s_manager.create_pod(
|
||||
pod_name=request.name,
|
||||
template=request.template,
|
||||
config_data=config_data
|
||||
)
|
||||
|
||||
# 获取 Pod 详细信息(包括 pod_id)
|
||||
try:
|
||||
import time
|
||||
time.sleep(1) # 等待 Pod 创建完成
|
||||
pod = k8s_manager.v1.read_namespaced_pod(
|
||||
name=request.name,
|
||||
namespace=NAMESPACE
|
||||
)
|
||||
result["pod_id"] = pod.metadata.uid
|
||||
result["pod_ip"] = pod.status.pod_ip
|
||||
result["host_ip"] = pod.status.host_ip
|
||||
result["node_name"] = pod.spec.node_name
|
||||
result["owner_info"] = {
|
||||
"user_id": user_id,
|
||||
"agent_name": request.name,
|
||||
"namespace": NAMESPACE,
|
||||
"labels": pod.metadata.labels
|
||||
}
|
||||
logger.info(f"✅ Agent创建成功,Pod ID: {result['pod_id']}, 用户: {user_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"获取Pod详细信息失败: {str(e)}")
|
||||
|
||||
return AgentResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建Agent失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/agents/{agent_name}", response_model=MessageResponse)
|
||||
async def delete_agent(agent_name: str):
|
||||
"""
|
||||
删除AI Agent Pod
|
||||
|
||||
Args:
|
||||
agent_name: Agent名称
|
||||
|
||||
Returns:
|
||||
删除结果
|
||||
"""
|
||||
try:
|
||||
logger.info(f"收到删除Agent请求: {agent_name}")
|
||||
result = k8s_manager.delete_pod(pod_name=agent_name)
|
||||
|
||||
if result.get("status") == "not_found":
|
||||
raise HTTPException(status_code=404, detail=result.get("message"))
|
||||
|
||||
return MessageResponse(**result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"删除Agent失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/agents/{agent_name}/status", response_model=PodStatusResponse)
|
||||
async def get_agent_status(agent_name: str):
|
||||
"""
|
||||
获取Agent状态
|
||||
|
||||
Args:
|
||||
agent_name: Agent名称
|
||||
|
||||
Returns:
|
||||
Agent状态信息
|
||||
"""
|
||||
try:
|
||||
logger.info(f"获取Agent状态: {agent_name}")
|
||||
result = k8s_manager.get_pod_status(pod_name=agent_name)
|
||||
|
||||
if result.get("status") == "not_found":
|
||||
raise HTTPException(status_code=404, detail=result.get("message"))
|
||||
|
||||
return PodStatusResponse(**result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取Agent状态失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/agents/{agent_name}/metrics", response_model=PodMetricsResponse)
|
||||
async def get_agent_metrics(agent_name: str):
|
||||
"""
|
||||
获取Agent资源使用情况
|
||||
|
||||
Args:
|
||||
agent_name: Agent名称
|
||||
|
||||
Returns:
|
||||
Agent资源使用信息
|
||||
"""
|
||||
try:
|
||||
logger.info(f"获取Agent资源信息: {agent_name}")
|
||||
result = k8s_manager.get_pod_metrics(pod_name=agent_name)
|
||||
return PodMetricsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取Agent资源信息失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/agents")
|
||||
async def list_agents(template: Optional[str] = None):
|
||||
"""
|
||||
列出所有Agent
|
||||
|
||||
Args:
|
||||
template: 模板类型过滤(可选)
|
||||
|
||||
Returns:
|
||||
Agent列表
|
||||
"""
|
||||
try:
|
||||
logger.info(f"列出Agents, 模板过滤: {template}")
|
||||
|
||||
label_selector = "managed-by=agent-manager"
|
||||
if template:
|
||||
label_selector += f",template={template}"
|
||||
|
||||
result = k8s_manager.list_pods(label_selector=label_selector)
|
||||
return {"agents": result, "count": len(result)}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"列出Agents失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/templates")
|
||||
async def list_templates():
|
||||
"""
|
||||
列出所有可用的Agent模板及其所需参数
|
||||
|
||||
Returns:
|
||||
模板列表及其配置信息
|
||||
"""
|
||||
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent"]
|
||||
|
||||
templates_info = []
|
||||
for template in valid_templates:
|
||||
info = k8s_manager.get_template_info(template)
|
||||
templates_info.append(info)
|
||||
|
||||
return {
|
||||
"templates": templates_info,
|
||||
"count": len(templates_info)
|
||||
}
|
||||
|
||||
|
||||
@app.get("/templates/platform")
|
||||
async def list_platform_templates():
|
||||
"""
|
||||
获取平台 Agent 镜像列表
|
||||
|
||||
Returns:
|
||||
平台提供的Agent模板列表
|
||||
"""
|
||||
# 平台 Agent 是预定义的标准模板
|
||||
platform_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "jina_search_agent"]
|
||||
|
||||
templates_info = []
|
||||
for template in platform_templates:
|
||||
info = k8s_manager.get_template_info(template)
|
||||
info["type"] = "platform"
|
||||
templates_info.append(info)
|
||||
|
||||
return {
|
||||
"templates": templates_info,
|
||||
"count": len(templates_info),
|
||||
"type": "platform"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/templates/custom")
|
||||
async def list_custom_templates():
|
||||
"""
|
||||
获取自定义 Agent 镜像列表
|
||||
|
||||
Returns:
|
||||
用户自定义的Agent模板列表
|
||||
"""
|
||||
# 自定义 Agent 是用户可以配置数据库连接的模板
|
||||
custom_templates = ["mysql_agent", "postgresql_agent"]
|
||||
|
||||
templates_info = []
|
||||
for template in custom_templates:
|
||||
info = k8s_manager.get_template_info(template)
|
||||
info["type"] = "custom"
|
||||
templates_info.append(info)
|
||||
|
||||
return {
|
||||
"templates": templates_info,
|
||||
"count": len(templates_info),
|
||||
"type": "custom"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/templates/{template_name}")
|
||||
async def get_template_info(template_name: str):
|
||||
"""
|
||||
获取指定模板的详细信息
|
||||
|
||||
Args:
|
||||
template_name: 模板名称
|
||||
|
||||
Returns:
|
||||
模板详细信息(端口、所需环境变量等)
|
||||
"""
|
||||
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent"]
|
||||
|
||||
if template_name not in valid_templates:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"模板 {template_name} 不存在。可用模板: {', '.join(valid_templates)}"
|
||||
)
|
||||
|
||||
return k8s_manager.get_template_info(template_name)
|
||||
|
||||
|
||||
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服务: {host}:{port}")
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
Reference in New Issue
Block a user