Files
taiji-AI-PAD/services/mcp-server/app/routes/agents.py
T
2026-03-12 02:32:23 +00:00

915 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Agent CRUD and execution endpoints with Kubernetes integration."""
from __future__ import annotations
import json
import time
import uuid
import structlog
from datetime import datetime
from decimal import Decimal
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from models import Agent, User, Billing, Execution, Balance, Session, TenantCustomAgentQuota
from schemas import (
AgentCard, AgentCreateRequest, ExecutionResult, MCPRequest,
SessionCreate, SessionResponse,
AgentStatusResponse, AgentMetricsResponse, TemplateInfo, TemplateListResponse,
K8sResourceConfig
)
from ..metrics import (
agents_queries_total,
agents_registered_total,
mcp_request_duration,
mcp_requests_total,
)
from ..state import get_state
from ..utils import record_tool_metrics
from ..auth import get_current_user
from ..resource_control import enforce_resource_control, resource_controller
from ..agent_manager_client import (
get_agent_manager_client,
AgentConfig,
AgentManagerError,
)
logger = structlog.get_logger(__name__)
router = APIRouter(prefix="/agents", tags=["agents"])
def _build_agent_card(agent: Agent) -> AgentCard:
"""构建 Agent 卡片信息"""
return AgentCard(
id=agent.id,
name=agent.name,
description=agent.description,
role=agent.role,
goal=agent.goal,
tools=agent.tools or [],
capabilities=agent.capabilities or [],
endpoints=agent.endpoints or {
"mcp": f"mcp://localhost:8002/agents/{agent.id}",
"http": f"http://localhost:8002/agents/{agent.id}",
"websocket": f"ws://localhost:8002/agents/{agent.id}/ws",
},
status=agent.status,
version=agent.version,
# K8s 相关信息
template=agent.template,
pod_name=agent.pod_name,
pod_ip=agent.pod_ip,
k8s_status=agent.k8s_status,
service_port=agent.service_port,
access_url=agent.access_url,
# 模型配置
model_name=agent.model_name,
# 资源配置
cpu_request=agent.cpu_request,
cpu_limit=agent.cpu_limit,
memory_request=agent.memory_request,
memory_limit=agent.memory_limit,
# 统计信息
total_executions=agent.total_executions,
success_rate=agent.success_rate,
avg_execution_time=agent.avg_execution_time,
created_at=agent.created_at,
updated_at=agent.updated_at,
pod_created_at=agent.pod_created_at,
)
async def _get_balance(db: AsyncSession, user_id: uuid.UUID, for_update: bool = False) -> Balance:
"""
获取或创建用户余额记录
Args:
db: 数据库会话
user_id: 用户ID
for_update: 是否使用行锁(用于更新操作)
Returns:
Balance 对象
"""
query = select(Balance).where(Balance.user_id == user_id)
if for_update:
query = query.with_for_update() # 行锁保护并发更新
result = await db.execute(query)
balance = result.scalar_one_or_none()
if balance is None:
balance = Balance(user_id=user_id, eu_balance=0.0)
db.add(balance)
await db.flush()
return balance
# ==================== 模板管理 ====================
@router.get("/templates", response_model=TemplateListResponse)
async def list_templates() -> TemplateListResponse:
"""获取所有可用的 Agent 模板"""
try:
client = get_agent_manager_client()
templates = await client.list_templates()
return TemplateListResponse(
templates=[
TemplateInfo(
template=t.template,
displayName=t.display_name,
description=t.description,
category=t.category,
port=t.port,
env_info=t.env_info
)
for t in templates
],
count=len(templates)
)
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/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,
displayName=t.display_name,
description=t.description,
category=t.category,
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,
displayName=t.display_name,
description=t.description,
category=t.category,
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:
"""获取模板详情"""
try:
client = get_agent_manager_client()
template = await client.get_template(template_name)
return TemplateInfo(
template=template.template,
port=template.port,
env_info=template.env_info
)
except AgentManagerError as e:
logger.error("获取模板详情失败", template=template_name, error=str(e))
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
logger.error("获取模板详情失败", template=template_name, error=str(e))
raise HTTPException(status_code=500, detail=str(e))
# ==================== Agent CRUD ====================
@router.post("", response_model=AgentCard)
async def create_agent(
request: AgentCreateRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
) -> AgentCard:
"""
创建新的 Agent。
如果提供了 template,将在 Kubernetes 中创建对应的 Pod。
"""
state = get_state()
try:
user_id = uuid.UUID(current_user["user_id"])
# ========== 资源管控检查 ==========
await enforce_resource_control(
user_id=str(user_id),
resource_type="agent",
resource_id=None,
estimated_cost=Decimal("0.0"),
db=db
)
# ==================================
# 准备资源配置
resource_config = request.resource_config or K8sResourceConfig()
# 创建数据库记录
agent = Agent(
name=request.name,
description=request.description,
role=request.role,
goal=request.goal,
tools=request.tools,
config=request.config,
capabilities=request.capabilities,
owner_id=user_id,
# K8s 相关字段
template=request.template,
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_config=resource_config.env,
k8s_status="Unknown",
)
db.add(agent)
await db.flush() # 获取 agent.id
# 如果指定了模板,创建 K8s Pod
if request.template:
try:
client = get_agent_manager_client()
# 构建 Pod 名称(使用 agent ID 确保唯一性)
pod_name = f"{request.name}-{str(agent.id)[:8]}"
# 创建 Agent 配置(适配新的 AgentConfig 格式)
agent_config = AgentConfig(
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,
replicas=1, # 默认单副本
)
# 调用 Agent Manager API 创建 Pod
# 使用统一的 POST /agents 接口
result = await client.create_agent(
name=pod_name,
template=request.template,
config=agent_config,
env=resource_config.env # 环境变量单独传递
)
# 更新数据库记录
agent.pod_name = result.name
agent.k8s_namespace = result.namespace
agent.k8s_status = result.status
agent.service_port = result.service_port
# 解析时间并移除时区信息(数据库使用 TIMESTAMP WITHOUT TIME ZONE)
pod_created = datetime.fromisoformat(result.created_at.replace("Z", "+00:00"))
agent.pod_created_at = pod_created.replace(tzinfo=None)
if result.access_info:
agent.endpoints = result.access_info.get("endpoints", {})
logger.info(
"K8s Pod 创建成功",
agent_id=str(agent.id),
pod_name=result.name,
status=result.status
)
except AgentManagerError as e:
# K8s 创建失败,回滚数据库
await db.rollback()
# Ensure detail is serializable for logging
detail_info = e.detail
try:
import json as _json
detail_serialized = _json.dumps(detail_info, ensure_ascii=False)
except Exception:
detail_serialized = str(detail_info)
logger.error(
"创建 K8s Pod 失败",
error=str(e),
status_code=e.status_code,
detail=detail_serialized
)
raise HTTPException(
status_code=e.status_code,
detail={
"error": "k8s_creation_failed",
"message": f"创建 Kubernetes Pod 失败: {e.message}",
"detail": detail_info
}
)
await db.commit()
await db.refresh(agent)
card = _build_agent_card(agent)
# 缓存到 Redis
redis_client = state.redis_client
if redis_client:
await redis_client.setex(
f"agent:{agent.id}", 3600, json.dumps(card.model_dump(mode="json"), ensure_ascii=False)
)
# 注册Agent的工具权限到Redis
if agent.tools:
agent_tools_key = f"agent:{agent.id}:tools"
for tool_name in agent.tools:
await redis_client.sadd(agent_tools_key, tool_name)
await redis_client.expire(agent_tools_key, 86400) # 24小时过期
# 同时注册工具信息到Redis(如果是内置函数)
from function_registry import get_function_registry
func_registry = get_function_registry()
for tool_name in agent.tools:
func_info = func_registry.get(tool_name)
if func_info:
tool_data = {
"name": tool_name,
"description": func_info.get("description", ""),
"category": "function",
"schema": {
"type": "object",
"properties": {
p["name"]: {"type": p.get("type", "string"), "description": p.get("description", "")}
for p in func_info.get("parameters", [])
},
"required": [p["name"] for p in func_info.get("parameters", []) if p.get("required", True)]
}
}
await redis_client.setex(
f"tool:{tool_name}", 86400, json.dumps(tool_data, ensure_ascii=False)
)
logger.info(f"已为Agent {agent.id} 注册 {len(agent.tools)} 个工具")
# 发布事件到 NATS
nats_client = state.nats_client
if nats_client:
await nats_client.publish(
"agent.created",
json.dumps(
{
"agent_id": str(agent.id),
"name": agent.name,
"template": agent.template,
"pod_name": agent.pod_name,
"timestamp": datetime.utcnow().isoformat(),
}
).encode(),
)
agents_registered_total.labels(status="success").inc()
logger.info("Agent创建成功", agent_id=str(agent.id), template=agent.template)
return card
except HTTPException:
raise
except Exception as exc:
await db.rollback()
agents_registered_total.labels(status="error").inc()
logger.error("创建Agent失败", error=str(exc))
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("", response_model=List[AgentCard])
async def list_agents(
skip: int = 0,
limit: int = 100,
template: Optional[str] = Query(None, description="按模板类型过滤"),
db: AsyncSession = Depends(get_db)
) -> List[AgentCard]:
"""返回分页的 Agent 列表"""
state = get_state()
redis_client = state.redis_client
try:
agents_queries_total.labels(operation="list").inc()
query = select(Agent).order_by(Agent.created_at.desc())
# 按模板过滤
if template:
query = query.where(Agent.template == template)
query = query.offset(skip).limit(limit)
result = await db.execute(query)
agent_rows = result.scalars().all()
cards: List[AgentCard] = []
for agent in agent_rows:
card = _build_agent_card(agent)
cards.append(card)
if redis_client:
await redis_client.setex(
f"agent:{agent.id}", 3600, json.dumps(card.model_dump(mode="json"), ensure_ascii=False)
)
return cards
except Exception as exc:
logger.error("获取Agent列表失败", error=str(exc))
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("/{agent_id}", response_model=AgentCard)
async def get_agent(agent_id: str, db: AsyncSession = Depends(get_db)) -> AgentCard:
"""获取指定 Agent 的详细信息"""
state = get_state()
redis_client = state.redis_client
try:
agents_queries_total.labels(operation="get").inc()
if redis_client:
cached = await redis_client.get(f"agent:{agent_id}")
if cached:
return AgentCard.parse_raw(cached)
agent_uuid = uuid.UUID(agent_id)
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
card = _build_agent_card(agent)
if redis_client:
await redis_client.setex(
f"agent:{agent.id}", 3600, json.dumps(card.model_dump(mode="json"), ensure_ascii=False)
)
return card
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID") from exc
except HTTPException:
raise
except Exception as exc:
logger.error("获取Agent失败", error=str(exc))
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.delete("/{agent_id}")
async def delete_agent(
agent_id: str,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
) -> Dict[str, Any]:
"""
删除 Agent。
如果 Agent 有关联的 K8s Pod,也会一并删除。
如果是自定义 Agent,会释放用户的配额。
"""
try:
agent_uuid = uuid.UUID(agent_id)
user_id = uuid.UUID(current_user["user_id"])
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID") from exc
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
# 检查权限
if agent.owner_id != user_id and current_user.get("role") != "super_admin":
raise HTTPException(status_code=403, detail="Access denied")
# 保存 Agent 信息用于后续处理
agent_name = agent.name
agent_type = agent.type
agent_cpu = float(agent.cpu or 0)
agent_memory = float(agent.memory or 0)
agent_owner_id = agent.owner_id
# 如果有 Pod,先删除 K8s Pod
if agent.pod_name:
try:
client = get_agent_manager_client()
await client.delete_agent(agent.pod_name)
logger.info("K8s Pod 删除成功", pod_name=agent.pod_name)
except AgentManagerError as e:
# 如果 Pod 不存在(404),继续删除数据库记录
if e.status_code != 404:
logger.error("删除 K8s Pod 失败", pod_name=agent.pod_name, error=str(e))
raise HTTPException(
status_code=e.status_code,
detail={
"error": "k8s_deletion_failed",
"message": f"删除 Kubernetes Pod 失败: {e.message}",
"detail": e.detail
}
)
# 如果是自定义 Agent,释放用户配额
if agent_type == "custom" and agent_owner_id:
quota_result = await db.execute(
select(TenantCustomAgentQuota).where(
TenantCustomAgentQuota.tenant_id == agent_owner_id
)
)
quota = quota_result.scalar_one_or_none()
if quota:
# 释放配额
quota.cpu_used = max(0, float(quota.cpu_used or 0) - agent_cpu)
quota.memory_used = max(0, float(quota.memory_used or 0) - agent_memory)
quota.agent_count = max(0, (quota.agent_count or 0) - 1)
logger.info(
"释放自定义 Agent 配额",
agent_id=agent_id,
cpu_released=agent_cpu,
memory_released=agent_memory
)
# 删除数据库记录
await db.delete(agent)
await db.commit()
# 清除缓存
state = get_state()
redis_client = state.redis_client
if redis_client:
await redis_client.delete(f"agent:{agent_id}")
# 发布事件
nats_client = state.nats_client
if nats_client:
await nats_client.publish(
"agent.deleted",
json.dumps(
{
"agent_id": agent_id,
"name": agent_name,
"type": agent_type,
"pod_name": agent.pod_name,
"timestamp": datetime.utcnow().isoformat(),
}
).encode(),
)
logger.info("Agent删除成功", agent_id=agent_id, type=agent_type)
return {"status": "success", "message": f"Agent {agent_name} 已删除"}
# ==================== Agent 状态和监控 ====================
@router.get("/{agent_id}/status", response_model=AgentStatusResponse)
async def get_agent_status(
agent_id: str,
db: AsyncSession = Depends(get_db),
) -> AgentStatusResponse:
"""
获取 Agent 的实时状态。
如果 Agent 有关联的 K8s Pod,会从 Agent Manager 获取最新状态。
"""
try:
agent_uuid = uuid.UUID(agent_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID") from exc
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
# 如果有 Pod,获取实时状态
if agent.pod_name:
try:
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
if status.endpoints:
agent.endpoints = status.endpoints
await db.commit()
return AgentStatusResponse(
id=agent.id,
name=agent.name,
status=agent.status,
k8s_status=status.status,
pod_name=status.name,
pod_ip=status.pod_ip,
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,
memory_request=agent.memory_request,
memory_limit=agent.memory_limit,
created_at=agent.created_at,
pod_created_at=agent.pod_created_at,
conditions=None, # 新接口不返回 conditions
)
except AgentManagerError as e:
logger.warning("获取 Pod 状态失败", pod_name=agent.pod_name, error=str(e))
# 返回数据库中的状态
# 返回数据库中的状态
return AgentStatusResponse(
id=agent.id,
name=agent.name,
status=agent.status,
k8s_status=agent.k8s_status or "Unknown",
pod_name=agent.pod_name,
pod_ip=agent.pod_ip,
service_port=agent.service_port,
access_url=agent.access_url,
endpoints=agent.endpoints or {},
cpu_request=agent.cpu_request,
cpu_limit=agent.cpu_limit,
memory_request=agent.memory_request,
memory_limit=agent.memory_limit,
created_at=agent.created_at,
pod_created_at=agent.pod_created_at,
)
@router.get("/{agent_id}/metrics", response_model=AgentMetricsResponse)
async def get_agent_metrics(
agent_id: str,
db: AsyncSession = Depends(get_db),
) -> AgentMetricsResponse:
"""
获取 Agent 的资源使用情况。
"""
try:
agent_uuid = uuid.UUID(agent_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID") from exc
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
# 如果有 Pod,获取实时资源使用
if agent.pod_name:
try:
client = get_agent_manager_client()
metrics = await client.get_agent_metrics(agent.pod_name)
return AgentMetricsResponse(
id=agent.id,
name=agent.name,
requests=metrics.requests,
limits=metrics.limits,
)
except AgentManagerError as e:
logger.warning("获取 Pod 资源使用失败", pod_name=agent.pod_name, error=str(e))
# 返回数据库中的配置
return AgentMetricsResponse(
id=agent.id,
name=agent.name,
requests={
"cpu": agent.cpu_request or "100m",
"memory": agent.memory_request or "128Mi",
},
limits={
"cpu": agent.cpu_limit or "500m",
"memory": agent.memory_limit or "512Mi",
},
)
# ==================== Agent 执行 ====================
@router.post("/{agent_id}/execute", response_model=ExecutionResult)
async def execute_agent(
agent_id: str,
request: MCPRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
session_id: Optional[str] = None,
) -> ExecutionResult:
"""Execute an MCP request for a given agent with session support."""
state = get_state()
handler = state.mcp_handler
if not handler:
raise HTTPException(status_code=500, detail="MCP handler not initialized")
try:
agent_uuid = uuid.UUID(agent_id)
user_id = uuid.UUID(current_user["user_id"])
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID or user ID") from exc
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
# Check if user owns the agent
if agent.owner_id != user_id and current_user.get("role") != "super_admin":
raise HTTPException(status_code=403, detail="Access denied")
# ========== 资源管控检查 ==========
# 执行前检查用户配额和速率限制
await enforce_resource_control(
user_id=str(user_id),
resource_type="agent",
resource_id=agent_id,
estimated_cost=Decimal("0.01"), # 可根据agent类型动态计算
db=db
)
# ==================================
# Get or create session
session = None
if session_id:
result_query = await db.execute(
select(Session).where(
Session.session_id == session_id,
Session.user_id == user_id
)
)
session = result_query.scalar_one_or_none()
if not session:
# Create new session
session = Session(
session_id=session_id or str(uuid.uuid4()),
user_id=user_id,
context={"agent_id": agent_id, "request_history": []},
session_metadata={"created_via": "agent_execution"},
status="active",
)
db.add(session)
await db.flush()
start_time = time.time()
try:
mcp_requests_total.labels(method=request.method, status="processing").inc()
# Pass session context and resource control parameters to handler
result = await handler.execute_request(
agent_id,
request,
user_id=str(user_id),
db_session=db
)
duration = time.time() - start_time
mcp_requests_total.labels(method=request.method, status="success").inc()
mcp_request_duration.labels(method=request.method).observe(duration)
record_tool_metrics(request, duration, success=True)
nats_client = state.nats_client
if nats_client:
await nats_client.publish(
f"agent.execution.{agent_id}",
json.dumps(
{
"agent_id": agent_id,
"request_id": request.id,
"method": request.method,
"timestamp": datetime.utcnow().isoformat(),
"success": result.success,
}
).encode(),
)
# 记录执行与计费
execution = Execution(
execution_id=str(request.id),
method=request.method,
params=request.params or {},
result=result.result if result.success else {},
error=result.error,
started_at=datetime.utcfromtimestamp(start_time),
completed_at=datetime.utcnow(),
execution_time=duration,
status="completed" if result.success else "failed",
cpu_usage=result.cpu_usage if hasattr(result, "cpu_usage") else 0.0,
memory_usage=result.memory_usage if hasattr(result, "memory_usage") else 0.0,
network_io=result.network_io if hasattr(result, "network_io") else 0.0,
# ⚠️ 已废弃:旧的 EU 计算方式(1 EU = 10秒)
# 新的计费逻辑中,EU = Cost(美元),即 1 EU = 1 美元
# 此处保留旧逻辑以兼容旧的 Execution 表
eu_consumed=max(duration / 10.0, 0.0), # TODO: 迁移到新计费逻辑
agent_id=agent.id,
session_id=session.id if session else None,
)
# Update session context with execution history
if session:
context = session.context or {}
request_history = context.get("request_history", [])
request_history.append({
"execution_id": str(execution.id),
"method": request.method,
"timestamp": datetime.utcnow().isoformat(),
"success": result.success,
})
# Keep only last 50 requests in session
context["request_history"] = request_history[-50:]
session.context = context
session.updated_at = datetime.utcnow()
db.add(session)
db.add(execution)
await db.flush()
billing = Billing(
execution_id=execution.id,
eu_consumed=execution.eu_consumed,
cost=execution.eu_consumed,
currency="EU",
cpu_time=execution.cpu_usage,
memory_max=execution.memory_usage,
network_io=execution.network_io,
storage_io=0.0,
user_id=agent.owner_id,
)
db.add(billing)
# 使用行锁保护余额更新,防止并发扣款
balance = await _get_balance(db, agent.owner_id, for_update=True)
balance.eu_balance = (balance.eu_balance or 0) - execution.eu_consumed
# ========== 记录资源消耗 ==========
# 记录到资源监控系统
await resource_controller.record_resource_consumption(
user_id=str(agent.owner_id),
resource_type="agent",
resource_id=agent_id,
cost=Decimal(str(execution.eu_consumed)),
execution_time_ms=duration * 1000,
cpu_usage=execution.cpu_usage,
memory_usage=execution.memory_usage,
network_io=execution.network_io,
db=db
)
# ==================================
await db.commit()
return result
except Exception as exc:
duration = time.time() - start_time
mcp_requests_total.labels(method=request.method, status="error").inc()
mcp_request_duration.labels(method=request.method).observe(duration)
record_tool_metrics(request, duration, success=False)
logger.error("执行Agent任务失败", error=str(exc))
raise HTTPException(status_code=500, detail=str(exc)) from exc