This commit is contained in:
zhanggangyong
2026-01-15 17:22:16 +00:00
parent 3a19aacd43
commit f065b00d12
8 changed files with 741 additions and 32 deletions
+250 -19
View File
@@ -198,8 +198,10 @@ class CreateAgentRequest(BaseModel):
"""创建Agent请求(旧版)"""
name: str = Field(..., description="Agent名称", min_length=1, max_length=63)
template: str = Field(..., description="模板类型")
framework: Optional[str] = Field(default="API", description="Agent框架类型: MCP, A2A, API")
config: Dict = Field(default_factory=dict, description="配置信息")
env: Optional[Dict[str, str]] = Field(default_factory=dict, description="环境变量")
namespace: Optional[str] = Field(default=None, description="Kubernetes命名空间,默认使用环境变量NAMESPACE的值")
class AgentResponse(BaseModel):
@@ -207,6 +209,7 @@ class AgentResponse(BaseModel):
name: str
namespace: str
status: str
framework: Optional[str] = None
created_at: Optional[str] = None
template: Optional[str] = None
service_port: Optional[int] = None
@@ -240,6 +243,7 @@ class PodStatusResponse(BaseModel):
status: str
health_status: Optional[str] = None # 新增:健康状态 (healthy, unhealthy, degraded)
template: Optional[str] = None
framework: Optional[str] = None # Agent框架类型 (MCP, A2A, API)
created_at: Optional[str] = None
node: Optional[str] = None
pod_ip: Optional[str] = None
@@ -249,6 +253,8 @@ class PodStatusResponse(BaseModel):
access_url: Optional[str] = None
endpoints: Optional[Dict] = None
conditions: Optional[List[Dict]] = None
# 访问信息(从数据库读取)
access_info: Optional[Dict] = None # 包含 external_ip, domain, URLs 等
class PodMetricsResponse(BaseModel):
@@ -279,15 +285,16 @@ async def root():
@app.post("/agents", response_model=AgentResponse)
async def create_agent(request: CreateAgentRequest):
async def create_agent(request: CreateAgentRequest, db: Session = Depends(get_db)):
"""
创建AI Agent Pod
创建AI Agent Pod(在独立命名空间中,并创建Service和Ingress)
Args:
request: 创建请求(name, template, config, user_id可选)
request: 创建请求(name, template, config, namespace可选, user_id可选)
db: 数据库会话
Returns:
创建的Agent信息包括pod_id
创建的Agent信息包括pod_id、service和ingress信息
"""
try:
logger.info(f"收到创建Agent请求: {request.name}, 模板: {request.template}")
@@ -300,8 +307,18 @@ async def create_agent(request: CreateAgentRequest):
detail=f"无效的模板类型。支持的模板: {', '.join(valid_templates)}"
)
# 验证 framework 类型
valid_frameworks = ["MCP", "A2A", "API"]
framework = (request.framework or "API").upper()
if framework not in valid_frameworks:
raise HTTPException(
status_code=400,
detail=f"无效的框架类型。支持的框架: {', '.join(valid_frameworks)}"
)
# 合并环境变量到config
config_data = request.config.copy()
config_data["agent_framework"] = framework # 添加框架类型到配置
if request.env:
config_data["env"] = request.env
logger.info(f"环境变量: {list(request.env.keys())}")
@@ -312,36 +329,180 @@ async def create_agent(request: CreateAgentRequest):
config_data["labels"] = {}
config_data["labels"]["user-id"] = user_id
config_data["labels"]["managed-by"] = "agent-manager"
config_data["labels"]["app"] = request.name
config_data["labels"]["framework"] = framework.lower() # 添加框架标签
# 创建Pod
result = k8s_manager.create_pod(
# 步骤1: 为每个Agent创建独立的命名空间
agent_namespace = k8s_manager.create_agent_namespace(
agent_name=request.name,
owner_id=user_id
)
logger.info(f"✅ Agent {request.name} 将部署在独立命名空间: {agent_namespace}")
# 步骤2: 创建Pod(在独立命名空间中)
temp_manager = K8sManager(namespace=agent_namespace, kubeconfig_path=KUBECONFIG_PATH)
result = temp_manager.create_pod(
pod_name=request.name,
template=request.template,
config_data=config_data
)
# 步骤3: 获取服务端口
service_port = k8s_manager.TEMPLATE_PORTS.get(request.template)
# 步骤4: 创建 LoadBalancer Service(AKS 会自动分配外网 IP)
service_info = None
dns_info = None
if service_port:
try:
import time
time.sleep(2) # 等待Pod启动
# 创建 LoadBalancer Service
service_info = temp_manager.create_service(
service_name=f"{request.name}-service",
namespace=agent_namespace,
pod_selector={"app": request.name},
service_port=80, # 外部访问端口
target_port=service_port # Pod内部端口
)
logger.info(f"✅ LoadBalancer Service 创建成功: {service_info['name']}")
# 步骤5: 等待 LoadBalancer IP 分配并创建 DNS
try:
logger.info("等待 LoadBalancer 外网 IP 分配...")
external_ip = temp_manager.wait_for_loadbalancer_ip(
service_name=f"{request.name}-service",
namespace=agent_namespace,
max_wait=120, # 最多等待2分钟
interval=5
)
service_info["external_ip"] = external_ip
logger.info(f"✅ LoadBalancer 外网 IP: {external_ip}")
# 步骤6: 自动创建 Azure DNS 记录
try:
logger.info(f"创建 DNS 记录: {request.name}.taijiagnet.com")
dns_info = temp_manager.create_dns_record(
subdomain=request.name,
ip_address=external_ip
)
logger.info(f"✅ DNS 记录: {dns_info['domain']} -> {external_ip}")
except Exception as e:
logger.warning(f"DNS 记录创建失败: {str(e)}")
dns_info = {"status": "failed", "error": str(e)}
except Exception as e:
logger.warning(f"等待 LoadBalancer IP 超时: {str(e)}")
logger.info(" 外网 IP 将在后台继续分配")
except Exception as e:
logger.warning(f"创建 LoadBalancer Service 失败: {str(e)}")
# 获取 Pod 详细信息(包括 pod_id)
try:
import time
time.sleep(1) # 等待 Pod 创建完成
pod = k8s_manager.v1.read_namespaced_pod(
pod = temp_manager.v1.read_namespaced_pod(
name=request.name,
namespace=NAMESPACE
namespace=agent_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["namespace"] = agent_namespace # 更新为实际使用的命名空间
result["framework"] = framework # 添加框架类型到响应
result["owner_info"] = {
"user_id": user_id,
"agent_name": request.name,
"namespace": NAMESPACE,
"namespace": agent_namespace,
"framework": framework,
"labels": pod.metadata.labels
}
logger.info(f"✅ Agent创建成功,Pod ID: {result['pod_id']}, 用户: {user_id}")
# 添加 LoadBalancer Service 信息到响应
if service_info:
result["service_info"] = service_info
# 构建访问信息
external_ip = service_info.get("external_ip")
if external_ip:
# 有外网 IP
result["access_info"] = {
"external_ip": external_ip,
"ip_url": f"http://{external_ip}:80",
"service_url": f"http://{service_info['cluster_ip']}:80",
"pod_url": f"http://{pod.status.pod_ip}:{service_port}" if pod.status.pod_ip and service_port else None
}
# 添加 DNS 信息
if dns_info and dns_info.get("status") == "created":
result["dns_info"] = dns_info
result["access_info"]["domain"] = dns_info["domain"]
result["access_info"]["domain_url"] = f"http://{dns_info['domain']}"
result["access_info"]["recommended"] = f"http://{dns_info['domain']}"
logger.info(f" - 推荐访问: http://{dns_info['domain']}")
else:
result["access_info"]["recommended"] = f"http://{external_ip}:80"
logger.info(f" - 外网访问: http://{external_ip}:80")
else:
# IP 还在分配中
result["access_info"] = {
"status": "pending",
"external_ip": None,
"note": "LoadBalancer IP 正在分配中,请稍后查询"
}
logger.info(f" - 外网 IP 正在分配中")
logger.info(f"✅ Agent创建成功!")
logger.info(f" - Pod ID: {result['pod_id']}")
logger.info(f" - Namespace: {agent_namespace}")
logger.info(f" - User: {user_id}")
except Exception as e:
logger.warning(f"获取Pod详细信息失败: {str(e)}")
# 保存到数据库
try:
# 提取访问信息
access_info = result.get("access_info", {})
external_ip = access_info.get("external_ip")
domain = access_info.get("domain")
ip_url = access_info.get("ip_url")
domain_url = access_info.get("domain_url")
recommended_url = access_info.get("recommended", domain_url or ip_url)
# 创建Agent记录
db_agent = Agent(
name=request.name,
display_name=request.name,
owner_id=user_id,
agent_type=AgentType.PLATFORM, # 默认为平台类型
status=AgentStatus.RUNNING,
agent_framework=framework.lower(),
namespace=agent_namespace,
service_name=service_info.get("name") if service_info else None,
external_ip=external_ip,
domain=domain,
ip_url=ip_url,
domain_url=domain_url,
recommended_url=recommended_url,
min_replicas=1,
max_replicas=1,
current_replicas=1
)
db.add(db_agent)
db.commit()
db.refresh(db_agent)
logger.info(f"✅ Agent信息已保存到数据库: {db_agent.id}")
except Exception as e:
logger.error(f"保存Agent到数据库失败: {str(e)}")
# 不抛出异常,因为Agent已经在K8s中创建成功
return AgentResponse(**result)
except Exception as e:
@@ -350,23 +511,48 @@ async def create_agent(request: CreateAgentRequest):
@app.delete("/agents/{agent_name}", response_model=MessageResponse)
async def delete_agent(agent_name: str):
async def delete_agent(agent_name: str, db: Session = Depends(get_db)):
"""
删除AI Agent Pod
删除AI Agent(包括独立命名空间、LoadBalancer Service、DNS 记录和数据库记录)
Args:
agent_name: Agent名称
db: 数据库会话
Returns:
删除结果
"""
try:
logger.info(f"收到删除Agent请求: {agent_name}")
result = k8s_manager.delete_pod(pod_name=agent_name)
# 步骤1: 删除 DNS 记录
try:
dns_result = k8s_manager.delete_dns_record(subdomain=agent_name)
if dns_result.get("status") == "deleted":
logger.info(f"✅ DNS 记录已删除: {dns_result.get('domain')}")
except Exception as e:
logger.warning(f"删除 DNS 记录失败(可忽略): {str(e)}")
# 步骤2: 删除 Agent 的独立命名空间(会自动删除Pod、Service等所有资源)
result = k8s_manager.delete_agent_namespace(agent_name=agent_name)
if result.get("status") == "not_found":
raise HTTPException(status_code=404, detail=result.get("message"))
# 步骤3: 从数据库删除Agent记录
try:
db_agent = db.query(Agent).filter(Agent.name == agent_name).first()
if db_agent:
db.delete(db_agent)
db.commit()
logger.info(f"✅ Agent数据库记录已删除: {agent_name}")
else:
logger.warning(f"⚠️ Agent {agent_name} 在数据库中未找到")
except Exception as db_error:
logger.error(f"删除数据库记录失败: {str(db_error)}")
# 不抛出异常,因为K8s资源已经删除
logger.info(f"✅ Agent {agent_name} 及其所有资源删除成功")
return MessageResponse(**result)
except HTTPException:
@@ -377,23 +563,68 @@ async def delete_agent(agent_name: str):
@app.get("/agents/{agent_name}/status", response_model=PodStatusResponse)
async def get_agent_status(agent_name: str):
async def get_agent_status(agent_name: str, db: Session = Depends(get_db)):
"""
获取Agent状态
获取Agent详细状态(包括访问信息)
Args:
agent_name: Agent名称
db: 数据库会话
Returns:
Agent状态信息
Agent状态信息(包括Pod状态和访问信息)
"""
try:
logger.info(f"获取Agent状态: {agent_name}")
# 从Kubernetes获取Pod状态
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"))
# 从数据库获取Agent记录(包含访问信息)
try:
db_agent = db.query(Agent).filter(Agent.name == agent_name).first()
if db_agent:
# 添加框架类型
result["framework"] = db_agent.agent_framework.upper() if db_agent.agent_framework else "API"
# 添加访问信息
access_info = {}
if db_agent.external_ip:
access_info["external_ip"] = db_agent.external_ip
if db_agent.ip_url:
access_info["ip_url"] = db_agent.ip_url
if db_agent.domain:
access_info["domain"] = db_agent.domain
if db_agent.domain_url:
access_info["domain_url"] = db_agent.domain_url
if db_agent.recommended_url:
access_info["recommended_url"] = db_agent.recommended_url
if db_agent.service_name:
access_info["service_name"] = db_agent.service_name
# 只有当有访问信息时才添加
if access_info:
result["access_info"] = access_info
logger.info(f"✅ 已添加访问信息: domain={db_agent.domain}, ip={db_agent.external_ip}")
else:
logger.warning(f"⚠️ Agent {agent_name} 在数据库中没有访问信息")
else:
logger.warning(f"⚠️ Agent {agent_name} 在数据库中未找到,可能是在数据库启用前创建的")
except Exception as db_error:
logger.error(f"从数据库读取访问信息失败: {str(db_error)}")
# 不抛出异常,继续返回Pod状态信息
return PodStatusResponse(**result)
except HTTPException:
@@ -458,7 +689,7 @@ async def list_templates():
Returns:
模板列表及其配置信息
"""
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent"]
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a"]
templates_info = []
for template in valid_templates:
@@ -480,7 +711,7 @@ async def list_platform_templates():
平台提供的Agent模板列表
"""
# 平台 Agent 是预定义的标准模板
platform_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "jina_search_agent", "azure_blob_agent"]
platform_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a"]
templates_info = []
for template in platform_templates:
@@ -530,7 +761,7 @@ async def get_template_info(template_name: str):
Returns:
模板详细信息(端口、所需环境变量等)
"""
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent"]
valid_templates = ["echo_agent", "chat_agent", "code_agent", "search_agent", "mysql_agent", "postgresql_agent", "jina_search_agent", "azure_blob_agent", "azure_blob_agent_mcp", "azure_blob_agent_a2a"]
if template_name not in valid_templates:
raise HTTPException(