forked from xiaohei/taiji-AI-PAD
更新agent域名
This commit is contained in:
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
+87
-116
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 接口变动需求 |
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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 $$;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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), # 按域名查询索引
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user