diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..747b88e --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,673 @@ +# AI Agent Manager API 文档 + +## 📋 目录 + +- [概述](#概述) +- [快速开始](#快速开始) +- [核心概念](#核心概念) +- [API 端点](#api-端点) +- [数据模型](#数据模型) +- [使用示例](#使用示例) +- [错误处理](#错误处理) +- [最佳实践](#最佳实践) + +--- + +## 概述 + +AI Agent Manager 是一个基于 Kubernetes 的 AI Agent 生命周期管理服务,提供完整的 Agent 创建、部署、监控和删除功能。 + +### 核心特性 + +- ✅ **独立命名空间**: 每个 Agent 部署在独立的 Kubernetes 命名空间中 +- ✅ **自动外网访问**: 自动创建 LoadBalancer Service 和 Azure DNS 记录 +- ✅ **多框架支持**: 支持 MCP、A2A、API 三种框架类型 +- ✅ **资源监控**: 实时监控 Agent 资源使用情况 +- ✅ **模板管理**: 预定义的 Agent 模板,快速部署 + +### 技术栈 + +- **Web 框架**: FastAPI +- **容器编排**: Kubernetes (AKS) +- **DNS 服务**: Azure DNS +- **监控**: Kubernetes Metrics Server + +### 服务信息 + +- **版本**: v1.0.0 +- **默认端口**: 8000 +- **默认命名空间**: ai-agents + +--- + +## 快速开始 + +### 验证服务 + +```bash +curl http://localhost:8000/ +``` + +响应: +```json +{ + "service": "AI Agent Manager", + "status": "running", + "namespace": "ai-agents" +} +``` + +--- + +## 核心概念 + +### 1. Agent + +Agent 是运行在 Kubernetes 中的 AI 服务实例,每个 Agent: +- 运行在独立的命名空间中 +- 拥有唯一的外网访问地址 +- 支持自动扩缩容 +- 可以实时监控资源使用 + +### 2. Template(模板) + +模板定义了 Agent 的类型和配置,包括: +- 容器镜像 +- 环境变量要求 +- 资源配置 +- 框架类型 + +### 3. Framework(框架) + +支持三种框架类型: +- **MCP**: Model Context Protocol +- **A2A**: Agent-to-Agent +- **API**: REST API(默认) + +### 4. Namespace(命名空间) + +每个 Agent 创建时自动生成独立的 Kubernetes 命名空间,格式:`agent-{agent-name}` + +--- + +## API 端点 + +### 基础信息 + +#### GET / + +获取服务状态 + +**响应**: +```json +{ + "service": "AI Agent Manager", + "status": "running", + "namespace": "ai-agents" +} +``` + +--- + +### Agent 管理 + +#### POST /agents + +创建新的 Agent + +**请求体**: +```json +{ + "name": "my-agent", + "template": "jina_search_agent", + "framework": "API", + "config": { + "user_id": "user123" + }, + "env": { + "JINA_API_KEY": "your-api-key" + } +} +``` + +**参数说明**: + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| name | string | ✅ | Agent 名称(1-63字符,小写字母、数字、连字符) | +| template | string | ✅ | 模板类型(见模板列表) | +| framework | string | ❌ | 框架类型:MCP、A2A、API(默认:API) | +| config | object | ❌ | 配置信息(如 user_id) | +| env | object | ❌ | 环境变量 | +| namespace | string | ❌ | 自定义命名空间(不推荐) | + +**响应**: +```json +{ + "name": "my-agent", + "namespace": "agent-my-agent", + "framework": "API", + "status": "Pending", + "template": "jina_search_agent", + "service_port": 8080, + "access_info": { + "external_ip": "135.171.210.24", + "ip_url": "http://135.171.210.24:80", + "domain": "my-agent.taijiagnet.com", + "domain_url": "http://my-agent.taijiagnet.com", + "recommended": "http://my-agent.taijiagnet.com" + }, + "pod_id": "7160fcd9-fbbe-48a1-9675-2b111cc36bc8", + "pod_ip": "10.244.3.80", + "host_ip": "10.224.0.7", + "node_name": "aks-node-001", + "owner_info": { + "user_id": "user123", + "agent_name": "my-agent", + "namespace": "agent-my-agent", + "framework": "API", + "labels": { + "app": "my-agent", + "framework": "api", + "managed-by": "agent-manager", + "template": "jina_search_agent" + } + } +} +``` + +**状态码**: +- `200`: 创建成功 +- `400`: 参数错误(无效的模板或框架类型) +- `500`: 服务器错误 + +--- + +#### GET /agents + +列出所有 Agent + +**查询参数**: +- `template` (可选): 按模板类型过滤 + +**示例**: +```bash +# 列出所有 Agent +curl http://localhost:8000/agents + +# 按模板过滤 +curl http://localhost:8000/agents?template=jina_search_agent +``` + +**响应**: +```json +{ + "agents": [ + { + "name": "my-agent", + "status": "Running", + "template": "jina_search_agent", + "created_at": "2026-01-14T10:00:00+00:00", + "pod_ip": "10.244.3.80" + } + ], + "count": 1 +} +``` + +--- + +#### GET /agents/{agent_name}/status + +获取 Agent 详细状态 + +**路径参数**: +- `agent_name`: Agent 名称 + +**响应**: +```json +{ + "name": "my-agent", + "namespace": "agent-my-agent", + "status": "Running", + "health_status": "healthy", + "template": "jina_search_agent", + "created_at": "2026-01-14T10:00:00+00:00", + "node": "aks-node-001", + "pod_ip": "10.244.3.80", + "containers": [ + { + "name": "my-agent", + "ready": true, + "restart_count": 0, + "state": "running", + "started_at": "2026-01-14T10:00:05+00:00" + } + ], + "resources": { + "requests": { + "cpu": "100m", + "memory": "128Mi" + }, + "limits": { + "cpu": "500m", + "memory": "512Mi" + }, + "usage": { + "cpu": "50m", + "memory": "200Mi", + "available": true + } + }, + "service_port": 8080, + "access_url": "http://10.244.3.80:8080", + "endpoints": { + "root": "http://10.244.3.80:8080/", + "health": "http://10.244.3.80:8080/health" + }, + "conditions": [ + { + "type": "Ready", + "status": "True", + "reason": "PodReady" + } + ] +} +``` + +**健康状态**: +- `healthy`: 容器运行正常 +- `unhealthy`: 容器未就绪或终止 +- `degraded`: 重启次数过多 + +--- + +#### GET /agents/{agent_name}/metrics + +获取 Agent 资源使用情况 + +**响应**: +```json +{ + "name": "my-agent", + "namespace": "agent-my-agent", + "requests": { + "cpu": "100m", + "memory": "128Mi" + }, + "limits": { + "cpu": "500m", + "memory": "512Mi" + }, + "usage": { + "cpu": "45m", + "memory": "256Mi" + }, + "timestamp": "2026-01-14T10:30:00+00:00", + "metrics_available": true +} +``` + +**注意**: 需要 Kubernetes Metrics Server 支持 + +--- + +#### DELETE /agents/{agent_name} + +删除 Agent + +**路径参数**: +- `agent_name`: Agent 名称 + +**响应**: +```json +{ + "status": "success", + "message": "Agent my-agent 的命名空间 agent-my-agent 及所有相关资源已删除", + "namespace": "agent-my-agent" +} +``` + +**删除内容**: +- ✅ Kubernetes 命名空间 +- ✅ Pod +- ✅ LoadBalancer Service +- ✅ Azure DNS 记录(如果存在) + +**状态码**: +- `200`: 删除成功 +- `404`: Agent 不存在 +- `500`: 服务器错误 + +--- + +### 模板管理 + +#### GET /templates + +列出所有可用模板 + +**响应**: +```json +{ + "templates": [ + { + "template": "jina_search_agent", + "port": 8080, + "env_info": { + "required": { + "JINA_API_KEY": "Jina API密钥" + }, + "optional": { + "SERVICE_PORT": "HTTP服务端口,默认8080" + } + } + } + ], + "count": 10 +} +``` + +--- + +#### GET /templates/platform + +列出平台模板 + +**响应**: 与 `/templates` 类似,但只包含平台预定义模板 + +**平台模板列表**: +- `echo_agent` +- `chat_agent` +- `code_agent` +- `search_agent` +- `jina_search_agent` +- `azure_blob_agent` +- `azure_blob_agent_mcp` +- `azure_blob_agent_a2a` + +--- + +#### GET /templates/custom + +列出自定义模板 + +**自定义模板列表**: +- `mysql_agent` +- `postgresql_agent` + +--- + +#### GET /templates/{template_name} + +获取指定模板详情 + +**路径参数**: +- `template_name`: 模板名称 + +**响应**: +```json +{ + "template": "jina_search_agent", + "port": 8080, + "env_info": { + "required": { + "JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取" + }, + "optional": { + "SERVICE_PORT": "HTTP服务端口,默认8080", + "SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0" + } + } +} +``` + +--- + +## 数据模型 + +### CreateAgentRequest + +创建 Agent 请求模型 + +```python +{ + "name": str, # Agent名称(必需,1-63字符) + "template": str, # 模板类型(必需) + "framework": str, # 框架类型(可选,默认API) + "config": dict, # 配置信息(可选) + "env": dict, # 环境变量(可选) + "namespace": str # 命名空间(可选) +} +``` + +### AgentResponse + +Agent 响应模型 + +```python +{ + "name": str, # Agent名称 + "namespace": str, # 命名空间 + "framework": str, # 框架类型 + "status": str, # 状态 + "template": str, # 模板类型 + "service_port": int, # 服务端口 + "access_info": dict, # 访问信息 + "pod_id": str, # Pod ID + "pod_ip": str, # Pod IP + "host_ip": str, # 宿主机IP + "node_name": str, # 节点名称 + "owner_info": dict # 所有者信息 +} +``` + +--- + +## 使用示例 + +### 示例 1: 创建 Jina 搜索 Agent + +```bash +curl -X POST http://localhost:8000/agents \ + -H "Content-Type: application/json" \ + -d '{ + "name": "search-bot", + "template": "jina_search_agent", + "framework": "API", + "config": { + "user_id": "alice" + }, + "env": { + "JINA_API_KEY": "jina_xxx" + } + }' +``` + +### 示例 2: 创建 MCP 框架的 Azure Blob Agent + +```bash +curl -X POST http://localhost:8000/agents \ + -H "Content-Type: application/json" \ + -d '{ + "name": "blob-mcp", + "template": "azure_blob_agent_mcp", + "framework": "MCP", + "config": { + "user_id": "bob" + }, + "env": { + "MODEL_PROVIDER": "openai", + "MODEL_NAME": "gpt-4", + "MODEL_API_KEY": "sk-xxx", + "AZURE_STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=https;..." + } + }' +``` + +### 示例 3: 查询 Agent 状态 + +```bash +curl http://localhost:8000/agents/search-bot/status +``` + +### 示例 4: 监控资源使用 + +```bash +curl http://localhost:8000/agents/search-bot/metrics +``` + +### 示例 5: 列出所有 Agent + +```bash +# 所有 Agent +curl http://localhost:8000/agents + +# 只列出 Jina 搜索 Agent +curl http://localhost:8000/agents?template=jina_search_agent +``` + +### 示例 6: 删除 Agent + +```bash +curl -X DELETE http://localhost:8000/agents/search-bot +``` + +### 示例 7: Python 客户端 + +```python +import requests + +# 基础 URL +BASE_URL = "http://localhost:8000" + +# 创建 Agent +def create_agent(name, template, framework="API", env=None): + response = requests.post( + f"{BASE_URL}/agents", + json={ + "name": name, + "template": template, + "framework": framework, + "config": {"user_id": "demo"}, + "env": env or {} + } + ) + return response.json() + +# 获取状态 +def get_agent_status(name): + response = requests.get(f"{BASE_URL}/agents/{name}/status") + return response.json() + +# 删除 Agent +def delete_agent(name): + response = requests.delete(f"{BASE_URL}/agents/{name}") + return response.json() + +# 使用示例 +agent = create_agent( + name="my-search", + template="jina_search_agent", + env={"JINA_API_KEY": "jina_xxx"} +) + +print(f"Agent 创建成功!") +print(f"访问地址: {agent['access_info']['recommended']}") + +# 查询状态 +status = get_agent_status("my-search") +print(f"状态: {status['status']}") +``` + +--- + +## 错误处理 + +### 错误响应格式 + +```json +{ + "detail": "错误描述信息" +} +``` + +### 常见错误 + +#### 400 Bad Request + +**原因**: +- 无效的模板类型 +- 无效的框架类型 +- Agent 名称不符合规范 + +**示例**: +```json +{ + "detail": "无效的模板类型。支持的模板: echo_agent, chat_agent, ..." +} +``` + +#### 404 Not Found + +**原因**: +- Agent 不存在 +- 模板不存在 + +**示例**: +```json +{ + "detail": "Pod my-agent 不存在" +} +``` + +#### 500 Internal Server Error + +**原因**: +- Kubernetes API 错误 +- DNS 配置错误 +- 网络问题 + +**处理建议**: +1. 检查 Kubernetes 集群状态 +2. 验证 kubeconfig 配置 +3. 检查网络连接 +4. 查看服务日志 + +--- + + +--- + +## 附录 + +### A. 支持的模板列表 + +| 模板名称 | 类型 | 用途 | 必需环境变量 | +|---------|------|------|-------------| +| jina_search_agent | Platform | Jina AI 搜索 | JINA_API_KEY | +| azure_blob_agent | Platform | Azure Blob 存储 | LITELLM_API_BASE, LITELLM_MODEL, LITELLM_API_KEY | +| azure_blob_agent_mcp | Platform | Azure Blob MCP | MODEL_PROVIDER, MODEL_NAME, MODEL_API_KEY | +| azure_blob_agent_a2a | Platform | Azure Blob A2A | MODEL_PROVIDER, MODEL_NAME, MODEL_API_KEY, AGENT_ID, AGENT_ROLE | +| mysql_agent | Custom | MySQL 数据库 | MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, OPENAI_API_KEY | +| postgresql_agent | Custom | PostgreSQL 数据库 | POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DATABASE, OPENAI_API_KEY | +| search_agent | Platform | 通用搜索 | - | +| echo_agent | Platform | Echo 测试 | - | +| chat_agent | Platform | 聊天 | - | +| code_agent | Platform | 代码生成 | - | + +### B. 框架类型说明 + +| 框架 | 全称 | 用途 | +|------|------|------| +| MCP | Model Context Protocol | 基于协议的模型上下文交互 | +| A2A | Agent-to-Agent | Agent 间通信 | +| API | REST API | 标准 HTTP API 接口 | + + +## 联系支持 + +如有问题或建议,请联系开发团队或查看项目文档。 + +**文档版本**: v1.0.0 +**最后更新**: 2026-01-14 diff --git a/Docs/API_DOCUMENTATION.md b/Docs/API_DOCUMENTATION.md deleted file mode 100644 index b815778..0000000 --- a/Docs/API_DOCUMENTATION.md +++ /dev/null @@ -1,1724 +0,0 @@ -# Agent Manager API 接口文档 - -## 基础信息 - -- **Base URL**: `http://localhost:8000` -- **版本**: v1.0.0 -- **协议**: HTTP/HTTPS -- **数据格式**: JSON -- **默认命名空间**: `ai-agents` -- **服务端口**: `8000` - ---- - -## 目录 - -1. [Agent 管理](#agent-管理) -2. [模板查询](#模板查询) -3. [状态监控](#状态监控) -4. [资源管理](#资源管理) - ---- - -## Agent 管理 - -### 1. 创建 Agent - -创建一个新的 AI Agent 实例。 - -**请求** - -```http -POST /agents -Content-Type: application/json -``` - -**请求参数** - -```json -{ - "name": "agent-name", // 必填,Agent名称,必须唯一,1-63字符 - "template": "echo_agent", // 必填,模板类型(见下方支持的模板列表) - "config": { // 可选,配置信息(默认为空对象) - "user_id": "user-001", // 推荐,用户标识,用于多租户管理(默认为"default") - "cpu_request": "100m", // 可选,CPU请求量 - "cpu_limit": "500m", // 可选,CPU限制 - "memory_request": "128Mi", // 可选,内存请求量 - "memory_limit": "512Mi" // 可选,内存限制 - }, - "env": { // 可选,环境变量(会被合并到config.env中) - "KEY": "value" - }, - "namespace": "custom-namespace" // 可选,Kubernetes命名空间,默认使用环境变量NAMESPACE的值或"ai-agents" -} -``` - -**命名空间说明** - -✅ **支持自定义命名空间**:可以在请求参数中通过 `namespace` 字段指定目标命名空间。 - -- **优先级**:请求参数 `namespace` > 环境变量 `NAMESPACE` > 默认值 `"ai-agents"` -- **示例**: - ```bash - # 使用默认命名空间(ai-agents) - curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{"name": "test-agent", "template": "echo_agent"}' - - # 指定自定义命名空间 - curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "test-agent", - "template": "echo_agent", - "namespace": "my-namespace" - }' - ``` -- **注意**:目标命名空间必须已存在于 Kubernetes 集群中 -``` - -**支持的模板类型** - -| 模板 | 说明 | 类型 | 框架 | -|------|------|------|------| -| `echo_agent` | Echo 测试服务 | 平台 | - | -| `chat_agent` | 聊天服务 | 平台 | - | -| `code_agent` | 代码执行服务 | 平台 | - | -| `search_agent` | 搜索服务 | 平台 | - | -| `jina_search_agent` | Jina 搜索服务 | 平台 | - | -| `mysql_agent` | MySQL 客户端 | 自定义 | - | -| `postgresql_agent` | PostgreSQL 客户端 | 自定义 | - | -| `azure_blob_agent` | Azure Blob Storage 客户端 (LangChain) | 自定义 | LangChain | -| `azure_blob_agent_mcp` | Azure Blob Storage 客户端 (MCP) | 自定义 | MCP | -| `azure_blob_agent_a2a` | Azure Blob Storage 客户端 (A2A) | 自定义 | A2A | - -**请求处理逻辑** - -创建 Agent 时,系统会执行以下步骤: - -1. **确定命名空间**:优先使用请求中的 `namespace` 参数,否则使用环境变量 `NAMESPACE` 或默认值 `"ai-agents"` -2. **验证模板**:检查 `template` 是否在支持的模板列表中 -3. **合并环境变量**:如果提供了 `env` 参数,会将其合并到 `config.env` 中 -4. **提取用户标识**:从 `config.user_id` 获取用户ID,如果未提供则使用 `"default"` -5. **添加标签**:自动为 Pod 添加以下标签: - - `user-id`: 用户标识 - - `managed-by`: "agent-manager" - - `template`: 模板类型 - - `app`: "ai-agent" -6. **创建 Pod**:在指定的命名空间中调用 Kubernetes API 创建 Pod(如果命名空间与默认不同,会创建临时 K8sManager 实例) -7. **获取详细信息**:等待1秒后从目标命名空间获取 Pod 的完整信息(pod_id, pod_ip, host_ip, node_name 等) -8. **返回响应**:返回包含所有详细信息的响应,包括实际使用的命名空间 - -**Agent 框架说明** - -从 v1.1.0 开始,Agent Manager 支持多种 AI Agent 框架: - -| 框架 | 说明 | 适用场景 | -|------|------|---------| -| **LangChain** | 使用 LangChain + LiteLLM | 复杂推理任务、多步骤处理流程 | -| **MCP** | Model Context Protocol | 标准化工具调用、轻量级集成 | -| **A2A** | Agent-to-Agent | 多 Agent 协作、分布式任务处理 | - - - -**响应** - -```json -{ - "name": "agent-name", - "namespace": "ai-agents", - "status": "Pending", - "created_at": "2026-01-12T07:35:00+00:00", - "template": "echo_agent", - "service_port": null, - "access_info": null, - "pod_id": "111175ce-8118-484d-9b3e-009733644acf", - "pod_ip": "10.244.2.24", - "host_ip": "10.224.0.5", - "node_name": "aks-node-123", - "owner_info": { - "user_id": "user-001", - "agent_name": "agent-name", - "namespace": "ai-agents", - "labels": { - "app": "ai-agent", - "managed-by": "agent-manager", - "template": "echo_agent", - "user-id": "user-001" - } - } -} -``` - -**响应字段说明** - -| 字段 | 类型 | 说明 | -|------|------|------| -| name | string | Agent 名称 | -| namespace | string | Kubernetes 命名空间 | -| status | string | Pod 状态(Pending/Running/Failed 等) | -| created_at | string | 创建时间(ISO 8601 格式) | -| template | string | 使用的模板类型 | -| service_port | int \| null | 服务端口(如果模板定义了端口) | -| access_info | object \| null | 访问信息 | -| pod_id | string | Pod 的唯一标识符(UUID) | -| pod_ip | string | Pod 的内部 IP 地址 | -| host_ip | string | Pod 所在节点的 IP 地址 | -| node_name | string | Pod 所在的 Kubernetes 节点名称 | -| owner_info | object | 所有者信息和标签 | -| owner_info.user_id | string | 用户标识(来自 config.user_id,默认"default") | -| owner_info.agent_name | string | Agent 名称 | -| owner_info.namespace | string | 命名空间 | -| owner_info.labels | object | Pod 的所有标签(包括自动添加的) - -**状态码** - -- `200` - 创建成功 -- `400` - 请求参数错误(无效的模板类型等) -- `500` - 服务器内部错误(Pod 创建失败等) - -**注意事项** - -1. **命名空间支持**:现在支持通过 `namespace` 参数指定目标命名空间,命名空间必须已存在 -2. **命名空间优先级**:请求参数 `namespace` > 环境变量 `NAMESPACE` > 默认值 `"ai-agents"` -3. **user_id 处理**:如果 `config.user_id` 未提供,系统会自动使用 `"default"` 作为默认值 -4. **标签自动添加**:系统会自动为 Pod 添加 `user-id`、`managed-by`、`template` 和 `app` 标签 -5. **环境变量合并**:`env` 参数会被合并到 `config.env` 中传递给容器 -6. **异步创建**:Pod 创建是异步的,初始状态通常为 `Pending`,需要等待调度和拉取镜像 -7. **详细信息延迟**:系统会等待 1 秒后获取 Pod 详细信息,如果获取失败会记录警告但不影响创建流程 -8. **名称唯一性**:Agent 名称在命名空间内必须唯一,重复创建会导致错误 - -**示例** - -基础示例 - Echo Agent: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "alice-echo", - "template": "echo_agent", - "config": { - "user_id": "alice" - } - }' -``` - -使用默认 user_id: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "test-echo", - "template": "echo_agent" - }' -# user_id 将自动设置为 "default" -# namespace 将使用默认值 "ai-agents" -``` - -指定自定义命名空间: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "production-agent", - "template": "echo_agent", - "namespace": "production", - "config": { - "user_id": "alice" - } - }' -# 将在 "production" 命名空间中创建 Agent -``` - -Azure Blob Agent (LangChain 版本) - 提供连接字符串: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "my-azure-blob-agent", - "template": "azure_blob_agent", - "config": { - "user_id": "alice" - }, - "env": { - "LITELLM_API_BASE": "http://litellm-service:4000", - "LITELLM_MODEL": "gpt-4", - "LITELLM_API_KEY": "sk-your-api-key", - "AZURE_STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=yourkey;EndpointSuffix=core.windows.net", - "SERVICE_PORT": "8080" - } - }' -``` - -**Azure Blob Agent (MCP 版本) - 标准化工具调用**: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "my-blob-mcp", - "template": "azure_blob_agent_mcp", - "config": { - "user_id": "alice", - "tenant_id": "tenant-001", - "namespace": "ai-agents", - "agent_framework": "mcp", - "model_provider": "openai", - "model_name": "gpt-4", - "model_api_key": "sk-your-api-key", - "model_endpoint": "https://api.openai.com/v1", - "storage_connection_string": "DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=yourkey;EndpointSuffix=core.windows.net", - "tools_config": { - "max_iterations": 5, - "enabled_tools": ["list_containers", "list_blobs", "search_blobs"] - }, - "cpu_request": "100m", - "memory_request": "256Mi" - } - }' -``` - -**Azure Blob Agent (A2A 版本) - Agent 间协作**: -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "my-blob-a2a", - "template": "azure_blob_agent_a2a", - "config": { - "user_id": "alice", - "tenant_id": "tenant-001", - "namespace": "ai-agents", - "agent_framework": "a2a", - "model_provider": "azure-openai", - "model_name": "gpt-4", - "model_api_key": "your-azure-openai-key", - "model_endpoint": "https://your-resource.openai.azure.com", - "storage_connection_string": "DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=yourkey;EndpointSuffix=core.windows.net", - "cpu_request": "100m", - "memory_request": "256Mi" - }, - "env": { - "AGENT_ID": "blob-agent-001", - "AGENT_ROLE": "storage_manager", - "AGENT_CAPABILITIES": "[\"blob_storage\", \"file_operations\"]" - } - }' -``` - -**测试部署后的 Agent** - -**LangChain 版本测试:** - -获取 Pod IP 并测试: -```bash -# 1. 检查 Agent 状态 -curl -s http://localhost:8000/agents/my-azure-blob-agent/status | jq '{status, health_status, pod_ip, access_url}' - -# 2. 获取 Pod IP -POD_IP=$(curl -s http://localhost:8000/agents/my-azure-blob-agent/status | jq -r '.pod_ip') -echo "Pod IP: $POD_IP" - -# 3. 测试健康检查 -curl http://$POD_IP:8080/health - -# 4. 查看 Agent 信息 -curl http://$POD_IP:8080/ | jq . - -# 5. 如果启动时未提供连接字符串,可以动态连接 -curl -X POST http://$POD_IP:8080/connect \ - -H "Content-Type: application/json" \ - -d '{ - "connection_string": "DefaultEndpointsProtocol=https;AccountName=youraccount;AccountKey=yourkey;EndpointSuffix=core.windows.net" - }' - -# 6. 检查连接状态 -curl http://$POD_IP:8080/status - -# 7. 执行自然语言查询 - 列出所有容器 -curl -X POST http://$POD_IP:8080/query \ - -H "Content-Type: application/json" \ - -d '{"query": "列出所有容器"}' | jq . - -# 8. 查看指定容器的文件 -curl -X POST http://$POD_IP:8080/query \ - -H "Content-Type: application/json" \ - -d '{"query": "显示 mycontainer 容器中的所有文件"}' | jq . - -# 9. 搜索文件 -curl -X POST http://$POD_IP:8080/query \ - -H "Content-Type: application/json" \ - -d '{"query": "搜索包含 report 的文件"}' | jq . - -# 10. 获取存储统计 -curl -X POST http://$POD_IP:8080/query \ - -H "Content-Type: application/json" \ - -d '{"query": "存储统计"}' | jq . -``` - -**MCP 版本测试:** - -```bash -# 1. 获取 Pod IP -POD_IP=$(curl -s http://localhost:8000/agents/my-blob-mcp/status | jq -r '.pod_ip') - -# 2. 测试健康检查 -curl http://$POD_IP:8080/health | jq . - -# 3. 查看 Agent 信息(包含框架类型) -curl http://$POD_IP:8080/ | jq . - -# 4. 列出所有可用的 MCP 工具 -curl http://$POD_IP:8080/mcp/tools | jq . - -# 5. 调用 MCP 工具 - 列出所有容器 -curl -X POST http://$POD_IP:8080/mcp/call \ - -H "Content-Type: application/json" \ - -d '{ - "tool_name": "list_containers", - "parameters": {} - }' | jq . - -# 6. 调用 MCP 工具 - 列出容器中的文件 -curl -X POST http://$POD_IP:8080/mcp/call \ - -H "Content-Type: application/json" \ - -d '{ - "tool_name": "list_blobs", - "parameters": { - "container_name": "mycontainer" - } - }' | jq . - -# 7. 调用 MCP 工具 - 获取文件信息 -curl -X POST http://$POD_IP:8080/mcp/call \ - -H "Content-Type: application/json" \ - -d '{ - "tool_name": "get_blob_info", - "parameters": { - "container_name": "mycontainer", - "blob_name": "myfile.txt" - } - }' | jq . - -# 8. 调用 MCP 工具 - 搜索文件 -curl -X POST http://$POD_IP:8080/mcp/call \ - -H "Content-Type: application/json" \ - -d '{ - "tool_name": "search_blobs", - "parameters": { - "container_name": "mycontainer", - "keyword": "report" - } - }' | jq . - -# 9. 调用 MCP 工具 - 获取存储统计 -curl -X POST http://$POD_IP:8080/mcp/call \ - -H "Content-Type: application/json" \ - -d '{ - "tool_name": "get_storage_stats", - "parameters": {} - }' | jq . - -# 10. 使用简化的查询接口(规则匹配) -curl -X POST http://$POD_IP:8080/query \ - -H "Content-Type: application/json" \ - -d '{"query": "列出所有容器"}' | jq . -``` - -**A2A 版本测试:** - -```bash -# 1. 获取 Pod IP -POD_IP=$(curl -s http://localhost:8000/agents/my-blob-a2a/status | jq -r '.pod_ip') - -# 2. 测试健康检查(包含 Agent 身份信息) -curl http://$POD_IP:8080/health | jq . - -# 3. 获取 Agent 能力 -curl http://$POD_IP:8080/a2a/capabilities | jq . - -# 4. 发送 A2A 消息 - 列出容器 -curl -X POST http://$POD_IP:8080/a2a/message \ - -H "Content-Type: application/json" \ - -d '{ - "message_id": "msg-001", - "from_agent": "external-caller", - "to_agent": "blob-agent-001", - "message_type": "request", - "action": "list_containers", - "parameters": {} - }' | jq . - -# 5. 发送 A2A 消息 - 列出文件 -curl -X POST http://$POD_IP:8080/a2a/message \ - -H "Content-Type: application/json" \ - -d '{ - "message_id": "msg-002", - "from_agent": "external-caller", - "to_agent": "blob-agent-001", - "message_type": "request", - "action": "list_blobs", - "parameters": { - "container_name": "mycontainer" - } - }' | jq . - -# 6. 发送 A2A 消息 - 获取统计 -curl -X POST http://$POD_IP:8080/a2a/message \ - -H "Content-Type: application/json" \ - -d '{ - "message_id": "msg-003", - "from_agent": "external-caller", - "to_agent": "blob-agent-001", - "message_type": "request", - "action": "get_stats", - "parameters": {} - }' | jq . - -# 7. 注册另一个 Agent(用于协作) -curl -X POST http://$POD_IP:8080/a2a/register \ - -H "Content-Type: application/json" \ - -d '{ - "agent_id": "analytics-agent", - "agent_role": "data_analyzer", - "capabilities": ["data_analysis", "visualization"], - "endpoint": "http://analytics-agent:8080" - }' | jq . - -# 8. 列出已注册的 Agent -curl http://$POD_IP:8080/a2a/agents | jq . - -# 9. 与其他 Agent 协作(需要先注册目标 Agent) -curl -X POST http://$POD_IP:8080/a2a/collaborate \ - -H "Content-Type: application/json" \ - -d '{ - "target_agent_id": "analytics-agent", - "action": "analyze_data", - "parameters": { - "data_source": "blob_storage" - } - }' | jq . -``` - -d '{"query": "统计存储使用情况"}' | jq . -``` - ---- - -### 2. 查询 Agent 列表 - -获取所有 Agent 的列表。 - -**请求** - -```http -GET /agents -``` - -**查询参数** - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| template | string | 否 | 按模板类型过滤 | - -**响应** - -```json -{ - "agents": [ - { - "name": "alice-echo", - "namespace": "ai-agents", - "status": "Running", - "template": "echo_agent", - "pod_ip": "10.244.2.24", - "labels": { - "user-id": "alice" - } - } - ], - "count": 1 -} -``` - -**示例** - -```bash -# 获取所有 Agents -curl http://localhost:8000/agents - -# 按模板过滤 -curl http://localhost:8000/agents?template=echo_agent -``` - ---- - -### 3. 获取 Agent 状态 - -获取指定 Agent 的详细状态信息,包括 Pod 状态、容器健康状态、资源配额等。 - -**请求** - -```http -GET /agents/{agent_name}/status -``` - -**路径参数** - -| 参数 | 类型 | 说明 | -|------|------|------| -| agent_name | string | Agent 名称 | - -**响应 - 健康状态** - -```json -{ - "name": "alice-echo", - "namespace": "ai-agents", - "status": "Running", - "health_status": "healthy", - "template": "echo_agent", - "created_at": "2026-01-05T07:35:00+00:00", - "node": "aks-node-123", - "pod_ip": "10.244.2.24", - "containers": [ - { - "name": "alice-echo", - "ready": true, - "restart_count": 0, - "state": "running", - "started_at": "2026-01-05T07:35:15+00:00" - } - ], - "resources": { - "requests": { - "cpu": "100m", - "memory": "128Mi" - }, - "limits": { - "cpu": "500m", - "memory": "512Mi" - }, - "usage": { - "cpu": "14502n", - "memory": "8704Ki", - "available": true - } - }, - "service_port": 8080, - "access_url": "http://10.244.2.24:8080", - "endpoints": { - "root": "http://10.244.2.24:8080/", - "health": "http://10.244.2.24:8080/health" - }, - "conditions": [ - { - "type": "Ready", - "status": "True", - "reason": null - }, - { - "type": "ContainersReady", - "status": "True", - "reason": null - } - ] -} -``` - -**响应 - 崩溃状态** - -```json -{ - "name": "my-mysql-agent", - "namespace": "ai-agents", - "status": "Waiting", - "health_status": "unhealthy", - "template": "mysql_agent", - "created_at": "2026-01-04T06:40:38+00:00", - "node": "aks-node-123", - "pod_ip": "10.244.1.53", - "containers": [ - { - "name": "my-mysql-agent", - "ready": false, - "restart_count": 599, - "state": "waiting", - "reason": "CrashLoopBackOff", - "message": "back-off 5m0s restarting failed container=my-mysql-agent pod=my-mysql-agent_ai-agents(...)" - } - ], - "resources": { - "requests": { - "cpu": "100m", - "memory": "128Mi" - }, - "limits": { - "cpu": "500m", - "memory": "512Mi" - }, - "usage": { - "cpu": null, - "memory": null, - "available": false, - "reason": "metrics-server未安装或Pod不存在" - } - }, - "conditions": [ - { - "type": "Ready", - "status": "False", - "reason": "ContainersNotReady" - }, - { - "type": "ContainersReady", - "status": "False", - "reason": "ContainersNotReady" - } - ] -} -``` - -**字段说明** - -| 字段 | 类型 | 说明 | -|------|------|------| -| name | string | Agent 名称 | -| namespace | string | 命名空间 | -| status | string | Pod 状态 (Running/Pending/Waiting/Terminated/Failed) | -| **health_status** | string | **健康状态** (healthy/unhealthy/degraded) | -| template | string | 使用的模板 | -| created_at | string | 创建时间 (ISO 8601) | -| node | string | 运行的节点 | -| pod_ip | string | Pod IP 地址 | -| **containers** | array | **容器详细状态** | -| resources | object | 资源配额和使用情况 | -| service_port | int \| null | 服务端口 | -| access_url | string \| null | 访问地址 | -| endpoints | object \| null | API 端点 | -| conditions | array | Pod 条件状态 | - -**健康状态说明** - -| 状态 | 说明 | -|------|------| -| `healthy` | 所有容器运行正常且就绪 | -| `unhealthy` | 容器崩溃、终止或未就绪 | -| `degraded` | 容器重启次数过多 (>5次) | - -**容器状态字段** - -| 字段 | 类型 | 说明 | -|------|------|------| -| name | string | 容器名称 | -| ready | boolean | 是否就绪 | -| restart_count | int | 重启次数 | -| state | string | 状态 (running/waiting/terminated) | -| reason | string \| null | 状态原因 (如 CrashLoopBackOff) | -| message | string \| null | 详细消息 | -| exit_code | int \| null | 退出码 (terminated 状态) | -| started_at | string \| null | 启动时间 (running 状态) | -| finished_at | string \| null | 结束时间 (terminated 状态) | - -**Pod 状态类型** - -| 状态 | 说明 | -|------|------| -| Running | Pod 正在运行 | -| Pending | Pod 等待调度 | -| Waiting | 容器等待启动 | -| Terminated | 容器已终止 | -| Failed | Pod 失败 | -| Succeeded | Pod 成功完成 | - -**状态码** - -- `200` - 成功 -- `404` - Agent 不存在 -- `500` - 服务器内部错误 - -**示例** - -```bash -# 查询健康的 Agent -curl http://localhost:8000/agents/alice-echo/status - -# 查询崩溃的 Agent -curl http://localhost:8000/agents/my-mysql-agent/status -``` - -**使用建议** - -1. **监控告警**:使用 `health_status` 字段而非 `status` 进行健康监控 -2. **故障排查**:检查 `containers` 数组获取容器崩溃原因和重启次数 -3. **自动化运维**:根据 `health_status` 自动触发重启或告警 -4. **日志分析**:结合 `restart_count` 和 `reason` 定位问题 - ---- - -### 4. 获取 Agent 资源使用情况 - -获取 Agent 的 CPU 和内存使用情况。 - -**请求** - -```http -GET /agents/{agent_name}/metrics -``` - -**功能说明** - -获取 Agent 的资源使用情况,包括: -- **requests/limits**: 资源配额(从 Pod spec 获取) -- **usage**: 实时资源使用情况(从 metrics-server 获取,需要集群安装 metrics-server) -- **timestamp**: metrics 数据的时间戳 - -**响应** - -```json -{ - "name": "alice-echo", - "namespace": "ai-agents", - "requests": { - "cpu": "100m", - "memory": "128Mi" - }, - "limits": { - "cpu": "500m", - "memory": "512Mi" - }, - "usage": { - "cpu": "14502n", - "memory": "8704Ki" - }, - "timestamp": "2026-01-06T05:01:04Z", - "metrics_available": null -} -``` - -**字段说明** - -| 字段 | 类型 | 说明 | -|------|------|------| -| name | string | Pod 名称 | -| namespace | string | 命名空间 | -| requests | object | 资源请求配额 | -| limits | object | 资源限制配额 | -| usage | object \| null | 实时资源使用(需要 metrics-server) | -| timestamp | string \| null | metrics 时间戳(ISO 8601 格式) | -| metrics_available | bool \| null | metrics-server 是否可用 | - -**CPU 单位说明** -- `n` (nanocores): 1 核 = 1,000,000,000n -- `m` (millicores): 1 核 = 1,000m -- 例如: `14502n` = 0.014502m ≈ 0.000014 核 - -**内存单位说明** -- `Ki` (Kibibytes): 1024 字节 -- `Mi` (Mebibytes): 1024 KiB -- 例如: `8704Ki` ≈ 8.5 MiB - -**示例** - -```bash -curl http://localhost:8000/agents/alice-echo/metrics -``` - -**注意事项** -- 如果集群未安装 metrics-server,`usage` 和 `timestamp` 将为 `null` -- metrics 数据由 Kubernetes metrics-server 提供,更新频率通常为 15-60 秒 -- `usage` 显示的是 Pod 的实际资源消耗,不是配额 - ---- - -### 5. 删除 Agent - -删除指定的 Agent。 - -**请求** - -```http -DELETE /agents/{agent_name} -``` - -**路径参数** - -| 参数 | 类型 | 说明 | -|------|------|------| -| agent_name | string | Agent 名称 | - -**响应** - -```json -{ - "status": "success", - "message": "Agent alice-echo 删除成功" -} -``` - -**状态码** - -- `200` - 删除成功 -- `404` - Agent 不存在 -- `500` - 服务器内部错误 - -**示例** - -```bash -curl -X DELETE http://localhost:8000/agents/alice-echo -``` - ---- - -## 模板查询 - -### 1. 获取所有模板 - -获取所有可用的 Agent 模板列表。 - -**请求** - -```http -GET /templates -``` - -**响应** - -```json -{ - "templates": [ - { - "template": "echo_agent", - "port": null, - "env_info": {} - }, - { - "template": "jina_search_agent", - "port": 8080, - "env_info": {} - }, - { - "template": "mysql_agent", - "port": null, - "env_info": { - "required": { - "MYSQL_HOST": "MySQL数据库主机地址", - "MYSQL_USER": "MySQL用户名", - "MYSQL_PASSWORD": "MySQL密码", - "MYSQL_DATABASE": "MySQL数据库名", - "OPENAI_API_KEY": "OpenAI API密钥" - }, - "optional": { - "MYSQL_PORT": "MySQL端口,默认3306" - } - } - } - ], - "count": 7 -} -``` - -**示例** - -```bash -curl http://localhost:8000/templates -``` - ---- - -### 2. 获取平台模板 - -获取平台提供的标准 Agent 模板列表。 - -**请求** - -```http -GET /templates/platform -``` - -**响应** - -```json -{ - "templates": [ - { - "template": "echo_agent", - "type": "platform", - "port": null, - "env_info": {} - }, - { - "template": "chat_agent", - "type": "platform", - "port": null, - "env_info": {} - }, - { - "template": "code_agent", - "type": "platform", - "port": null, - "env_info": {} - }, - { - "template": "search_agent", - "type": "platform", - "port": null, - "env_info": {} - }, - { - "template": "jina_search_agent", - "type": "platform", - "port": 8080, - "env_info": {} - } - ], - "count": 5, - "type": "platform" -} -``` - -**平台模板说明** - -- **echo_agent**: 简单的 Echo 服务,用于测试 -- **chat_agent**: 聊天对话服务 -- **code_agent**: 代码生成和执行服务 -- **search_agent**: 通用搜索服务 -- **jina_search_agent**: 基于 Jina 的向量搜索服务(端口: 8080) -- **azure_blob_agent**: Azure Blob Storage 客户端(LangChain 框架,端口: 8080) -- **azure_blob_agent_mcp**: Azure Blob Storage 客户端(MCP 框架,端口: 8080) -- **azure_blob_agent_a2a**: Azure Blob Storage 客户端(A2A 框架,端口: 8080) - -**示例** - -```bash -curl http://localhost:8000/templates/platform -``` - ---- - -### 3. 获取自定义模板 - -获取需要用户配置环境变量的自定义 Agent 模板列表。 - -**请求** - -```http -GET /templates/custom -``` - -**响应** - -```json -{ - "templates": [ - { - "template": "mysql_agent", - "type": "custom", - "port": null, - "env_info": { - "required": { - "MYSQL_HOST": "MySQL数据库主机地址", - "MYSQL_USER": "MySQL用户名", - "MYSQL_PASSWORD": "MySQL密码", - "MYSQL_DATABASE": "MySQL数据库名", - "OPENAI_API_KEY": "OpenAI API密钥" - }, - "optional": { - "MYSQL_PORT": "MySQL端口,默认3306" - } - } - }, - { - "template": "postgresql_agent", - "type": "custom", - "port": null, - "env_info": { - "required": { - "POSTGRES_HOST": "PostgreSQL数据库主机地址", - "POSTGRES_USER": "PostgreSQL用户名", - "POSTGRES_PASSWORD": "PostgreSQL密码", - "POSTGRES_DATABASE": "PostgreSQL数据库名", - "OPENAI_API_KEY": "OpenAI API密钥" - }, - "optional": { - "POSTGRES_PORT": "PostgreSQL端口,默认5432" - } - } - } - ], - "count": 2, - "type": "custom" -} -``` - -**自定义模板说明** - -自定义模板需要用户在创建时通过 `env` 参数提供必需的环境变量。 - -**示例:创建 MySQL Agent** - -```bash -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "my-mysql-agent", - "template": "mysql_agent", - "config": { - "user_id": "alice" - }, - "env": { - "MYSQL_HOST": "mysql.example.com", - "MYSQL_USER": "root", - "MYSQL_PASSWORD": "password", - "MYSQL_DATABASE": "mydb", - "OPENAI_API_KEY": "sk-..." - } - }' -``` - -**示例** - -```bash -curl http://localhost:8000/templates/custom -``` - ---- - -### 4. 获取指定模板详情 - -获取单个模板的详细信息。 - -**请求** - -```http -GET /templates/{template_name} -``` - -**路径参数** - -| 参数 | 类型 | 说明 | -|------|------|------| -| template_name | string | 模板名称 | - -**响应** - -```json -{ - "template": "mysql_agent", - "port": null, - "env_info": { - "required": { - "MYSQL_HOST": "MySQL数据库主机地址", - "MYSQL_USER": "MySQL用户名", - "MYSQL_PASSWORD": "MySQL密码", - "MYSQL_DATABASE": "MySQL数据库名", - "OPENAI_API_KEY": "OpenAI API密钥" - }, - "optional": { - "MYSQL_PORT": "MySQL端口,默认3306" - } - } -} -``` - -**状态码** - -- `200` - 成功 -- `404` - 模板不存在 - -**示例** - -```bash -curl http://localhost:8000/templates/mysql_agent -``` - ---- - -## 状态监控 - -### 健康检查 - -检查服务是否正常运行。 - -**请求** - -```http -GET / -``` - -**响应** - -```json -{ - "service": "AI Agent Manager", - "status": "running", - "namespace": "ai-agents" -} -``` - -**示例** - -```bash -curl http://localhost:8000/ -``` - ---- - -## 多租户管理 - -### 按用户查询 Agents - -使用 Kubernetes 标签选择器按用户 ID 查询 Agents。 - -**方法 1: 通过 kubectl** - -```bash -# 查询特定用户的所有 Agents -kubectl get pods -n ai-agents -l user-id=alice - -# 查看详细信息 -kubectl get pods -n ai-agents -l user-id=alice \ - -o custom-columns=NAME:.metadata.name,POD_ID:.metadata.uid,STATUS:.status.phase -``` - -**方法 2: 通过 API 查询后过滤** - -```bash -curl http://localhost:8000/agents | \ - jq '.agents[] | select(.labels["user-id"]=="alice")' -``` - -### 验证 Pod 归属 - -**通过 Pod ID 验证** - -```bash -# 通过 Pod ID 查询 -kubectl get pods -n ai-agents -o json | \ - jq ".items[] | select(.metadata.uid==\"$POD_ID\")" -``` - -**通过 user-id 标签验证** - -```bash -kubectl get pod -n ai-agents \ - -o jsonpath='{.metadata.labels.user-id}' -``` - ---- - -## 错误码 - -### HTTP 状态码 - -| 状态码 | 说明 | -|--------|------| -| 200 | 请求成功 | -| 201 | 创建成功 | -| 400 | 请求参数错误 | -| 404 | 资源不存在 | -| 409 | 资源冲突(如 Agent 已存在) | -| 500 | 服务器内部错误 | - -### 错误响应格式 - -```json -{ - "detail": "错误详细信息" -} -``` - ---- - -## 使用示例 - -### Python SDK 示例 - -```python -import requests - -class AgentManagerClient: - def __init__(self, base_url="http://localhost:8000"): - self.base_url = base_url - - def create_agent(self, name, template, user_id, env=None): - """创建 Agent""" - payload = { - "name": name, - "template": template, - "config": {"user_id": user_id}, - "env": env or {} - } - response = requests.post( - f"{self.base_url}/agents", - json=payload - ) - response.raise_for_status() - return response.json() - - def get_agent_status(self, name): - """获取 Agent 状态""" - response = requests.get( - f"{self.base_url}/agents/{name}/status" - ) - response.raise_for_status() - return response.json() - - def list_agents(self, template=None): - """列出所有 Agents""" - params = {"template": template} if template else {} - response = requests.get( - f"{self.base_url}/agents", - params=params - ) - response.raise_for_status() - return response.json() - - def delete_agent(self, name): - """删除 Agent""" - response = requests.delete( - f"{self.base_url}/agents/{name}" - ) - response.raise_for_status() - return response.json() - - def list_templates(self, type=None): - """列出模板""" - if type == "platform": - url = f"{self.base_url}/templates/platform" - elif type == "custom": - url = f"{self.base_url}/templates/custom" - else: - url = f"{self.base_url}/templates" - - response = requests.get(url) - response.raise_for_status() - return response.json() - -# 使用示例 -client = AgentManagerClient() - -# 创建 Agent -result = client.create_agent( - name="alice-echo", - template="echo_agent", - user_id="alice" -) -print(f"✅ Agent 创建成功,Pod ID: {result['pod_id']}") - -# 查询状态 -status = client.get_agent_status("alice-echo") -print(f"Agent 状态: {status['status']}") - -# 列出所有 Agents -agents = client.list_agents() -print(f"总共 {agents['count']} 个 Agents") - -# 删除 Agent -client.delete_agent("alice-echo") -print("✅ Agent 删除成功") -``` - -### JavaScript/Node.js 示例 - -```javascript -const axios = require('axios'); - -class AgentManagerClient { - constructor(baseURL = 'http://localhost:8000') { - this.client = axios.create({ baseURL }); - } - - async createAgent(name, template, userId, env = {}) { - const response = await this.client.post('/agents', { - name, - template, - config: { user_id: userId }, - env - }); - return response.data; - } - - async getAgentStatus(name) { - const response = await this.client.get(`/agents/${name}/status`); - return response.data; - } - - async listAgents(template = null) { - const params = template ? { template } : {}; - const response = await this.client.get('/agents', { params }); - return response.data; - } - - async deleteAgent(name) { - const response = await this.client.delete(`/agents/${name}`); - return response.data; - } - - async listTemplates(type = null) { - let url = '/templates'; - if (type === 'platform') url = '/templates/platform'; - if (type === 'custom') url = '/templates/custom'; - - const response = await this.client.get(url); - return response.data; - } -} - -// 使用示例 -(async () => { - const client = new AgentManagerClient(); - - // 创建 Agent - const result = await client.createAgent('bob-chat', 'chat_agent', 'bob'); - console.log(`✅ Agent 创建成功,Pod ID: ${result.pod_id}`); - - // 查询状态 - const status = await client.getAgentStatus('bob-chat'); - console.log(`Agent 状态: ${status.status}`); - - // 列出平台模板 - const templates = await client.listTemplates('platform'); - console.log(`平台模板: ${templates.count} 个`); -})(); -``` - ---- - -## 附录 - -### A. 资源配置建议 - -| Agent 类型 | CPU Request | CPU Limit | Memory Request | Memory Limit | -|-----------|-------------|-----------|----------------|--------------| -| echo_agent | 100m | 500m | 128Mi | 512Mi | -| chat_agent | 200m | 1000m | 256Mi | 1Gi | -| code_agent | 500m | 2000m | 512Mi | 2Gi | -| search_agent | 200m | 1000m | 256Mi | 1Gi | -| mysql_agent | 100m | 500m | 128Mi | 512Mi | -| postgresql_agent | 100m | 500m | 128Mi | 512Mi | -| jina_search_agent | 500m | 2000m | 1Gi | 4Gi | - -### B. 命名规范 - -- **Agent 名称**: 小写字母、数字、连字符,长度 1-63 字符 -- **推荐格式**: `{user_id}-{type}` 或 `{user_id}-{type}-{number}` -- **示例**: `alice-echo`, `bob-chat-001`, `team-a-search` - -### C. 标签说明 - -所有创建的 Agent 自动包含以下标签: - -| 标签 | 说明 | 示例值 | -|------|------|--------| -| `app` | 应用类型 | `ai-agent` | -| `template` | 模板类型 | `echo_agent` | -| `managed-by` | 管理器标识 | `agent-manager` | -| `user-id` | 用户标识 | `alice`, `bob` | - ---- - -## 更新日志 - -### v1.0.0 (2026-01-12) - -**最新更新** -- ✅ 文档已同步到最新的 app.py 实现 -- ✅ 修正环境变量字段名称(`env_variables` → `env`) -- ✅ 更新健康检查响应格式 -- ✅ 修正 HTTP 状态码(创建成功返回 200) -- ✅ 添加命名空间和服务端口信息 - -**功能特性** -- ✅ 实现 Agent 创建和管理 -- ✅ 支持 10 种 Agent 模板(包含 3 种 Azure Blob Agent 框架版本) -- ✅ 多租户支持(user-id 标签) -- ✅ 多框架支持(LangChain、MCP、A2A) -- ✅ Pod ID 返回和归属验证 -- ✅ 模板分类查询(平台/自定义) -- ✅ 资源监控和状态查询 -- ✅ 可自定义命名空间 - -**支持的 Agent 模板** -1. **echo_agent** - Echo 测试服务 -2. **chat_agent** - 聊天服务 -3. **code_agent** - 代码执行服务 -4. **search_agent** - 搜索服务 -5. **jina_search_agent** - Jina 搜索服务 -6. **mysql_agent** - MySQL 客户端(自定义) -7. **postgresql_agent** - PostgreSQL 客户端(自定义) -8. **azure_blob_agent** - Azure Blob Storage(LangChain 框架) -9. **azure_blob_agent_mcp** - Azure Blob Storage(MCP 框架) -10. **azure_blob_agent_a2a** - Azure Blob Storage(A2A 框架) - ---- - -## 多框架 Agent 支持 (v1.1.0+) - -### 框架对比 - -| 特性 | LangChain | MCP | A2A | -|------|-----------|-----|-----| -| **实现方式** | LangChain + LiteLLM | Model Context Protocol | Agent-to-Agent Protocol | -| **工具调用** | LangChain Tools | MCP Tool Classes | A2A Action Handlers | -| **主要端点** | `/query` | `/mcp/tools`, `/mcp/call` | `/a2a/capabilities`, `/a2a/message` | -| **协作能力** | ❌ | ❌ | ✅ Agent 注册和通信 | -| **适用场景** | 复杂推理任务 | 标准化工具调用 | 多 Agent 协作 | -| **集成难度** | 中等 | 简单 | 中等 | - -### 配置参数说明 - -#### 通用参数(所有框架) - -| 参数 | 类型 | 必需 | 说明 | 示例 | -|------|------|------|------|------| -| `user_id` | string | ✅ | 用户标识 | `"user-001"` | -| `tenant_id` | string | ❌ | 租户标识 | `"tenant-001"` | -| `namespace` | string | ❌ | Kubernetes 命名空间 | `"ai-agents"` | -| `agent_framework` | string | ❌ | 框架类型 | `"mcp"` 或 `"a2a"` | -| `cpu_request` | string | ❌ | CPU 请求量 | `"100m"` | -| `cpu_limit` | string | ❌ | CPU 限制 | `"500m"` | -| `memory_request` | string | ❌ | 内存请求量 | `"256Mi"` | -| `memory_limit` | string | ❌ | 内存限制 | `"512Mi"` | - -#### 模型配置参数(MCP/A2A) - -| 参数 | 类型 | 必需 | 说明 | 示例 | -|------|------|------|------|------| -| `model_provider` | string | ✅ | 模型提供商 | `"openai"`, `"azure-openai"` | -| `model_name` | string | ✅ | 模型名称 | `"gpt-4"`, `"gpt-3.5-turbo"` | -| `model_api_key` | string | ✅ | 模型 API 密钥 | `"sk-xxxx"` | -| `model_endpoint` | string | ❌ | 模型 API 端点 | `"https://api.openai.com/v1"` | - -#### 工具配置参数(MCP/A2A) - -| 参数 | 类型 | 必需 | 说明 | 示例 | -|------|------|------|------|------| -| `tools_config` | object | ❌ | 工具配置 JSON | `{"max_iterations": 5}` | -| `tool_endpoint` | string | ❌ | 外部工具端点 | `"http://tools-api:8080"` | -| `tool_api_key` | string | ❌ | 工具 API 密钥 | `"tool-key-xxx"` | - -#### 存储配置参数(Azure Blob Agent) - -| 参数 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `storage_connection_string` | string | ❌ | Azure Storage 连接字符串 | -| `storage_account_name` | string | ❌ | 存储账户名称 | - -### MCP 框架 API 端点 - -MCP Agent 部署后提供以下额外端点: - -#### GET /mcp/tools -列出所有可用的 MCP 工具 - -**响应示例:** -```json -{ - "tools": [ - { - "name": "list_containers", - "description": "列出 Azure Blob Storage 中的所有容器", - "inputSchema": { - "type": "object", - "properties": {}, - "required": [] - } - }, - { - "name": "list_blobs", - "description": "列出指定容器中的所有文件", - "inputSchema": { - "type": "object", - "properties": { - "container_name": { - "type": "string", - "description": "容器名称" - } - }, - "required": ["container_name"] - } - } - ], - "count": 5, - "framework": "mcp" -} -``` - -#### POST /mcp/call -调用指定的 MCP 工具 - -**请求:** -```json -{ - "tool_name": "list_blobs", - "parameters": { - "container_name": "mycontainer" - } -} -``` - -**响应:** -```json -{ - "tool": "list_blobs", - "result": { - "success": true, - "container": "mycontainer", - "blobs": [...], - "count": 10, - "total_size_mb": 125.5 - }, - "timestamp": "2026-01-12T14:50:00Z" -} -``` - -### A2A 框架 API 端点 - -A2A Agent 部署后提供以下额外端点: - -#### GET /a2a/capabilities -获取 Agent 的能力信息 - -**响应示例:** -```json -{ - "agent_id": "blob-agent-001", - "agent_role": "storage_manager", - "capabilities": ["blob_storage", "file_operations"], - "supported_actions": [ - "list_containers", - "list_blobs", - "get_blob_info", - "search_blobs", - "get_stats" - ], - "framework": "a2a" -} -``` - -#### POST /a2a/register -注册其他 Agent(用于协作) - -**请求:** -```json -{ - "agent_id": "analytics-agent", - "agent_role": "data_analyzer", - "capabilities": ["data_analysis", "visualization"], - "endpoint": "http://analytics-agent:8080" -} -``` - -#### POST /a2a/message -发送 A2A 消息给 Agent - -**请求:** -```json -{ - "message_id": "msg-001", - "from_agent": "caller-agent", - "to_agent": "blob-agent-001", - "message_type": "request", - "action": "list_containers", - "parameters": {} -} -``` - -**响应:** -```json -{ - "message_id": "msg-001", - "from_agent": "blob-agent-001", - "to_agent": "caller-agent", - "message_type": "response", - "action": "list_containers", - "result": { - "success": true, - "containers": [...], - "count": 5 - }, - "timestamp": "2026-01-12T14:50:00Z" -} -``` - -#### GET /a2a/agents -列出已注册的 Agent - -#### POST /a2a/collaborate -与其他 Agent 协作 - -### 命名空间支持 - -从 v1.1.0 开始,支持自定义 Kubernetes 命名空间: - -```bash -# 在自定义命名空间中创建 Agent -curl -X POST http://localhost:8000/agents \ - -H "Content-Type: application/json" \ - -d '{ - "name": "my-agent", - "template": "azure_blob_agent_mcp", - "config": { - "namespace": "my-namespace", - "user_id": "user-001", - ... - } - }' - -# 查询特定命名空间的 Agent -kubectl get pods -n my-namespace -l app=ai-agent - -# 通过 API 查询时,namespace 会在响应中返回 -curl http://localhost:8000/agents/my-agent | jq '.namespace' -``` - -### 环境变量传递 - -创建 Agent 时,以下配置会自动转换为容器环境变量: - -| 配置参数 | 环境变量名 | -|----------|-----------| -| `agent_framework` | `AGENT_FRAMEWORK` | -| `tools_config` | `TOOLS_CONFIG` (JSON字符串) | -| `tool_endpoint` | `TOOL_ENDPOINT` | -| `tool_api_key` | `TOOL_API_KEY` | -| `model_provider` | `MODEL_PROVIDER` | -| `model_name` | `MODEL_NAME` | -| `model_endpoint` | `MODEL_ENDPOINT` | -| `model_api_key` | `MODEL_API_KEY` | -| `storage_connection_string` | `AZURE_STORAGE_CONNECTION_STRING` | -| `storage_account_name` | `STORAGE_ACCOUNT_NAME` | -| `user_id` | `USER_ID` | -| `tenant_id` | `TENANT_ID` | -| `namespace` | `NAMESPACE` | - -### 故障排查 - -**Agent 创建失败** - -1. 检查模板名称是否正确 -2. 验证必需参数是否提供(如 model_api_key) -3. 查看 agent-manager 日志 - -**MCP 工具调用失败** - -1. 使用 `GET /mcp/tools` 确认工具名称 -2. 检查参数格式是否符合 inputSchema -3. 查看 agent pod 日志 - -**A2A Agent 无法协作** - -1. 确认目标 Agent 已注册 -2. 检查网络连接和端点可访问性 -3. 验证 message 格式是否正确 - -### 更多资源 - -- [多框架使用指南](agent_templates/MULTI_FRAMEWORK_GUIDE.md) -- [快速参考](agent_templates/QUICK_REFERENCE.md) -- [实现总结](MULTI_FRAMEWORK_SUMMARY.md) - ---- - -## 联系支持 - -如有问题或建议,请联系开发团队。 diff --git a/Docs/用户资源信息查询接口文档.md b/Docs/用户资源信息查询接口文档.md index bc774b4..9281f57 100644 --- a/Docs/用户资源信息查询接口文档.md +++ b/Docs/用户资源信息查询接口文档.md @@ -1,7 +1,8 @@ # 用户资源信息查询接口文档 -> **版本**: 2026-01-14 v2 +> **版本**: 2026-01-14 v3 > **认证方式**: Bearer Token(JWT) +> **更新说明**: 新增域名访问支持(externalIp、domain、domainUrl、accessUrl 字段) --- @@ -219,8 +220,10 @@ response = client.chat.completions.create( 该接口用于获取当前用户的 Agent 资源信息: -1. **平台 Agent**:已部署的平台 Agent 列表,包含实时 IP 和访问地址 -2. **自定义 Agent**:已部署的自定义 Agent 列表,包含实时 IP 和访问地址 +1. **平台 Agent**:已部署的平台 Agent 列表,包含域名、外网IP和访问地址 +2. **自定义 Agent**:已部署的自定义 Agent 列表,包含域名、外网IP和访问地址 + +> 💡 **新特性**:每个 Agent 现在会自动分配域名和外网 IP,推荐使用域名访问 Agent 服务。 --- @@ -277,22 +280,26 @@ Content-Type: application/json | 字段 | 类型 | 说明 | 示例 | |------|------|------|------| -| `name` | string | Agent 实例名称 | `"echo-agent-b00a7b8e-fa5a66"` | +| `name` | string | Agent 实例名称 | `"echo-agent-b00a7b8e-5507d7"` | | `template` | string | Agent 类型/模板 | `"echo_agent"` | | `templateName` | string | 模板名称 | `"echo_agent"` | -| `status` | string | 运行状态(AKS 实时) | `"Running"` / `"Waiting"` / `"Pending"` / `"Failed"` | -| `healthStatus` | string | 健康状态(AKS 实时) | `"healthy"` / `"degraded"` / `"unhealthy"` / `"unknown"` | -| `podIp` | string \| null | **Pod IP 地址**(AKS 实时) | `"10.244.2.103"` | -| `accessUrl` | string \| null | **访问 URL**(AKS 实时) | `null` | -| `servicePort` | integer \| null | 服务端口 | `null` | -| `namespace` | string | K8s 命名空间 | `"ai-agents"` | -| `hostIp` | string \| null | 宿主机 IP(AKS 实时) | `null` | -| `nodeName` | string \| null | 节点名称(AKS 实时) | `null` | +| `status` | string | 运行状态 | `"Running"` / `"Pending"` / `"Failed"` / `"unknown"` | +| `healthStatus` | string | 健康状态 | `"healthy"` / `"degraded"` / `"unhealthy"` / `"unknown"` | +| `podIp` | string \| null | Pod 内部 IP 地址 | `"10.244.3.5"` | +| `externalIp` | string \| null | **🆕 外网 IP 地址** | `"20.195.113.211"` | +| `domain` | string \| null | **🆕 域名**(推荐访问方式) | `"echo-agent-b00a7b8e-5507d7.taijiagnet.com"` | +| `domainUrl` | string \| null | **🆕 域名访问地址** | `"http://echo-agent-b00a7b8e-5507d7.taijiagnet.com"` | +| `accessUrl` | string \| null | **🆕 推荐访问地址**(域名优先) | `"http://echo-agent-b00a7b8e-5507d7.taijiagnet.com"` | +| `servicePort` | integer \| null | 服务端口 | `8000` | +| `namespace` | string | K8s 命名空间 | `"agent-echo-agent-b00a7b8e-5507d7"` | +| `hostIp` | string \| null | 宿主机 IP | `"10.224.0.7"` | +| `nodeName` | string \| null | K8s 节点名称 | `"aks-taijipod-34569487-vmss00000b"` | | `cpu` | string | CPU 配置 | `"100m"` | | `memory` | string | 内存配置 | `"256Mi"` | | `replicas` | integer | 副本数量 | `1` | -| `startTime` | string | 启动时间(ISO 8601) | `"2026-01-11T15:17:17.579421"` | -| `runningSeconds` | integer | 已运行秒数 | `227937` | +| `startTime` | string | 启动时间(ISO 8601) | `"2026-01-14T13:36:22.443568"` | +| `runningSeconds` | integer | 已运行秒数 | `3600` | +| `endpoints` | array \| undefined | 端点 URL 列表 | `["http://10.244.3.5:8000"]` | --- @@ -315,119 +322,53 @@ Content-Type: application/json "data": { "platformAgents": [ { - "name": "echo-agent-b00a7b8e-fa5a66", + "name": "echo-agent-b00a7b8e-5507d7", "template": "echo_agent", "templateName": "echo_agent", "status": "Running", "healthStatus": "healthy", - "podIp": "10.244.2.103", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", + "podIp": "10.244.3.5", + "externalIp": "20.195.113.211", + "domain": "echo-agent-b00a7b8e-5507d7.taijiagnet.com", + "domainUrl": "http://echo-agent-b00a7b8e-5507d7.taijiagnet.com", + "accessUrl": "http://echo-agent-b00a7b8e-5507d7.taijiagnet.com", + "servicePort": 8000, + "namespace": "agent-echo-agent-b00a7b8e-5507d7", + "hostIp": "10.224.0.7", + "nodeName": "aks-taijipod-34569487-vmss00000b", "cpu": "100m", "memory": "256Mi", "replicas": 1, - "startTime": "2026-01-11T15:17:17.579421", - "runningSeconds": 227937, - "hostIp": null, - "nodeName": null - }, - { - "name": "echo-agent-b00a7b8e-362760", - "template": "echo_agent", - "templateName": "echo_agent", - "status": "Running", - "healthStatus": "healthy", - "podIp": "10.244.2.225", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", - "cpu": "100m", - "memory": "256Mi", - "replicas": 1, - "startTime": "2026-01-12T13:22:29.551127", - "runningSeconds": 148425, - "hostIp": null, - "nodeName": null + "startTime": "2026-01-14T13:36:22.443568", + "runningSeconds": 3600 } ], "customAgents": [ { - "name": "test-pgsql-agent-curl", - "template": "postgresql_agent", - "templateName": "A2A", - "status": "Waiting", - "healthStatus": "degraded", - "podIp": "10.244.1.61", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", - "cpu": "100m", - "memory": "128Mi", - "replicas": 1, - "startTime": "2026-01-13T07:32:52.290231", - "runningSeconds": 83003, - "hostIp": null, - "nodeName": null - }, - { - "name": "my-pgsql-agent", - "template": "postgresql_agent", - "templateName": "MCP", - "status": "Waiting", - "healthStatus": "degraded", - "podIp": "10.244.1.158", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", - "cpu": "100m", - "memory": "256Mi", - "replicas": 1, - "startTime": "2026-01-13T12:03:12.933103", - "runningSeconds": 66782, - "hostIp": null, - "nodeName": null - }, - { - "name": "test-auto-apikey-agent", - "template": "postgresql_agent", - "templateName": "MCP", - "status": "Waiting", - "healthStatus": "degraded", - "podIp": "10.244.0.43", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", - "cpu": "100m", - "memory": "256Mi", - "replicas": 1, - "startTime": "2026-01-13T12:10:27.145811", - "runningSeconds": 66348, - "hostIp": null, - "nodeName": null - }, - { - "name": "mysqltestagent", + "name": "my-mysql-agent", "template": "mysql_agent", "templateName": "MCP", - "status": "Waiting", - "healthStatus": "degraded", - "podIp": "10.244.2.42", - "accessUrl": null, - "servicePort": null, - "namespace": "ai-agents", - "cpu": "1000m", + "status": "Running", + "healthStatus": "healthy", + "podIp": "10.244.1.61", + "externalIp": "20.6.66.41", + "domain": "my-mysql-agent.taijiagnet.com", + "domainUrl": "http://my-mysql-agent.taijiagnet.com", + "accessUrl": "http://my-mysql-agent.taijiagnet.com", + "servicePort": 8000, + "namespace": "agent-my-mysql-agent", + "hostIp": "10.224.0.5", + "nodeName": "aks-taijipod-34569487-vmss00000a", + "cpu": "500m", "memory": "1Gi", "replicas": 1, - "startTime": "2026-01-13T12:38:00.340200", - "runningSeconds": 64695, - "hostIp": null, - "nodeName": null + "startTime": "2026-01-14T10:00:00.000000", + "runningSeconds": 14400 } ], "summary": { - "totalPlatformAgents": 2, - "totalCustomAgents": 4 + "totalPlatformAgents": 1, + "totalCustomAgents": 1 } }, "message": "Agent 列表获取成功" @@ -467,13 +408,40 @@ Content-Type: application/json ### Agent 访问方式 -通过 `podIp` 可以在集群内部访问 Agent 服务: +#### 🌐 推荐:使用域名访问(稳定) + +通过 `domain` 或 `accessUrl` 访问 Agent 服务,域名不会因 Pod 重启而变化: ```bash -# 直接通过 Pod IP 访问(默认端口 8080) -curl http://10.244.2.103:8080/api/chat +# 使用域名访问(推荐) +curl http://echo-agent-b00a7b8e-5507d7.taijiagnet.com/api/chat + +# 或使用 accessUrl 字段的值 +curl http://echo-agent-b00a7b8e-5507d7.taijiagnet.com/health ``` +#### 🔗 使用外网 IP 访问 + +```bash +# 使用外网 IP 访问 +curl http://20.195.113.211/api/chat +``` + +#### 📍 集群内部访问(仅限 K8s 集群内) + +```bash +# 使用 Pod IP 访问(仅集群内部) +curl http://10.244.3.5:8000/api/chat +``` + +### 访问方式优先级 + +| 优先级 | 访问方式 | 字段 | 稳定性 | 说明 | +|:---:|---------|------|:---:|------| +| 1 | 域名 | `domain` / `accessUrl` | ⭐⭐⭐ | **推荐**,Pod 重启后不变 | +| 2 | 外网 IP | `externalIp` | ⭐⭐ | LoadBalancer IP,较稳定 | +| 3 | Pod IP | `podIp` | ⭐ | Pod 重启后会变化 | + --- ## 📌 数据来源说明 @@ -482,8 +450,9 @@ curl http://10.244.2.103:8080/api/chat |---------|------|--------| | LiteLLM 密钥 | PostgreSQL 数据库 | 静态(创建时存储) | | Agent 基本信息(名称、模板、CPU/内存) | PostgreSQL 数据库 | 静态(创建时存储) | +| Agent 访问信息(domain、externalIp) | PostgreSQL 数据库 | 静态(创建时存储) | | Agent 状态(status, healthStatus) | AKS 集群(通过 Agent Manager) | **实时查询** | -| Agent 网络信息(podIp) | AKS 集群(通过 Agent Manager) | **实时查询** | +| Agent Pod 信息(podIp、hostIp) | AKS 集群(通过 Agent Manager) | **实时查询** | ### 调用链路 @@ -498,6 +467,8 @@ curl http://10.244.2.103:8080/api/chat ## ⚠️ 注意事项 1. **密钥安全**:返回的 `apiKey` 是完整的解密密钥,请妥善保管,不要泄露 -2. **实时性**:Agent 的 `podIp`、`status` 等信息是实时从 AKS 查询的,可能有轻微延迟 -3. **服务可用性**:如果 Agent Manager 服务不可用,Agent 的实时信息将显示为 `null` 或 `unknown` -4. **Pod IP 变化**:Pod 重启后 IP 地址会改变 +2. **推荐域名访问**:使用 `domain` 或 `accessUrl` 访问 Agent,比 Pod IP 更稳定 +3. **DNS 生效时间**:新创建的 Agent 域名可能需要 1-5 分钟 DNS 传播时间 +4. **实时性**:`status`、`podIp` 等信息是实时从 AKS 查询的,可能有轻微延迟 +5. **服务可用性**:如果 Agent Manager 服务不可用,实时信息将显示为 `null` 或 `unknown` +6. **旧 Agent 兼容**:在 2026-01-14 之前创建的 Agent,`domain`、`externalIp` 等字段可能为 `null` diff --git a/docs/agent-domain-access-改动计划.md b/docs/agent-domain-access-改动计划.md new file mode 100644 index 0000000..1b00c85 --- /dev/null +++ b/docs/agent-domain-access-改动计划.md @@ -0,0 +1,501 @@ +# Agent 域名访问改动计划 + +> **版本**: 2026-01-14 v1 +> **状态**: ✅ 已完成 +> **相关服务**: mcp-server, agent-manager +> **完成时间**: 2026-01-14 + +--- + +## 📋 背景与需求 + +### 业务变更说明 + +1. **Agent Manager 服务升级**:部署好的每个 Pod 现在会自动绑定域名和外网 IP +2. **访问方式变更**:租户后续将使用域名访问属于自己的平台 Agent 和自定义 Agent +3. **数据存储需求**:需要记录 Agent Manager 返回的访问信息(domain、external_ip 等) + +### Agent Manager 返回的 access_info 结构 + +```json +{ + "access_info": { + "external_ip": "135.171.210.24", + "ip_url": "http://135.171.210.24:80", + "domain": "my-agent.taijiagent.com", + "domain_url": "http://my-agent.taijiagent.com", + "recommended": "http://my-agent.taijiagent.com" + } +} +``` + +--- + +## 🔍 当前代码分析 + +### 1. 数据库模型现状 + +#### Agent 模型 (`models.py:125`) + +```python +class Agent(BaseModel, Base): + # ... 已有字段 + access_url = Column(String(500)) # 访问 URL(单个字段) + endpoints = Column(JSON, default=dict) # 端点信息 + # ❌ 缺少 domain、external_ip 等字段 +``` + +#### AgentBillingRecord 模型 (`models.py:1157`) + +```python +class AgentBillingRecord(BaseModel, Base): + # ... 已有字段 + agent_name = Column(String(100), nullable=False) + # ❌ 缺少 access_info 相关字段(domain、external_ip、access_url) +``` + +### 2. Agent Manager Client 现状 + +#### AgentCreateResult (`agent_manager_client.py:97`) + +```python +@dataclass +class AgentCreateResult: + name: str + namespace: str + status: str + access_info: Optional[Dict[str, Any]] = None # ✅ 已有,但未完整使用 +``` + +#### AgentStatusResult (`agent_manager_client.py:119`) + +```python +@dataclass +class AgentStatusResult: + # ❌ 缺少 domain、external_ip 等字段 + access_url: Optional[str] = None # 只有单个 access_url + endpoints: Optional[List[str]] = None +``` + +### 3. 创建 Agent 代码现状 + +#### 平台 Agent 创建 (`user.py:1427-1439`) + +```python +result = await client.create_agent(...) +# 创建 Agent 记录时 +agent = Agent( + name=instance_name, + # ❌ 未保存 result.access_info 中的 domain、external_ip +) +``` + +#### 自定义 Agent 创建 (`user.py:3146-3157`) + +```python +result = await client.create_custom_agent(...) +# ❌ 响应中只返回了 accessInfo,未持久化 domain 到数据库 +return SuccessResponse( + data={ + "accessInfo": result.access_info, # 只是透传,未存储 + } +) +``` + +### 4. 查询 Agent 列表代码现状 + +#### 用户 Agent 资源查询 (`user.py:3904-3999`) + +```python +@router.get("/resources/agents") +async def get_user_agents_info(...): + # 从 Agent Manager 获取状态 + agent_status = await client.get_agent_status(record.agent_name) + agent_info["accessUrl"] = agent_status.access_url # ❌ 只返回 access_url + # ❌ 缺少 domain、external_ip 等字段 +``` + +--- + +## ✅ 改动方案 + +### 阶段一:数据库模型改动 + +#### 1.1 修改 AgentBillingRecord 模型 + +**文件**: `services/mcp-server/models.py` + +```python +class AgentBillingRecord(BaseModel, Base): + # ... 已有字段 + + # ========== 新增:访问信息字段 ========== + external_ip = Column(String(45), nullable=True) # 外网 IP 地址 + domain = Column(String(255), nullable=True) # 域名 + domain_url = Column(String(500), nullable=True) # 域名访问地址 + access_url = Column(String(500), nullable=True) # 推荐访问地址 + service_port = Column(Integer, nullable=True) # 服务端口 + namespace = Column(String(100), nullable=True) # K8s 命名空间 +``` + +#### 1.2 创建数据库迁移脚本 + +**文件**: `services/mcp-server/alembic/versions/xxxx_add_agent_access_info.py` + +```python +"""Add agent access info fields + +Revision ID: xxxx +""" + +def upgrade(): + op.add_column('agent_billing_records', + sa.Column('external_ip', sa.String(45), nullable=True)) + op.add_column('agent_billing_records', + sa.Column('domain', sa.String(255), nullable=True)) + op.add_column('agent_billing_records', + sa.Column('domain_url', sa.String(500), nullable=True)) + op.add_column('agent_billing_records', + sa.Column('access_url', sa.String(500), nullable=True)) + op.add_column('agent_billing_records', + sa.Column('service_port', sa.Integer, nullable=True)) + op.add_column('agent_billing_records', + sa.Column('namespace', sa.String(100), nullable=True)) + + # 添加索引(可选,用于按域名查询) + op.create_index('idx_agent_billing_domain', 'agent_billing_records', ['domain']) + +def downgrade(): + op.drop_index('idx_agent_billing_domain', 'agent_billing_records') + op.drop_column('agent_billing_records', 'namespace') + op.drop_column('agent_billing_records', 'service_port') + op.drop_column('agent_billing_records', 'access_url') + op.drop_column('agent_billing_records', 'domain_url') + op.drop_column('agent_billing_records', 'domain') + op.drop_column('agent_billing_records', 'external_ip') +``` + +--- + +### 阶段二:Agent Manager Client 改动 + +#### 2.1 修改 AgentStatusResult + +**文件**: `services/mcp-server/app/agent_manager_client.py` + +```python +@dataclass +class AgentStatusResult: + # ... 已有字段 + + # ========== 新增:访问信息字段 ========== + external_ip: Optional[str] = None # 外网 IP + domain: Optional[str] = None # 域名 + domain_url: Optional[str] = None # 域名访问地址 +``` + +#### 2.2 修改 get_agent_status 方法解析逻辑 + +**文件**: `services/mcp-server/app/agent_manager_client.py` + +在 `get_agent_status` 方法中,需要解析 Agent Manager 返回的 access_info: + +```python +async def get_agent_status(self, name: str) -> AgentStatusResult: + # ... 现有逻辑 + + # 解析 access_info + access_info = data.get("access_info", {}) + + return AgentStatusResult( + # ... 现有字段 + external_ip=access_info.get("external_ip"), + domain=access_info.get("domain"), + domain_url=access_info.get("domain_url"), + access_url=access_info.get("recommended") or access_info.get("domain_url"), + ) +``` + +--- + +### 阶段三:创建 Agent 代码改动 + +#### 3.1 平台 Agent 创建改动 + +**文件**: `services/mcp-server/app/routes/user.py` + +**位置**: `list_platform_agents` / `deploy_platform_agent` 函数 + +```python +# 在创建 billing_record 时保存 access_info +billing_record = AgentBillingRecord( + # ... 已有字段 + + # ========== 新增:保存访问信息 ========== + external_ip=result.access_info.get("external_ip") if result.access_info else None, + domain=result.access_info.get("domain") if result.access_info else None, + domain_url=result.access_info.get("domain_url") if result.access_info else None, + access_url=result.access_info.get("recommended") if result.access_info else None, + service_port=result.service_port, + namespace=result.namespace, +) +``` + +#### 3.2 自定义 Agent 创建改动 + +**文件**: `services/mcp-server/app/routes/user.py` + +**位置**: `create_custom_agent` 函数(约第 3146-3213 行) + +```python +# 在创建 billing_record 时保存 access_info +billing_record = AgentBillingRecord( + # ... 已有字段 + + # ========== 新增:保存访问信息 ========== + external_ip=result.access_info.get("external_ip") if result.access_info else None, + domain=result.access_info.get("domain") if result.access_info else None, + domain_url=result.access_info.get("domain_url") if result.access_info else None, + access_url=result.access_info.get("recommended") if result.access_info else None, + service_port=result.service_port, + namespace=result.namespace, +) +``` + +--- + +### 阶段四:查询 Agent 列表改动 + +#### 4.1 用户 Agent 资源查询改动 + +**文件**: `services/mcp-server/app/routes/user.py` + +**位置**: `get_user_agents_info` 函数(约第 3904-3999 行) + +```python +@router.get("/resources/agents", response_model=SuccessResponse) +async def get_user_agents_info(...): + for record in billing_records: + agent_info = { + # ... 已有字段 + + # ========== 新增:访问信息字段(优先使用数据库存储的值) ========== + "externalIp": record.external_ip, + "domain": record.domain, + "domainUrl": record.domain_url, + "accessUrl": record.access_url, # 推荐访问地址 + } + + # 从 Agent Manager 获取最新状态(实时更新 IP 等信息) + 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 Manager 返回了新的值) + if agent_status.external_ip: + agent_info["externalIp"] = agent_status.external_ip + if agent_status.domain: + agent_info["domain"] = agent_status.domain + if agent_status.domain_url: + agent_info["domainUrl"] = agent_status.domain_url + if agent_status.access_url: + agent_info["accessUrl"] = agent_status.access_url + except Exception as e: + logger.warning(f"获取 Agent {record.agent_name} 状态失败: {e}") +``` + +#### 4.2 自定义 Agent 列表查询改动 + +**文件**: `services/mcp-server/app/routes/user.py` + +**位置**: `list_my_custom_agents` 函数(约第 3462-3516 行) + +同样需要添加 domain 等字段的返回。 + +--- + +### 阶段五:响应模型改动 + +#### 5.1 添加/修改 Schema + +**文件**: `services/mcp-server/app/schemas.py` 或 `services/mcp-server/schemas.py` + +```python +class AgentAccessInfo(BaseModel): + """Agent 访问信息""" + external_ip: Optional[str] = Field(None, description="外网 IP 地址") + domain: Optional[str] = Field(None, description="域名") + domain_url: Optional[str] = Field(None, description="域名访问地址") + ip_url: Optional[str] = Field(None, description="IP 访问地址") + recommended: Optional[str] = Field(None, description="推荐访问地址") + + +class AgentResourceInfo(BaseModel): + """用户 Agent 资源信息""" + name: str + template: str + templateName: Optional[str] = None + status: str + healthStatus: str + + # Pod 信息 + podIp: Optional[str] = None + hostIp: Optional[str] = None + nodeName: Optional[str] = None + + # ========== 新增:访问信息 ========== + externalIp: Optional[str] = Field(None, description="外网 IP 地址") + domain: Optional[str] = Field(None, description="域名") + domainUrl: Optional[str] = Field(None, description="域名访问地址") + accessUrl: Optional[str] = Field(None, description="推荐访问地址(域名优先)") + + # 资源配置 + servicePort: Optional[int] = None + namespace: str = "ai-agents" + cpu: Optional[str] = None + memory: Optional[str] = None + replicas: int = 1 + + # 运行信息 + startTime: Optional[str] = None + runningSeconds: int = 0 + endpoints: Optional[List[str]] = None +``` + +--- + +## 📄 接口文档更新 + +### GET /api/user/resources/agents 响应更新 + +```json +{ + "success": true, + "data": { + "platformAgents": [ + { + "name": "echo-agent-b00a7b8e-fa5a66", + "template": "echo_agent", + "templateName": "echo_agent", + "status": "Running", + "healthStatus": "healthy", + "podIp": "10.244.2.103", + "externalIp": "135.171.210.24", + "domain": "echo-agent-b00a7b8e-fa5a66.taijiagent.com", + "domainUrl": "http://echo-agent-b00a7b8e-fa5a66.taijiagent.com", + "accessUrl": "http://echo-agent-b00a7b8e-fa5a66.taijiagent.com", + "servicePort": 80, + "namespace": "agent-echo-agent-b00a7b8e-fa5a66", + "cpu": "100m", + "memory": "256Mi", + "replicas": 1, + "startTime": "2026-01-11T15:17:17.579421", + "runningSeconds": 227937 + } + ], + "customAgents": [ + { + "name": "my-mysql-agent", + "template": "mysql_agent", + "templateName": "MCP", + "status": "Running", + "healthStatus": "healthy", + "podIp": "10.244.1.61", + "externalIp": "135.171.210.25", + "domain": "my-mysql-agent.taijiagent.com", + "domainUrl": "http://my-mysql-agent.taijiagent.com", + "accessUrl": "http://my-mysql-agent.taijiagent.com", + "servicePort": 80, + "namespace": "agent-my-mysql-agent", + "cpu": "500m", + "memory": "1Gi", + "replicas": 1, + "startTime": "2026-01-13T07:32:52.290231", + "runningSeconds": 83003 + } + ], + "summary": { + "totalPlatformAgents": 1, + "totalCustomAgents": 1 + } + }, + "message": "Agent 列表获取成功" +} +``` + +--- + +## 📝 改动文件清单 + +| 序号 | 文件路径 | 改动类型 | 改动说明 | +|:---:|---------|---------|---------| +| 1 | `services/mcp-server/models.py` | 修改 | AgentBillingRecord 添加访问信息字段 | +| 2 | `services/mcp-server/alembic/versions/xxx.py` | 新增 | 数据库迁移脚本 | +| 3 | `services/mcp-server/app/agent_manager_client.py` | 修改 | AgentStatusResult 添加 domain 等字段 | +| 4 | `services/mcp-server/app/routes/user.py` | 修改 | 创建 Agent 时保存 access_info | +| 5 | `services/mcp-server/app/routes/user.py` | 修改 | 查询 Agent 列表返回 domain 等字段 | +| 6 | `services/mcp-server/app/schemas.py` | 修改 | 添加/修改响应模型 | +| 7 | `Docs/用户资源信息查询接口文档.md` | 修改 | 更新接口文档 | +| 8 | `Docs/数据工具与自定义Agent-前端接口文档.md` | 修改 | 更新接口文档 | + +--- + +## 🔄 实施步骤 + +### Step 1: 数据库改动(需要停机) + +1. 备份数据库 +2. 执行数据库迁移脚本 +3. 验证迁移结果 + +### Step 2: 代码改动 + +1. 修改 `models.py` +2. 修改 `agent_manager_client.py` +3. 修改 `user.py` 中的创建 Agent 逻辑 +4. 修改 `user.py` 中的查询 Agent 逻辑 +5. 修改响应模型 + +### Step 3: 测试验证 + +1. 单元测试 +2. 集成测试(创建 Agent → 查询列表 → 访问域名) +3. 前端联调 + +### Step 4: 文档更新 + +1. 更新 API 接口文档 +2. 更新前端接口文档 + +--- + +## ⚠️ 注意事项 + +1. **向后兼容**:新增字段均为可选(nullable=True),不影响现有数据 +2. **域名生效时间**:域名 DNS 解析可能有延迟(通常 1-5 分钟) +3. **访问优先级**:推荐使用 `accessUrl`(域名优先),Pod IP 会随重启变化 +4. **安全考虑**:域名访问可能需要配置 HTTPS(后续考虑) + +--- + +## 📊 预估工时 + +| 阶段 | 工时估算 | +|-----|---------| +| 数据库改动 | 0.5 天 | +| Agent Manager Client 改动 | 0.5 天 | +| 创建 Agent 代码改动 | 1 天 | +| 查询 Agent 列表改动 | 0.5 天 | +| 测试与联调 | 1 天 | +| 文档更新 | 0.5 天 | +| **总计** | **4 天** | + +--- + +**文档编写**: AI Assistant +**最后更新**: 2026-01-14 + diff --git a/plans/Agent启动业务流程分析报告.md b/plans/Agent启动业务流程分析报告.md deleted file mode 100644 index fe45a8a..0000000 --- a/plans/Agent启动业务流程分析报告.md +++ /dev/null @@ -1,271 +0,0 @@ -# Agent 启动业务流程分析报告 - -> **版本**: v1.0.0 -> **创建时间**: 2026-01-08 -> **分析目标**: 确认平台 Agent 和自定义 Agent 的启动业务流程实现状态 - ---- - -## 1. 业务流程概述 - -### 1.1 业务逻辑1:平台 Agent 启动流程 - -**流程描述**:渠道分配平台 Agent 给租户后,平台 Agent 通过 Agent Manager 服务启动到 AKS 中开始运行。 - -```mermaid -sequenceDiagram - participant CA as 渠道管理员 - participant MCP as mcp-server - participant AM as Agent Manager - participant AKS as Azure Kubernetes - - CA->>MCP: 1. 申请平台 Agent 配额 - MCP->>MCP: 2. 创建 ResourceApplication - Note over MCP: 状态: pending - - rect rgb(200, 220, 255) - Note over MCP: 管理员审批流程 - MCP->>MCP: 3. 管理员审批通过 - MCP->>MCP: 4. 创建渠道 PlatformAgentQuota - end - - CA->>MCP: 5. 分配平台 Agent 给租户 - MCP->>MCP: 6. 检查渠道配额 - MCP->>MCP: 7. 创建租户 PlatformAgentQuota - MCP->>AM: 8. 调用 POST /agents 创建 Pod - AM->>AKS: 9. 创建 K8s Pod - AKS-->>AM: 10. 返回 Pod 状态 - AM-->>MCP: 11. 返回创建结果 - MCP->>MCP: 12. 创建 Agent 记录 - MCP-->>CA: 13. 返回分配成功 -``` - -### 1.2 业务逻辑2:自定义 Agent 启动流程 - -**流程描述**:渠道分配自定义 Agent 配额给租户后,租户通过 mcp-server 平台配置相关配置文件后,自定义 Agent 通过 Agent Manager 服务启动到 AKS 中。 - -```mermaid -sequenceDiagram - participant CA as 渠道管理员 - participant T as 租户 - participant MCP as mcp-server - participant AM as Agent Manager - participant AKS as Azure Kubernetes - - CA->>MCP: 1. 分配自定义 Agent 配额给租户 - MCP->>MCP: 2. 创建 TenantCustomAgentQuota - Note over MCP: CPU/内存配额 - - T->>MCP: 3. 创建自定义 Agent - Note over T,MCP: 提供模板、环境变量、资源配置 - MCP->>MCP: 4. 检查租户配额 - MCP->>MCP: 5. 查询租户 LiteLLM Key(如指定模型) - MCP->>MCP: 6. 构建环境变量(注入 LiteLLM 配置) - MCP->>AM: 7. 调用 POST /agents 创建 Pod - AM->>AKS: 8. 创建 K8s Pod - AKS-->>AM: 9. 返回 Pod 状态 - AM-->>MCP: 10. 返回创建结果 - MCP->>MCP: 11. 更新配额使用量 - MCP->>MCP: 12. 创建 AgentBillingRecord - MCP-->>T: 13. 返回创建成功 -``` - ---- - -## 2. 实现状态分析 - -### 2.1 平台 Agent 启动流程 - ✅ 已实现 - -| 步骤 | 功能 | 实现文件 | 实现状态 | -|------|------|----------|----------| -| 1 | 渠道申请平台 Agent 配额 | [`platform_agent_quota.py:239-316`](services/mcp-server/app/routes/platform_agent_quota.py:239) | ✅ 已实现 | -| 2 | 管理员审批申请 | [`platform_agent_quota.py:626-721`](services/mcp-server/app/routes/platform_agent_quota.py:626) | ✅ 已实现 | -| 3 | 渠道分配平台 Agent 给租户 | [`platform_agent_quota.py:404-579`](services/mcp-server/app/routes/platform_agent_quota.py:404) | ✅ 已实现 | -| 4 | 调用 Agent Manager 创建 Pod | [`platform_agent_quota.py:520-524`](services/mcp-server/app/routes/platform_agent_quota.py:520) | ✅ 已实现 | -| 5 | 创建 Agent 数据库记录 | [`platform_agent_quota.py:540-551`](services/mcp-server/app/routes/platform_agent_quota.py:540) | ✅ 已实现 | -| 6 | 更新配额使用量 | [`platform_agent_quota.py:527-537`](services/mcp-server/app/routes/platform_agent_quota.py:527) | ✅ 已实现 | - -**关键代码位置**: - -- **渠道分配接口**: [`POST /api/channel/tenants/{tenant_id}/platform-agents`](services/mcp-server/app/routes/platform_agent_quota.py:404) -- **Agent Manager 客户端**: [`agent_manager_client.py:751-830`](services/mcp-server/app/agent_manager_client.py:751) - -**实现细节**: - -```python -# platform_agent_quota.py:520-524 - 调用 Agent Manager 创建 Pod -result = await client.create_agent( - name=pod_name, - template=request.templateName, - config=agent_config -) -``` - -### 2.2 自定义 Agent 启动流程 - ✅ 已实现 - -| 步骤 | 功能 | 实现文件 | 实现状态 | -|------|------|----------|----------| -| 1 | 渠道分配自定义 Agent 配额 | [`channel.py:564-651`](services/mcp-server/app/routes/channel.py:564) | ✅ 已实现 | -| 2 | 租户查看配额 | [`user.py:342-400`](services/mcp-server/app/routes/user.py:342) | ✅ 已实现 | -| 3 | 租户创建自定义 Agent | [`user.py:1539-1712`](services/mcp-server/app/routes/user.py:1539) | ✅ 已实现 | -| 4 | 检查配额 | [`user.py:1563-1600`](services/mcp-server/app/routes/user.py:1563) | ✅ 已实现 | -| 5 | 查询 LiteLLM Key 并注入环境变量 | [`user.py:1609-1645`](services/mcp-server/app/routes/user.py:1609) | ✅ 已实现 | -| 6 | 调用 Agent Manager 创建 Pod | [`user.py:1660-1666`](services/mcp-server/app/routes/user.py:1660) | ✅ 已实现 | -| 7 | 更新配额使用量 | [`user.py:1669-1671`](services/mcp-server/app/routes/user.py:1669) | ✅ 已实现 | -| 8 | 创建计费记录 | [`user.py:1674-1684`](services/mcp-server/app/routes/user.py:1674) | ✅ 已实现 | - -**关键代码位置**: - -- **创建自定义 Agent 接口**: [`POST /api/user/custom-agents`](services/mcp-server/app/routes/user.py:1539) -- **LiteLLM Key 注入**: [`user.py:1609-1645`](services/mcp-server/app/routes/user.py:1609) - -**实现细节**: - -```python -# user.py:1631-1639 - 注入 LiteLLM 环境变量 -env_vars["OPENAI_API_BASE"] = settings.litellm_url -env_vars["OPENAI_API_KEY"] = decrypted_key -env_vars["MODEL_NAME"] = model_name -env_vars["LITELLM_MODEL"] = model_name - -# user.py:1660-1666 - 调用 Agent Manager 创建 Pod -result = await client.create_custom_agent( - name=req.name, - template=req.template, - user_id=str(user_id), - env_vars=env_vars, - config=agent_config -) -``` - ---- - -## 3. 接口清单 - -### 3.1 平台 Agent 相关接口 - -| 接口 | 方法 | 路径 | 说明 | -|------|------|------|------| -| 查看可用平台 Agent | GET | `/api/channel/available-platform-agents` | 渠道查看可申请的平台 Agent | -| 申请平台 Agent 配额 | POST | `/api/channel/applications/platform-agents` | 渠道申请配额 | -| 查看申请列表 | GET | `/api/channel/applications/platform-agents` | 渠道查看自己的申请 | -| 审批申请 | PUT | `/api/admin/applications/platform-agents/{id}/review` | 管理员审批 | -| 分配给租户 | POST | `/api/channel/tenants/{tenant_id}/platform-agents` | **核心接口:分配并启动 Pod** | -| 查看渠道配额 | GET | `/api/channel/platform-agents` | 渠道查看已有配额 | -| 租户查看配额 | GET | `/api/user/platform-agents` | 租户查看自己的配额 | -| 停止 Agent | DELETE | `/api/user/platform-agents/{agent_name}` | 租户停止 Agent | - -### 3.2 自定义 Agent 相关接口 - -| 接口 | 方法 | 路径 | 说明 | -|------|------|------|------| -| 分配配额给租户 | PUT | `/api/channel/tenants/{tenant_id}/resources` | 渠道分配 CPU/内存配额 | -| 查看配额 | GET | `/api/user/custom-agent-quota` | 租户查看自己的配额 | -| 查看模板 | GET | `/api/user/custom-agents/templates` | 租户查看可用模板 | -| 创建自定义 Agent | POST | `/api/user/custom-agents` | **核心接口:创建并启动 Pod** | -| 查看 Agent 列表 | GET | `/api/user/custom-agents` | 租户查看自己的 Agent | -| 删除 Agent | DELETE | `/api/user/custom-agents/{name}` | 租户删除 Agent | -| 扩缩容 | PUT | `/api/user/custom-agents/{name}/scale` | 租户调整资源 | - ---- - -## 4. Agent Manager 客户端接口 - -mcp-server 通过 [`AgentManagerClient`](services/mcp-server/app/agent_manager_client.py:535) 与 Agent Manager 服务交互: - -| 方法 | Agent Manager 接口 | 说明 | -|------|-------------------|------| -| `create_agent()` | POST /agents | 创建 Agent Pod | -| `create_platform_agent()` | POST /agents | 创建平台 Agent(便捷方法) | -| `create_custom_agent()` | POST /agents | 创建自定义 Agent(便捷方法) | -| `delete_agent()` | DELETE /agents/{name} | 删除 Agent Pod | -| `get_agent_status()` | GET /agents/{name}/status | 获取 Agent 状态 | -| `get_agent_metrics()` | GET /agents/{name}/metrics | 获取资源使用情况 | -| `list_agents()` | GET /agents | 列出所有 Agent | -| `list_platform_templates()` | GET /templates/platform | 获取平台模板 | -| `list_custom_templates()` | GET /templates/custom | 获取自定义模板 | - ---- - -## 5. 数据模型 - -### 5.1 配额管理 - -``` -PlatformAgentQuota -├── target_id: UUID (渠道ID 或 租户ID) -├── target_type: str (channel 或 tenant) -├── template_name: str (模板名称) -├── pod_quota: int (配额上限) -├── pod_used: int (已使用) -└── allocated_at: datetime - -TenantCustomAgentQuota -├── tenant_id: UUID -├── cpu_quota: float (CPU 配额,核心数) -├── memory_quota: float (内存配额,GB) -├── cpu_used: float -├── memory_used: float -└── agent_count: int -``` - -### 5.2 Agent 记录 - -``` -Agent -├── name: str -├── type: str (platform 或 custom) -├── template: str -├── pod_name: str -├── k8s_namespace: str -├── k8s_status: str -├── service_port: int -├── owner_id: UUID (租户ID) -└── status: str -``` - ---- - -## 6. 结论 - -### 6.1 实现状态总结 - -| 业务流程 | 实现状态 | 说明 | -|----------|----------|------| -| 平台 Agent 启动流程 | ✅ **完整实现** | 渠道分配时立即启动 Pod | -| 自定义 Agent 启动流程 | ✅ **完整实现** | 租户创建时启动 Pod,支持 LiteLLM 集成 | - -### 6.2 关键实现特点 - -1. **平台 Agent**: - - 渠道分配配额给租户时,**立即调用 Agent Manager 启动 Pod** - - 配额管理采用两级结构:渠道配额 → 租户配额 - - 支持配额使用量追踪(pod_used) - -2. **自定义 Agent**: - - 租户创建 Agent 时,**立即调用 Agent Manager 启动 Pod** - - 支持 LiteLLM 环境变量自动注入(OPENAI_API_BASE, OPENAI_API_KEY, MODEL_NAME) - - 配额管理基于 CPU/内存资源 - -3. **LiteLLM 集成**: - - 根据 [`Agent-Manager接口变动需求文档.md`](plans/Agent-Manager接口变动需求文档.md) 的设计 - - 自定义 Agent 创建时会自动注入 LiteLLM 相关环境变量 - - 环境变量通过 `env_vars` 参数传递给 Agent Manager - -### 6.3 待确认事项 - -1. **Agent Manager 服务**:需要确认 Agent Manager 服务是否已部署并正常运行 -2. **敏感信息处理**:根据需求文档,建议 Agent Manager 使用 K8s Secret 存储敏感环境变量(如 OPENAI_API_KEY) -3. **模板配置**:管理员需要通过 `/api/admin/platform-agents/templates/{name}/config` 接口配置平台 Agent 模板的资源限制 - ---- - -## 7. 相关文件索引 - -| 文件 | 说明 | -|------|------| -| [`services/mcp-server/app/routes/platform_agent_quota.py`](services/mcp-server/app/routes/platform_agent_quota.py) | 平台 Agent 配额管理路由 | -| [`services/mcp-server/app/routes/user.py`](services/mcp-server/app/routes/user.py) | 用户侧 API(含自定义 Agent) | -| [`services/mcp-server/app/routes/channel.py`](services/mcp-server/app/routes/channel.py) | 渠道 API(含配额分配) | -| [`services/mcp-server/app/agent_manager_client.py`](services/mcp-server/app/agent_manager_client.py) | Agent Manager 客户端 | -| [`plans/Agent-Manager接口变动需求文档.md`](plans/Agent-Manager接口变动需求文档.md) | Agent Manager 接口变动需求 | diff --git a/plans/Agent计费完整流程图.md b/plans/Agent计费完整流程图.md deleted file mode 100644 index 4ec63f2..0000000 --- a/plans/Agent计费完整流程图.md +++ /dev/null @@ -1,269 +0,0 @@ -# Agent 计费完整流程图 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Agent 计费完整流程 │ -└─────────────────────────────────────────────────────────────────────────────┘ - -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ │ │ │ │ │ -│ MCP Server │ │Agent Manager │ │ Kubernetes │ -│ │ │ │ │ │ -└───────┬──────┘ └───────┬──────┘ └───────┬──────┘ - │ │ │ - │ │ │ - │ 1. POST /agents │ │ - │ { │ │ - │ name: "alice-echo"│ │ - │ template: "echo" │ │ - │ config: { │ │ - │ user_id: "xxx" │ │ - │ } │ │ - │ } │ │ - ├───────────────────────>│ │ - │ │ │ - │ │ 2. Create Pod │ - │ │ with Labels: │ - │ │ user-id=xxx │ - │ ├───────────────────────>│ - │ │ │ - │ │ 3. Pod Created │ - │ │<───────────────────────┤ - │ │ │ - │ 4. Agent Created │ │ - │<───────────────────────┤ │ - │ │ │ - │ │ │ - │ ⏱️ Agent 运行中... │ - │ │ │ - │ │ │ - │ │ 5. Agent 停止 │ - │ │ (用户调用完成) │ - │ │ │ - │ │ 6. Read Pod Labels │ - │ ├───────────────────────>│ - │ │ │ - │ │ 7. Return Labels │ - │ │ user-id=xxx │ - │ │<───────────────────────┤ - │ │ │ - │ │ 8. Calculate Time │ - │ │ running_time = │ - │ │ stop - start │ - │ │ │ - │ 9. POST /billing/ │ │ - │ agent-callback │ │ - │ { │ │ - │ agentName: "..." │ │ - │ userId: "xxx" │◄── 从 Labels 获取 │ - │ podRunningTime: 120 │ - │ toolsUsed: [...] │ │ - │ } │ │ - │<───────────────────────┤ │ - │ │ │ - │ 10. Create Billing │ │ - │ Record │ │ - │ - Calculate EU │ │ - │ - Calculate Cost │ │ - │ - Deduct Balance │ │ - │ │ │ - │ 11. Response │ │ - │ { │ │ - │ success: true │ │ - │ recordId: "..." │ │ - │ } │ │ - ├───────────────────────>│ │ - │ │ │ - │ │ 12. Delete Pod │ - │ ├───────────────────────>│ - │ │ │ - ▼ ▼ ▼ - - -═══════════════════════════════════════════════════════════════════════════════ -关键点:用户身份识别 -═══════════════════════════════════════════════════════════════════════════════ - -步骤 2: Agent Manager 创建 Pod 时 - ↓ - 将 user_id 存储在 Pod Labels 中 - ↓ - labels: - user-id: "xxx" - ↓ -步骤 6-7: Agent 停止时读取 Pod Labels - ↓ - 获取 user_id - ↓ -步骤 9: 回调时使用该 user_id - - -═══════════════════════════════════════════════════════════════════════════════ -数据库记录结构 -═══════════════════════════════════════════════════════════════════════════════ - -agent_billing_records 表: - -┌─────────────────┬──────────────────────┬─────────────────────┐ -│ 字段 │ 值示例 │ 来源 │ -├─────────────────┼──────────────────────┼─────────────────────┤ -│ user_id │ user-123-456 │ Pod Labels │ -│ agent_name │ alice-echo-agent │ Agent Manager │ -│ duration_seconds│ 120 │ 计算: stop - start │ -│ eu_consumed │ 12 │ ceil(120/10) │ -│ cost │ 0.10 │ 基于费率计算 │ -│ tools_used │ ["web_search"] │ Agent Manager │ -│ request_id │ req-abc-123 │ Agent Manager │ -│ start_time │ 2026-01-11T10:00:00Z │ Pod 创建时间 │ -│ end_time │ 2026-01-11T10:02:00Z │ Pod 删除时间 │ -└─────────────────┴──────────────────────┴─────────────────────┘ - - -═══════════════════════════════════════════════════════════════════════════════ -错误处理流程 -═══════════════════════════════════════════════════════════════════════════════ - -场景 1: Pod Labels 中没有 user_id - ↓ -Agent Manager 应该: - 1. 从内部映射表查询(如果维护了) - 2. 记录错误日志 - 3. 使用默认/系统用户 ID(可选) - 4. 继续回调(避免计费丢失) - - -场景 2: 回调 MCP Server 失败 - ↓ -Agent Manager 应该: - 1. 重试 2-3 次(指数退避) - 2. 将失败的回调存储到队列 - 3. 定期重试队列中的失败回调 - 4. 记录详细错误日志 - - -场景 3: MCP Server 返回用户不存在 - ↓ -Agent Manager 应该: - 1. 记录严重错误 - 2. 通知管理员 - 3. 不再重试该条记录 - - -═══════════════════════════════════════════════════════════════════════════════ -``` - -## 关键技术点 - -### 1. 用户身份识别 - -```yaml -# Pod 定义示例 -apiVersion: v1 -kind: Pod -metadata: - name: alice-echo-agent - labels: - app: agent - template: echo_agent - user-id: user-123-456 # ✅ 关键标签 - annotations: - taiji.ai/user-id: user-123-456 - taiji.ai/channel-id: channel-789 - taiji.ai/created-at: "2026-01-11T10:00:00Z" -spec: - containers: - - name: agent - image: taiji/echo-agent:latest -``` - -### 2. 运行时间计算 - -```python -# Agent Manager -from datetime import datetime - -# Pod 创建时 -pod_start_time = pod.status.start_time - -# Pod 停止时 -pod_stop_time = pod.metadata.deletion_timestamp or datetime.utcnow() - -# 计算运行时间(秒) -running_time_seconds = int((pod_stop_time - pod_start_time).total_seconds()) -``` - -### 3. 工具使用记录 - -```python -# Agent 运行时记录工具调用 -tools_used = [] - -def call_tool(tool_name: str): - tools_used.append(tool_name) - # 执行工具... - -# 回调时发送 -await report_billing( - agent_name=agent_name, - user_id=user_id, - pod_running_time=120, - tools_used=list(set(tools_used)) # 去重 -) -``` - -## 性能优化建议 - -### 1. 使用 Redis 缓存 user_id - -```python -# 创建时 -redis.setex(f"agent:{agent_name}:user_id", 86400, user_id) - -# 读取时 -user_id = redis.get(f"agent:{agent_name}:user_id") -if not user_id: - # 回退到读取 Pod Labels - pod = v1.read_namespaced_pod(...) - user_id = pod.metadata.labels["user-id"] -``` - -### 2. 异步回调 - -```python -# 不阻塞 Pod 删除 -asyncio.create_task(report_billing(...)) -``` - -### 3. 批量处理 - -```python -# 收集一批回调请求 -pending_callbacks = [] - -# 定期批量发送 -async def flush_callbacks(): - if pending_callbacks: - await mcp_client.batch_report_billing(pending_callbacks) -``` - -## 监控指标 - -### Agent Manager 侧 - -- `agent_callback_total` - 回调总次数 -- `agent_callback_success` - 回调成功次数 -- `agent_callback_failed` - 回调失败次数 -- `agent_callback_retry` - 回调重试次数 -- `agent_callback_duration_seconds` - 回调耗时 - -### MCP Server 侧 - -- `billing_callback_received` - 收到的回调 -- `billing_record_created` - 创建的计费记录 -- `billing_callback_errors` - 回调错误数 - -## 相关文档 - -- [Agent用户身份识别机制说明.md](../Docs/Agent用户身份识别机制说明.md) -- [Agent-Manager回调配置指南.md](../Docs/Agent-Manager回调配置指南.md) -- [Agent-EU计费改进实施报告.md](./Agent-EU计费改进实施报告.md) diff --git a/services/mcp-server/app/agent_manager_client.py b/services/mcp-server/app/agent_manager_client.py index e5ddd46..f8e0994 100644 --- a/services/mcp-server/app/agent_manager_client.py +++ b/services/mcp-server/app/agent_manager_client.py @@ -183,6 +183,12 @@ class AgentStatusResult: conditions: Optional[List[Dict[str, Any]]] = None # Compatible with old fields template: Optional[str] = None + # ========== 访问信息字段(从 access_info 解析) ========== + external_ip: Optional[str] = None # 外网 IP 地址 + domain: Optional[str] = None # 域名 + domain_url: Optional[str] = None # 域名访问地址 + ip_url: Optional[str] = None # IP 访问地址 + # ====================================================== @property def is_healthy(self) -> bool: @@ -1024,6 +1030,16 @@ class AgentManagerClient: # New format: list endpoints = endpoints_data + # ========== 解析 access_info(域名和外网IP) ========== + access_info = data.get("access_info", {}) + external_ip = access_info.get("external_ip") + domain = access_info.get("domain") + domain_url = access_info.get("domain_url") + ip_url = access_info.get("ip_url") + # 优先使用 access_info 中的推荐地址,否则使用旧的 access_url + access_url = access_info.get("recommended") or data.get("access_url") + # ====================================================== + return AgentStatusResult( name=data["name"], namespace=data["namespace"], @@ -1035,13 +1051,18 @@ class AgentManagerClient: node_name=data.get("node_name"), labels=data.get("labels"), service_port=data.get("service_port"), - access_url=data.get("access_url"), + access_url=access_url, containers=containers, resources=data.get("resources"), endpoints=endpoints, conditions=data.get("conditions"), # Extract template from labels (for compatibility) - template=data.get("labels", {}).get("template") if data.get("labels") else None + template=data.get("labels", {}).get("template") if data.get("labels") else None, + # ========== 新增:访问信息字段 ========== + external_ip=external_ip, + domain=domain, + domain_url=domain_url, + ip_url=ip_url, ) async def get_agent_metrics(self, agent_name: str) -> AgentMetricsResult: diff --git a/services/mcp-server/app/routes/user.py b/services/mcp-server/app/routes/user.py index 630c023..ec0168b 100644 --- a/services/mcp-server/app/routes/user.py +++ b/services/mcp-server/app/routes/user.py @@ -1457,7 +1457,8 @@ async def deploy_agent( ) db.add(agent) - # 记录计费 + # 记录计费(包含访问信息) + access_info = result.access_info or {} billing_record = AgentBillingRecord( user_id=user_id, channel_id=channel_id, @@ -1470,6 +1471,13 @@ async def deploy_agent( cpu_used=quota.cpu_per_pod or "100m", memory_used=quota.memory_per_pod or "256Mi", replicas=req.instances, + # ========== 保存访问信息 ========== + external_ip=access_info.get("external_ip"), + domain=access_info.get("domain"), + domain_url=access_info.get("domain_url"), + access_url=access_info.get("recommended") or access_info.get("domain_url"), + service_port=result.service_port, + namespace=result.namespace, ) db.add(billing_record) @@ -2400,7 +2408,8 @@ async def deploy_platform_agent( ) db.add(agent) - # 记录计费 + # 记录计费(包含访问信息) + access_info = result.access_info or {} billing_record = AgentBillingRecord( user_id=user_id, channel_id=channel_id, @@ -2413,6 +2422,13 @@ async def deploy_platform_agent( cpu_used=quota.cpu_per_pod or "100m", memory_used=quota.memory_per_pod or "256Mi", replicas=1, + # ========== 保存访问信息 ========== + external_ip=access_info.get("external_ip"), + domain=access_info.get("domain"), + domain_url=access_info.get("domain_url"), + access_url=access_info.get("recommended") or access_info.get("domain_url"), + service_port=result.service_port, + namespace=result.namespace, ) db.add(billing_record) @@ -2425,6 +2441,8 @@ async def deploy_platform_agent( "status": result.status, "servicePort": result.service_port, "accessInfo": result.access_info, + "domain": access_info.get("domain"), + "domainUrl": access_info.get("domain_url"), "quotaRemaining": quota.pod_quota - quota.pod_used, }, message=f"平台 Agent {req.agentType} 部署成功" @@ -2521,7 +2539,8 @@ async def use_platform_agent( # 更新配额使用量 quota.pod_used += 1 - # 记录计费 + # 记录计费(包含访问信息) + access_info = result.access_info or {} billing_record = AgentBillingRecord( user_id=user_id, channel_id=channel_id, @@ -2534,6 +2553,13 @@ async def use_platform_agent( cpu_used=quota.cpu_per_pod or "100m", memory_used=quota.memory_per_pod or "256Mi", replicas=1, + # ========== 保存访问信息 ========== + external_ip=access_info.get("external_ip"), + domain=access_info.get("domain"), + domain_url=access_info.get("domain_url"), + access_url=access_info.get("recommended") or access_info.get("domain_url"), + service_port=result.service_port, + namespace=result.namespace, ) db.add(billing_record) @@ -2546,6 +2572,8 @@ async def use_platform_agent( "status": result.status, "servicePort": result.service_port, "accessInfo": result.access_info, + "domain": access_info.get("domain"), + "domainUrl": access_info.get("domain_url"), "quotaRemaining": quota.pod_quota - quota.pod_used, }, message=f"平台 Agent {req.agentType} 启动成功" @@ -3162,7 +3190,8 @@ async def create_custom_agent( quota.memory_used = memory_used + memory_request quota.agent_count = (quota.agent_count or 0) + 1 - # 记录计费 + # 记录计费(包含访问信息) + access_info = result.access_info or {} billing_record = AgentBillingRecord( user_id=user_id, channel_id=channel_id, @@ -3174,6 +3203,13 @@ async def create_custom_agent( cpu_used=req.cpuRequest, memory_used=req.memoryRequest, tools_used=req.tools if req.tools else [], + # ========== 保存访问信息 ========== + external_ip=access_info.get("external_ip"), + domain=access_info.get("domain"), + domain_url=access_info.get("domain_url"), + access_url=access_info.get("recommended") or access_info.get("domain_url"), + service_port=result.service_port, + namespace=result.namespace, ) db.add(billing_record) @@ -3203,6 +3239,8 @@ async def create_custom_agent( "status": result.status, "servicePort": result.service_port, "accessInfo": result.access_info, + "domain": access_info.get("domain"), + "domainUrl": access_info.get("domain_url"), "modelInjected": model_name is not None, "quotaRemaining": { "cpu": remaining_cpu - cpu_request, @@ -3951,9 +3989,16 @@ async def get_user_agents_info( "status": "unknown", "healthStatus": "unknown", "podIp": None, - "accessUrl": None, - "servicePort": None, - "namespace": "ai-agents", + # ========== 访问信息(优先使用数据库存储的值) ========== + "externalIp": record.external_ip, + "domain": record.domain, + "domainUrl": record.domain_url, + "accessUrl": record.access_url, + "servicePort": record.service_port, + "namespace": record.namespace or "ai-agents", + # ====================================================== + "hostIp": None, + "nodeName": None, "cpu": record.cpu_used, "memory": record.memory_used, "replicas": record.replicas, @@ -3961,30 +4006,34 @@ async def get_user_agents_info( "runningSeconds": int((datetime.utcnow() - record.start_time).total_seconds()) if record.start_time else 0, } - # 从 Agent Manager 获取详细状态 + # 从 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 - # 端点信息 + # ========== 更新访问信息(如果 Agent Manager 返回了最新值) ========== + if agent_status.external_ip: + agent_info["externalIp"] = agent_status.external_ip + if agent_status.domain: + agent_info["domain"] = agent_status.domain + if agent_status.domain_url: + agent_info["domainUrl"] = agent_status.domain_url + if agent_status.access_url: + agent_info["accessUrl"] = agent_status.access_url + if agent_status.service_port: + agent_info["servicePort"] = agent_status.service_port + if agent_status.namespace: + agent_info["namespace"] = agent_status.namespace + # ================================================================== + + # 端点信息(字符串列表,如 ["http://10.244.1.100:8080"]) 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 - ] + agent_info["endpoints"] = agent_status.endpoints except Exception as e: logger.warning(f"获取 Agent {record.agent_name} 状态失败: {e}") diff --git a/services/mcp-server/migrations/016_add_agent_access_info_fields.sql b/services/mcp-server/migrations/016_add_agent_access_info_fields.sql new file mode 100644 index 0000000..b82b285 --- /dev/null +++ b/services/mcp-server/migrations/016_add_agent_access_info_fields.sql @@ -0,0 +1,61 @@ +-- Migration 016: Add Agent Access Info Fields +-- Description: 为 agent_billing_records 表添加访问信息字段(域名、外网IP等) +-- Date: 2026-01-14 + +-- ========== 添加访问信息字段 ========== + +-- 外网 IP 地址 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS external_ip VARCHAR(45); + +-- 域名 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS domain VARCHAR(255); + +-- 域名访问地址 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS domain_url VARCHAR(500); + +-- 推荐访问地址 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS access_url VARCHAR(500); + +-- 服务端口 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS service_port INTEGER; + +-- K8s 命名空间 +ALTER TABLE agent_billing_records +ADD COLUMN IF NOT EXISTS namespace VARCHAR(100); + +-- ========== 添加索引 ========== + +-- 按域名查询索引(用于根据域名查找 Agent) +CREATE INDEX IF NOT EXISTS idx_agent_billing_domain +ON agent_billing_records(domain); + +-- ========== 添加注释 ========== + +COMMENT ON COLUMN agent_billing_records.external_ip IS '外网 IP 地址(Agent Manager 分配)'; +COMMENT ON COLUMN agent_billing_records.domain IS '域名(如 my-agent.taijiagent.com)'; +COMMENT ON COLUMN agent_billing_records.domain_url IS '域名访问地址(如 http://my-agent.taijiagent.com)'; +COMMENT ON COLUMN agent_billing_records.access_url IS '推荐访问地址(域名优先)'; +COMMENT ON COLUMN agent_billing_records.service_port IS '服务端口'; +COMMENT ON COLUMN agent_billing_records.namespace IS 'Kubernetes 命名空间'; + +-- ========== 验证迁移 ========== + +-- 检查字段是否添加成功 +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'agent_billing_records' + AND column_name = 'domain' + ) THEN + RAISE NOTICE 'Migration 016 completed successfully: domain field added'; + ELSE + RAISE EXCEPTION 'Migration 016 failed: domain field not found'; + END IF; +END $$; + diff --git a/services/mcp-server/migrations/run_016_migration.py b/services/mcp-server/migrations/run_016_migration.py new file mode 100644 index 0000000..b92b7c0 --- /dev/null +++ b/services/mcp-server/migrations/run_016_migration.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +Migration 016: Add Agent Access Info Fields + +为 agent_billing_records 表添加访问信息字段(域名、外网IP等) + +Usage: + python migrations/run_016_migration.py +""" + +import asyncio +import os +import sys + +# 添加项目根目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sqlalchemy import text +from database import engine + + +async def run_migration(): + """运行迁移""" + print("=" * 60) + print("Migration 016: Add Agent Access Info Fields") + print("=" * 60) + + # 读取 SQL 文件 + sql_file = os.path.join(os.path.dirname(__file__), "016_add_agent_access_info_fields.sql") + with open(sql_file, "r", encoding="utf-8") as f: + sql_content = f.read() + + # 分割 SQL 语句 + statements = [] + current_stmt = [] + in_do_block = False + + for line in sql_content.split("\n"): + line_stripped = line.strip() + + # 跳过注释和空行(但保留 DO 块中的内容) + if not in_do_block: + if line_stripped.startswith("--") or not line_stripped: + continue + + # 检测 DO 块开始 + if line_stripped.upper().startswith("DO $$"): + in_do_block = True + current_stmt.append(line) + continue + + # 检测 DO 块结束 + if in_do_block and line_stripped == "END $$;": + current_stmt.append(line) + statements.append("\n".join(current_stmt)) + current_stmt = [] + in_do_block = False + continue + + if in_do_block: + current_stmt.append(line) + continue + + # 普通语句处理 + current_stmt.append(line) + if line_stripped.endswith(";"): + stmt = "\n".join(current_stmt) + if stmt.strip(): + statements.append(stmt) + current_stmt = [] + + # 执行迁移 + async with engine.begin() as conn: + for i, stmt in enumerate(statements, 1): + try: + # 打印语句摘要 + stmt_preview = stmt.strip()[:80].replace("\n", " ") + if len(stmt.strip()) > 80: + stmt_preview += "..." + print(f"\n[{i}/{len(statements)}] Executing: {stmt_preview}") + + await conn.execute(text(stmt)) + print(f" ✓ Success") + except Exception as e: + error_msg = str(e) + # 忽略 "column already exists" 错误 + if "already exists" in error_msg.lower(): + print(f" ⚠ Skipped (already exists)") + else: + print(f" ✗ Error: {error_msg}") + raise + + print("\n" + "=" * 60) + print("Migration 016 completed successfully!") + print("=" * 60) + + +async def verify_migration(): + """验证迁移结果""" + print("\nVerifying migration...") + + async with engine.begin() as conn: + # 检查新字段是否存在 + result = await conn.execute(text(""" + SELECT column_name, data_type, character_maximum_length + FROM information_schema.columns + WHERE table_name = 'agent_billing_records' + AND column_name IN ('external_ip', 'domain', 'domain_url', 'access_url', 'service_port', 'namespace') + ORDER BY column_name + """)) + columns = result.fetchall() + + print(f"\nNew columns in agent_billing_records table:") + print("-" * 50) + for col in columns: + col_name, data_type, max_len = col + type_info = f"{data_type}({max_len})" if max_len else data_type + print(f" ✓ {col_name}: {type_info}") + + expected_columns = {'external_ip', 'domain', 'domain_url', 'access_url', 'service_port', 'namespace'} + found_columns = {col[0] for col in columns} + + if found_columns == expected_columns: + print(f"\n✓ All {len(expected_columns)} columns verified successfully!") + else: + missing = expected_columns - found_columns + if missing: + print(f"\n✗ Missing columns: {missing}") + return False + + return True + + +if __name__ == "__main__": + try: + asyncio.run(run_migration()) + asyncio.run(verify_migration()) + except KeyboardInterrupt: + print("\nMigration cancelled.") + sys.exit(1) + except Exception as e: + print(f"\nMigration failed: {e}") + sys.exit(1) + diff --git a/services/mcp-server/models.py b/services/mcp-server/models.py index 924ab27..c85a671 100644 --- a/services/mcp-server/models.py +++ b/services/mcp-server/models.py @@ -1198,6 +1198,15 @@ class AgentBillingRecord(BaseModel, Base): tools_used = Column(JSON, nullable=True) # 使用的工具列表 request_id = Column(String(100), nullable=True) # 请求 ID + # ========== 访问信息字段(Agent Manager 返回) ========== + external_ip = Column(String(45), nullable=True) # 外网 IP 地址 + domain = Column(String(255), nullable=True) # 域名 + domain_url = Column(String(500), nullable=True) # 域名访问地址 + access_url = Column(String(500), nullable=True) # 推荐访问地址 + service_port = Column(Integer, nullable=True) # 服务端口 + namespace = Column(String(100), nullable=True) # K8s 命名空间 + # ====================================================== + # 关联关系 user = relationship("User") channel = relationship("Channel") @@ -1207,6 +1216,7 @@ class AgentBillingRecord(BaseModel, Base): Index("idx_agent_billing_channel", channel_id), Index("idx_agent_billing_period", period_start, period_end), Index("idx_agent_billing_type", agent_type), + Index("idx_agent_billing_domain", domain), # 按域名查询索引 )