diff --git a/-H b/-H deleted file mode 100644 index f59dcfa..0000000 --- a/-H +++ /dev/null @@ -1 +0,0 @@ -{"detail":"Not authenticated"} \ No newline at end of file diff --git a/Docs/用户资源信息查询接口文档.md b/Docs/用户资源信息查询接口文档.md new file mode 100644 index 0000000..5b8d948 --- /dev/null +++ b/Docs/用户资源信息查询接口文档.md @@ -0,0 +1,337 @@ +# 用户资源信息查询接口文档 + +> **版本**: 2026-01-13 v1 +> **接口路径**: `GET /api/user/resources/info` +> **认证方式**: Bearer Token(JWT) + +--- + +## 📋 接口概述 + +| 项目 | 值 | +|------|-----| +| **接口路径** | `GET /api/user/resources/info` | +| **接口描述** | 获取用户的 LiteLLM 密钥和已部署 Agent 的详细信息 | +| **认证方式** | Bearer Token(JWT) | +| **Content-Type** | application/json | + +### 功能说明 + +该接口用于获取当前用户的以下资源信息: + +1. **LiteLLM 密钥**:解密后的完整 API Key,可直接用于调用 AI 模型 +2. **平台 Agent**:已部署的平台 Agent 列表,包含实时 IP 和访问地址 +3. **自定义 Agent**:已部署的自定义 Agent 列表,包含实时 IP 和访问地址 + +--- + +## 📥 请求参数 + +### 请求头(Headers) + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|:----:|------| +| `Authorization` | string | ✅ | Bearer Token,格式:`Bearer ` | + +### 请求体(Body) + +无 + +### 查询参数(Query) + +无 + +--- + +## 📤 请求示例 + +```http +GET /api/user/resources/info HTTP/1.1 +Host: api.taiji-ai.com +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: application/json +``` + +--- + +## 📊 响应参数 + +### 响应结构 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | boolean | 请求是否成功 | +| `message` | string | 响应消息 | +| `data` | object | 响应数据 | + +### data 对象 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `litellmKeys` | array | LiteLLM 密钥列表 | +| `litellmApiBase` | string | LiteLLM 网关地址(全局) | +| `platformAgents` | array | 已部署的平台 Agent 列表 | +| `customAgents` | array | 已部署的自定义 Agent 列表 | +| `summary` | object | 汇总统计信息 | + +--- + +### litellmKeys 数组元素 + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| `modelName` | string | 模型名称 | `"azure/gpt-4"` | +| `apiKey` | string | **解密后的完整 API Key**(用于调用模型) | `"sk-xxxxxxxxxxxxxxxx"` | +| `apiBase` | string | LiteLLM 网关地址 | `"https://litellm.xxx.azurecontainerapps.io"` | +| `rpmLimit` | integer | 每分钟请求数限制 | `60` | +| `tpmLimit` | integer | 每分钟 Token 数限制 | `10000` | +| `maxBudget` | number \| null | 最大预算(美元) | `100.0` | +| `budgetDuration` | string | 预算周期 | `"monthly"` | +| `status` | string | 密钥状态 | `"active"` | +| `createdAt` | string | 创建时间(ISO 8601) | `"2026-01-13T10:00:00"` | +| `error` | string | 错误信息(仅解密失败时返回) | `"密钥解密失败"` | + +--- + +### platformAgents / customAgents 数组元素 + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| `name` | string | Agent 实例名称 | `"user-123-echo-agent-abc"` | +| `template` | string | Agent 类型/模板 | `"echo_agent"` | +| `templateName` | string | 模板名称 | `"echo_agent"` | +| `status` | string | 运行状态(AKS 实时) | `"Running"` / `"Pending"` / `"Failed"` | +| `healthStatus` | string | 健康状态(AKS 实时) | `"healthy"` / `"unhealthy"` / `"unknown"` | +| `podIp` | string \| null | **Pod IP 地址**(AKS 实时) | `"10.244.1.100"` | +| `accessUrl` | string \| null | **访问 URL**(AKS 实时) | `"http://xxx.ai-agents.svc.cluster.local:8080"` | +| `servicePort` | integer \| null | 服务端口 | `8080` | +| `namespace` | string | K8s 命名空间 | `"ai-agents"` | +| `hostIp` | string \| null | 宿主机 IP(AKS 实时) | `"192.168.1.10"` | +| `nodeName` | string \| null | 节点名称(AKS 实时) | `"aks-agentpool-xxxxx"` | +| `cpu` | string | CPU 配置 | `"100m"` | +| `memory` | string | 内存配置 | `"256Mi"` | +| `replicas` | integer | 副本数量 | `1` | +| `startTime` | string | 启动时间(ISO 8601) | `"2026-01-13T08:00:00"` | +| `runningSeconds` | integer | 已运行秒数 | `7200` | +| `endpoints` | array | 端点列表(可选) | 见下表 | + +### endpoints 数组元素(可选) + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| `name` | string | 端点名称 | `"http"` | +| `port` | integer | 端口号 | `8080` | +| `protocol` | string | 协议 | `"TCP"` | +| `targetPort` | integer | 目标端口 | `8080` | + +--- + +### summary 对象 + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| `totalLitellmKeys` | integer | LiteLLM 密钥总数 | `2` | +| `totalPlatformAgents` | integer | 已部署的平台 Agent 数量 | `1` | +| `totalCustomAgents` | integer | 已部署的自定义 Agent 数量 | `1` | + +--- + +## 📝 响应示例 + +### 成功响应 + +```json +{ + "success": true, + "message": "用户资源信息获取成功", + "data": { + "litellmKeys": [ + { + "modelName": "azure/gpt-4", + "apiKey": "sk-1234567890abcdef1234567890abcdef", + "apiBase": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io", + "rpmLimit": 60, + "tpmLimit": 10000, + "maxBudget": 100.0, + "budgetDuration": "monthly", + "status": "active", + "createdAt": "2026-01-10T10:30:00" + }, + { + "modelName": "azure/gpt-3.5-turbo", + "apiKey": "sk-abcdef1234567890abcdef1234567890", + "apiBase": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io", + "rpmLimit": 120, + "tpmLimit": 50000, + "maxBudget": 50.0, + "budgetDuration": "monthly", + "status": "active", + "createdAt": "2026-01-10T10:30:00" + } + ], + "litellmApiBase": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io", + "platformAgents": [ + { + "name": "user-abc123-echo-agent-def456", + "template": "echo_agent", + "templateName": "echo_agent", + "status": "Running", + "healthStatus": "healthy", + "podIp": "10.244.1.100", + "accessUrl": "http://user-abc123-echo-agent-def456.ai-agents.svc.cluster.local:8080", + "servicePort": 8080, + "namespace": "ai-agents", + "hostIp": "192.168.1.10", + "nodeName": "aks-agentpool-12345678-vmss000001", + "cpu": "100m", + "memory": "256Mi", + "replicas": 1, + "startTime": "2026-01-13T08:00:00", + "runningSeconds": 7200, + "endpoints": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": 8080 + } + ] + } + ], + "customAgents": [ + { + "name": "user-abc123-mysql-agent-xyz789", + "template": "mysql_agent", + "templateName": "mysql_agent", + "status": "Running", + "healthStatus": "healthy", + "podIp": "10.244.1.101", + "accessUrl": "http://user-abc123-mysql-agent-xyz789.ai-agents.svc.cluster.local:8080", + "servicePort": 8080, + "namespace": "ai-agents", + "hostIp": "192.168.1.11", + "nodeName": "aks-agentpool-12345678-vmss000002", + "cpu": "200m", + "memory": "512Mi", + "replicas": 1, + "startTime": "2026-01-13T09:30:00", + "runningSeconds": 1800, + "endpoints": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": 8080 + } + ] + } + ], + "summary": { + "totalLitellmKeys": 2, + "totalPlatformAgents": 1, + "totalCustomAgents": 1 + } + } +} +``` + +### 无资源时的响应 + +```json +{ + "success": true, + "message": "用户资源信息获取成功", + "data": { + "litellmKeys": [], + "litellmApiBase": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io", + "platformAgents": [], + "customAgents": [], + "summary": { + "totalLitellmKeys": 0, + "totalPlatformAgents": 0, + "totalCustomAgents": 0 + } + } +} +``` + +### 认证失败响应 + +**HTTP Status Code**: `401 Unauthorized` + +```json +{ + "detail": "Not authenticated" +} +``` + +--- + +## 🔧 使用说明 + +### LiteLLM 密钥使用方式 + +获取到的 `apiKey` 可以直接用于调用 AI 模型(OpenAI 兼容格式): + +```python +import openai + +# 从接口返回的数据中获取 +api_key = response["data"]["litellmKeys"][0]["apiKey"] +api_base = response["data"]["litellmKeys"][0]["apiBase"] + +client = openai.OpenAI( + api_key=api_key, + base_url=api_base +) + +response = client.chat.completions.create( + model="azure/gpt-4", + messages=[{"role": "user", "content": "Hello!"}] +) +``` + +### Agent 访问方式 + +通过 `accessUrl` 可以在集群内部访问 Agent 服务: + +```bash +# 在集群内部调用 Agent +curl http://user-abc123-echo-agent-def456.ai-agents.svc.cluster.local:8080/api/chat +``` + +通过 `podIp` 和 `servicePort` 也可以直接访问: + +```bash +# 直接通过 Pod IP 访问 +curl http://10.244.1.100:8080/api/chat +``` + +--- + +## 📌 数据来源说明 + +| 数据类型 | 来源 | 实时性 | +|---------|------|--------| +| LiteLLM 密钥 | PostgreSQL 数据库 | 静态(创建时存储) | +| Agent 基本信息(名称、模板、CPU/内存) | PostgreSQL 数据库 | 静态(创建时存储) | +| Agent 状态(status, healthStatus) | AKS 集群(通过 Agent Manager) | **实时查询** | +| Agent 网络信息(podIp, accessUrl) | AKS 集群(通过 Agent Manager) | **实时查询** | + +### 调用链路 + +``` +前端 → mcp-server → Agent Manager → AKS (Kubernetes API) + ↓ + 查询 Pod/Service 真实状态 +``` + +--- + +## ⚠️ 注意事项 + +1. **密钥安全**:返回的 `apiKey` 是完整的解密密钥,请妥善保管,不要泄露 +2. **实时性**:Agent 的 `podIp`、`status` 等信息是实时从 AKS 查询的,可能有轻微延迟 +3. **服务可用性**:如果 Agent Manager 服务不可用,Agent 的实时信息将显示为 `null` 或 `unknown` +4. **Pod IP 变化**:Pod 重启后 IP 地址会改变,建议使用 `accessUrl`(Service DNS)访问 + diff --git a/config/prometheus.yml b/config/prometheus.yml index 7fcc4f4..88bebd5 100644 --- a/config/prometheus.yml +++ b/config/prometheus.yml @@ -38,18 +38,6 @@ scrape_configs: scrape_interval: 10s scrape_timeout: 5s - # LiteLLM Gateway (如果有metrics端点) - - job_name: 'litellm-gateway' - static_configs: - - targets: ['litellm-gateway:4000'] - labels: - service: 'litellm-gateway' - component: 'gateway' - metrics_path: '/metrics' - scrape_interval: 10s - scrape_timeout: 5s - # 如果端点不存在,Prometheus会记录错误但不会影响其他服务 - # PostgreSQL (需要postgres_exporter,当前未部署) # - job_name: 'postgres' # static_configs: diff --git a/docker-compose.yml b/docker-compose.yml index 3628420..f439710 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,28 +20,6 @@ services: - taiji-network restart: unless-stopped - # LiteLLM网关服务 - litellm-gateway: - build: - context: ./services/model-gateway - dockerfile: Dockerfile - container_name: taiji-litellm-gateway - ports: - - "4000:4000" - environment: - - LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY} - - DATABASE_URL=${DATABASE_URL} - - REDIS_URL=${REDIS_URL} - # OpenRouter 配置 - - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} - - OPENROUTER_BASE_URL=${OPENROUTER_BASE_URL} - volumes: - - ./services/model-gateway/config:/app/config - - ./logs:/app/logs - networks: - - taiji-network - restart: unless-stopped - # 数据接入服务 (Python) data-ingestion: build: @@ -101,7 +79,6 @@ services: - ./logs:/app/logs depends_on: - nats - - litellm-gateway networks: - taiji-network restart: unless-stopped diff --git a/services/mcp-server/app/routes/user.py b/services/mcp-server/app/routes/user.py index 4497e22..9a9df11 100644 --- a/services/mcp-server/app/routes/user.py +++ b/services/mcp-server/app/routes/user.py @@ -3820,3 +3820,162 @@ async def update_user_profile( "company": getattr(user, "company", None), } ) + + +# ============= 用户资源信息综合查询 ============= + +@router.get("/resources/info", response_model=SuccessResponse) +async def get_user_resources_info( + principal: dict = Depends(require_auth), + db: AsyncSession = Depends(get_db) +): + """ + 获取用户的资源信息综合查询 + + 返回: + 1. LiteLLM 密钥列表(解密后的完整密钥) + 2. 已部署的平台 Agent 列表(包含 IP 地址和访问信息) + 3. 已部署的自定义 Agent 列表(包含 IP 地址和访问信息) + + 注意: + - LiteLLM 密钥用于调用 AI 模型(OpenAI 兼容格式) + - Agent 信息包含实时状态和访问地址 + """ + from app.litellm_client import get_litellm_client + from config import settings + + user_id = principal.get("user_id") + + # ========== 1. 获取 LiteLLM 密钥 ========== + litellm_keys = [] + try: + # 查询用户的模型密钥 + result = await db.execute( + select(TenantModelKey).where( + and_( + TenantModelKey.tenant_id == user_id, + TenantModelKey.status == "active" + ) + ) + ) + tenant_keys = result.scalars().all() + + litellm_client = get_litellm_client() + + for key in tenant_keys: + try: + # 解密密钥 + decrypted_key = litellm_client.decrypt_key(key.litellm_key_hash) + litellm_keys.append({ + "modelName": key.model_name, + "apiKey": decrypted_key, + "apiBase": settings.litellm_url, + "rpmLimit": key.rpm_limit, + "tpmLimit": key.tpm_limit, + "maxBudget": float(key.max_budget) if key.max_budget else None, + "budgetDuration": key.budget_duration, + "status": key.status, + "createdAt": key.created_at.isoformat() if key.created_at else None, + }) + except Exception as e: + logger.warning(f"解密 LiteLLM 密钥失败: {e}") + litellm_keys.append({ + "modelName": key.model_name, + "apiKey": None, + "apiBase": settings.litellm_url, + "error": "密钥解密失败", + "status": key.status, + }) + except Exception as e: + logger.error(f"查询 LiteLLM 密钥失败: {e}") + + # ========== 2. 获取已部署的 Agent ========== + platform_agents = [] + custom_agents = [] + + # 查询用户的活跃 Agent(未停止的) + result = await db.execute( + select(AgentBillingRecord).where( + and_( + AgentBillingRecord.user_id == user_id, + AgentBillingRecord.end_time == None # 正在运行 + ) + ) + ) + billing_records = result.scalars().all() + + # 尝试获取 Agent Manager 客户端 + agent_manager_available = False + client = None + try: + from app.agent_manager_client import get_agent_manager_client, AgentManagerError + client = get_agent_manager_client() + agent_manager_available = True + except Exception as e: + logger.warning(f"Agent Manager 客户端不可用: {e}") + + for record in billing_records: + agent_info = { + "name": record.agent_name, + "template": record.agent_type, + "templateName": record.template_name, + "status": "unknown", + "healthStatus": "unknown", + "podIp": None, + "accessUrl": None, + "servicePort": None, + "namespace": "ai-agents", + "cpu": record.cpu_used, + "memory": record.memory_used, + "replicas": record.replicas, + "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, + } + + # 从 Agent Manager 获取详细状态 + if agent_manager_available and client: + try: + agent_status = await client.get_agent_status(record.agent_name) + agent_info["status"] = agent_status.status + agent_info["healthStatus"] = agent_status.health_status + agent_info["podIp"] = agent_status.pod_ip + agent_info["accessUrl"] = agent_status.access_url + agent_info["servicePort"] = agent_status.service_port + agent_info["namespace"] = agent_status.namespace + agent_info["hostIp"] = agent_status.host_ip + agent_info["nodeName"] = agent_status.node_name + + # 端点信息 + if agent_status.endpoints: + agent_info["endpoints"] = [ + { + "name": ep.name, + "port": ep.port, + "protocol": ep.protocol, + "targetPort": ep.target_port, + } + for ep in agent_status.endpoints + ] + except Exception as e: + logger.warning(f"获取 Agent {record.agent_name} 状态失败: {e}") + + # 分类存储 + if record.is_platform_agent: + platform_agents.append(agent_info) + else: + custom_agents.append(agent_info) + + return SuccessResponse( + data={ + "litellmKeys": litellm_keys, + "litellmApiBase": settings.litellm_url, + "platformAgents": platform_agents, + "customAgents": custom_agents, + "summary": { + "totalLitellmKeys": len(litellm_keys), + "totalPlatformAgents": len(platform_agents), + "totalCustomAgents": len(custom_agents), + } + }, + message="用户资源信息获取成功" + )