This commit is contained in:
zhanggangyong
2026-02-02 14:08:13 +00:00
parent 1177f696f1
commit a498072888
17 changed files with 1968 additions and 2090 deletions
-673
View File
@@ -1,673 +0,0 @@
# 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
+302 -388
View File
@@ -1,54 +1,22 @@
# 外部数据工具 - 接口文档
# 外部数据工具 - 快速接入指南
> **版本**: 2026-01-23 v1.0
> **版本**: 2026-01-29 v2.0
> **基础路径**: `/api/user/external-tools`
> **认证方式**: Bearer Token(在请求头添加 `Authorization: Bearer <JWT Token>`)
> **设计原则**: 只需 3 步,连接您的 API
---
## 📊 业务流程
## 🚀 快速开始
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 外部数据工具流程 │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ① 创建外部工具 ② Agent Manager 生成 ③ 创建自定义 Agent │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ POST │ ───→ │ 生成 Pydantic │ ───→ │ POST │ │
│ │ /external-tools│ │ 工具代码文件 │ │ /custom-agents │ │
│ └───────────────┘ └───────────────┘ │ +externalTools │ │
│ │ │ └───────────────┘ │
│ ↓ ↓ │ │
│ 保存基本信息 返回 tool_ref_id 传递 tool_ref_ids │
│ 到 PostgreSQL 给 Agent Manager │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
### 创建工具只需提供 4 个信息:
### 核心概念
| 概念 | 说明 |
|------|------|
| **外部数据工具** | 用户创建的连接外部 API 的工具配置 |
| **tool_ref_id** | Agent Manager 生成工具后返回的标识,部署 Agent 时传递 |
| **工具状态** | pending(等待生成), active(可用), error(生成失败) |
### 存储职责划分
| 存储位置 | 存储内容 |
|---------|---------|
| **MCP-Server (PostgreSQL)** | 工具基本信息(名称、URL、方法)、tool_ref_id、状态 |
| **Agent Manager** | Pydantic 工具代码文件、完整配置(含敏感信息) |
---
## 🔐 通用请求头
```http
Content-Type: application/json
Authorization: Bearer <JWT Token>
```
| 信息 | 说明 | 示例 |
|------|------|------|
| **名称** | 给工具起个名字 | `"天气查询"` |
| **API 地址** | 您的 API URL | `"https://api.weather.com/forecast"` |
| **认证方式** | 三选一 | `"api_key"` / `"bearer"` / `"basic"` |
| **认证凭证** | 您的密钥或账密 | 见下方示例 |
---
@@ -70,17 +38,17 @@ Authorization: Bearer <JWT Token>
| 序号 | 接口 | 方法 | 说明 |
|:---:|------|------|------|
| 9 | `/api/user/toolkits` | POST | 创建工具集(最多 8 个工具) |
| 10 | `/api/user/toolkits` | GET | 获取工具集列表 |
| 11 | `/api/user/toolkits/{toolkit_id}` | GET | 获取工具集详情 |
| 12 | `/api/user/toolkits/{toolkit_id}` | PUT | 更新工具集 |
| 13 | `/api/user/toolkits/{toolkit_id}` | DELETE | 删除工具集 |
| 8 | `/api/user/toolkits` | POST | 创建工具集(最多 8 个工具) |
| 9 | `/api/user/toolkits` | GET | 获取工具集列表 |
| 10 | `/api/user/toolkits/{toolkit_id}` | GET | 获取工具集详情 |
| 11 | `/api/user/toolkits/{toolkit_id}` | PUT | 更新工具集 |
| 12 | `/api/user/toolkits/{toolkit_id}` | DELETE | 删除工具集 |
### 自定义 Agent 接口
| 序号 | 接口 | 方法 | 说明 |
|:---:|------|------|------|
| 8 | `/api/user/custom-agents` | POST | 创建自定义 Agent(支持外部工具/工具集) |
| 13 | `/api/user/custom-agents` | POST | 创建自定义 Agent(支持外部工具/工具集) |
---
@@ -97,96 +65,150 @@ POST /api/user/external-tools
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ✅ | 工具名称(1-100字符) |
| `description` | string | ❌ | 工具描述 |
| `description` | string | ❌ | 工具描述(帮助您记忆工具用途) |
| `url` | string | ✅ | API 端点 URL |
| `method` | string | ❌ | HTTP 方法,默认 POST |
| `headers` | object | ❌ | 自定义请求头 |
| `auth` | object | ❌ | 认证配置 |
| `request_params` | object | ❌ | URL 查询参数定义(JSON Schema) |
| `request_body` | object | ❌ | 请求体定义(JSON Schema) |
| `response_mapping` | object | ❌ | 响应字段映射 |
| `timeout` | integer | ❌ | 超时时间(秒),默认 30 |
| `retry` | object | ❌ | 重试配置 |
| `auth` | object | ✅ | 认证配置(见下方示例) |
| `example` | object | ❌ | 请求参数示例(强烈建议提供) |
### 认证配置 (auth)
> 💡 **提示**:`example` 字段帮助系统理解您的 API 参数结构,强烈建议填写。
### 请求格式
```json
{
"name": "天气查询",
"description": "查询城市天气(可选)",
"url": "https://api.weather.com/forecast",
"auth": {
"type": "api_key",
"secret": "sk-xxxxxxxxxxxx"
},
"example": {
"city": "北京",
"units": "metric"
}
}
```
就这么简单。系统会自动完成剩余配置。
---
## 🔐 认证方式(三选一)
### 方式 A:API Key
#### API Key 认证
```json
{
"auth": {
"type": "api_key",
"key": "sk-xxxxxxxxxxxx",
"in": "header",
"name": "X-API-Key"
"secret": "your-api-key-here"
}
}
```
#### Bearer Token 认证
### 方式 B:Bearer Token
```json
{
"auth": {
"type": "bearer",
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
"secret": "eyJhbGciOiJIUzI1NiIs..."
}
}
```
#### Basic Auth 认证
### 方式 C:账号密码(Basic Auth)
```json
{
"auth": {
"type": "basic",
"username": "admin",
"password": "password123"
"password": "your-password"
}
}
```
### 请求示例
---
## 📝 完整示例
### 示例 1:天气 API
```json
{
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"name": "天气查询",
"url": "https://api.weather.com/forecast",
"auth": {
"type": "api_key",
"key": "sk-xxxxxxxxxxxx",
"in": "header",
"name": "X-API-Key"
"secret": "sk-weather-12345"
},
"request_params": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称",
"required": true
}
}
},
"timeout": 30
"example": {
"city": "上海"
}
}
```
### 响应示例
### 示例 2:企业内部 CRM
```json
{
"name": "客户信息查询",
"url": "https://crm.company.com/api/customers",
"auth": {
"type": "bearer",
"secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"example": {
"customer_id": "C12345"
}
}
```
### 示例 3:数据库查询服务
```json
{
"name": "销售数据查询",
"url": "https://db.company.com/query",
"auth": {
"type": "basic",
"username": "readonly",
"password": "secure123"
},
"example": {
"table": "sales",
"date_range": "2026-01"
}
}
```
---
## ✅ 响应示例
### 创建成功
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"tool_ref_id": "tool-weather-abc123",
"name": "天气查询",
"status": "active",
"created_at": "2026-01-23T10:00:00Z"
},
"message": "外部数据工具创建成功"
"message": "工具创建成功,可以开始使用了"
}
```
### 创建失败
```json
{
"success": false,
"error": "无法连接到您提供的 API 地址,请检查 URL 是否正确"
}
```
@@ -209,36 +231,22 @@ Content-Type: multipart/form-data
### JSON 文件格式
与创建接口的请求格式相同:
```json
{
"name": "weather-api",
"description": "查询城市天气信息",
"url": "https://api.weather.com/v1/forecast",
"method": "GET",
"name": "天气查询",
"url": "https://api.weather.com/forecast",
"auth": {
"type": "api_key",
"key": "your-weather-api-key",
"in": "query",
"name": "apikey"
"secret": "sk-weather-12345"
},
"request_params": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称",
"required": true
}
}
},
"timeout": 10
"example": {
"city": "上海"
}
}
```
### 响应示例
同创建接口
---
## 3️⃣ 获取工具列表
@@ -266,10 +274,9 @@ GET /api/user/external-tools
"tools": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"name": "天气查询",
"description": "查询城市天气",
"url": "https://api.weather.com/forecast",
"auth_type": "api_key",
"status": "active",
"usage_count": 15,
@@ -306,12 +313,10 @@ GET /api/user/external-tools/{tool_id}
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"name": "天气查询",
"description": "查询城市天气",
"url": "https://api.weather.com/forecast",
"auth_type": "api_key",
"tool_ref_id": "tool-weather-abc123",
"status": "active",
"usage_count": 15,
"created_at": "2026-01-23T10:00:00Z",
@@ -320,8 +325,6 @@ GET /api/user/external-tools/{tool_id}
}
```
> **注意**:MCP-Server 只存储基本展示信息,不存储完整配置和敏感信息。
---
## 5️⃣ 更新工具配置
@@ -334,7 +337,15 @@ PUT /api/user/external-tools/{tool_id}
### 请求参数
与创建接口相同,需要传递完整配置(因为 MCP-Server 不存储完整配置)。
与创建接口相同,支持更新以下字段:
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ❌ | 工具名称 |
| `description` | string | ❌ | 工具描述 |
| `url` | string | ❌ | API 端点 URL |
| `auth` | object | ❌ | 认证配置 |
| `example` | object | ❌ | 请求参数示例 |
### 响应示例
@@ -343,12 +354,11 @@ PUT /api/user/external-tools/{tool_id}
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool-v2",
"tool_ref_id": "tool-weather-abc123-v2",
"name": "天气查询-v2",
"status": "active",
"updated_at": "2026-01-23T11:00:00Z"
},
"message": "外部数据工具更新成功"
"message": "工具更新成功"
}
```
@@ -370,7 +380,7 @@ DELETE /api/user/external-tools/{tool_id}
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000"
},
"message": "外部数据工具删除成功"
"message": "工具删除成功"
}
```
@@ -378,6 +388,8 @@ DELETE /api/user/external-tools/{tool_id}
## 7️⃣ 测试工具连接
创建后,您可以测试工具是否正常工作:
### 接口
```
@@ -388,14 +400,14 @@ POST /api/user/external-tools/{tool_id}/test
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `test_params` | object | ❌ | 测试参数 |
| `test_input` | object | ❌ | 测试参数 |
### 请求示例
```json
{
"test_params": {
"city": "北京"
"test_input": {
"city": "深圳"
}
}
```
@@ -412,8 +424,8 @@ POST /api/user/external-tools/{tool_id}/test
"sample_response": {
"status": "ok",
"data": {
"city": "北京",
"temperature": "15°C"
"city": "深圳",
"temperature": "22°C"
}
}
},
@@ -423,239 +435,11 @@ POST /api/user/external-tools/{tool_id}/test
---
## 8️⃣ 创建带有外部工具的自定义 Agent
> **注意**: 此功能已整合到原有的自定义 Agent 创建接口中
### 接口
```
POST /api/user/custom-agents
```
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ✅ | Agent 名称(1-63字符) |
| `template` | string | ✅ | Agent 模板名称(从 Agent Manager 获取) |
| `frameworkTemplate` | string | ❌ | 框架模板类型(A2A/langchain/MCP),默认 MCP |
| `description` | string | ❌ | Agent 描述 |
| `externalTools` | string[] | ❌ | **外部数据工具 ID 列表**(使用新的外部工具) |
| `tools` | string[] | ❌ | 内置工具 ID 列表 |
| `cpuRequest` | string | ❌ | CPU 请求量,默认 "100m" |
| `cpuLimit` | string | ❌ | CPU 限制量 |
| `memoryRequest` | string | ❌ | 内存请求量,默认 "128Mi" |
| `memoryLimit` | string | ❌ | 内存限制量 |
| `model` | string | ❌ | 使用的模型名称(会自动注入 LiteLLM 配置) |
| `envConfig` | object | ❌ | 自定义环境变量 |
### 请求示例(使用外部数据工具)
```json
{
"name": "my-data-agent",
"template": "custom_agent",
"description": "我的数据处理 Agent",
"externalTools": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
],
"cpuRequest": "500m",
"cpuLimit": "1000m",
"memoryRequest": "512Mi",
"memoryLimit": "1Gi",
"model": "gpt-4"
}
```
### 响应示例
```json
{
"success": true,
"data": {
"name": "my-data-agent",
"namespace": "ai-agents",
"status": "Pending",
"tools_attached": 2,
"servicePort": 8080,
"accessInfo": {
"domain": "my-data-agent.example.com",
"domain_url": "https://my-data-agent.example.com"
},
"modelInjected": true,
"quotaRemaining": {
"cpu": 3.5,
"memory": 7.0
}
},
"message": "自定义 Agent my-data-agent 创建成功"
}
```
---
## ❌ 错误响应
### 通用格式
```json
{
"detail": {
"error": "错误代码",
"message": "错误信息"
}
}
```
### 常见错误码
| HTTP状态码 | 错误代码 | 说明 |
|-----------|---------|------|
| 400 | `invalid_config` | 工具配置格式无效 |
| 400 | `invalid_url` | URL 格式无效 |
| 400 | `invalid_tool_id` | 无效的工具 ID 格式 |
| 400 | `tool_not_active` | 工具尚未就绪 |
| 400 | `missing_tools` | 必须选择至少一个工具 |
| 400 | `quota_insufficient` | CPU/内存配额不足 |
| 403 | `no_quota` | 没有自定义Agent配额 |
| 403 | `no_model_permission` | 没有指定模型的使用权限 |
| 404 | `tool_not_found` | 工具不存在 |
| 409 | `tool_name_exists` | 工具名称已存在 |
| 500 | `am_generate_failed` | Agent Manager 生成工具失败 |
---
## 📊 Agent Manager 接口(内部使用)
以下接口由 MCP-Server 内部调用,前端无需关注:
| 接口 | 方法 | 说明 |
|------|------|------|
| `POST /tools/generate` | 生成 Pydantic 工具文件 |
| `PUT /tools/{tool_ref_id}` | 更新工具文件 |
| `DELETE /tools/{tool_ref_id}` | 删除工具文件 |
| `POST /tools/{tool_ref_id}/test` | 测试工具连接 |
| `POST /agents` | 创建 Agent(支持 tool_refs 参数) |
---
## 📋 JSON 配置文件示例
### 示例 1:天气查询工具
```json
{
"name": "weather-api",
"description": "查询城市天气信息",
"url": "https://api.weather.com/v1/forecast",
"method": "GET",
"auth": {
"type": "api_key",
"key": "your-weather-api-key",
"in": "query",
"name": "apikey"
},
"request_params": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称",
"required": true
},
"units": {
"type": "string",
"description": "温度单位",
"enum": ["metric", "imperial"],
"default": "metric"
}
}
},
"timeout": 10
}
```
### 示例 2:企业内部 API
```json
{
"name": "internal-crm-api",
"description": "查询客户信息",
"url": "https://internal.company.com/api/v2/customers",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"auth": {
"type": "bearer",
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"request_body": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "客户ID",
"required": true
},
"include_orders": {
"type": "boolean",
"description": "是否包含订单信息",
"default": false
}
}
},
"response_mapping": {
"success_field": "code",
"success_value": 0,
"data_field": "data",
"error_field": "message"
},
"timeout": 30
}
```
### 示例 3:数据库查询服务
```json
{
"name": "db-query-service",
"description": "执行 SQL 查询",
"url": "https://db-gateway.company.com/query",
"method": "POST",
"auth": {
"type": "basic",
"username": "readonly",
"password": "secure-password-123"
},
"request_body": {
"type": "object",
"properties": {
"database": {
"type": "string",
"description": "数据库名称",
"required": true
},
"sql": {
"type": "string",
"description": "SQL 查询语句",
"required": true
}
}
},
"timeout": 60
}
```
---
## 🧰 工具集接口
工具集允许用户将多个外部数据工具组合在一起,方便部署自定义 Agent。
工具集允许您将多个外部数据工具组合在一起,方便部署自定义 Agent。
### 9️⃣ 创建工具集
### 8️⃣ 创建工具集
```
POST /api/user/toolkits
@@ -700,7 +484,7 @@ POST /api/user/toolkits
---
### 🔟 获取工具集列表
### 9️⃣ 获取工具集列表
```
GET /api/user/toolkits
@@ -738,7 +522,7 @@ GET /api/user/toolkits
---
### 1️⃣1️⃣ 获取工具集详情
### 🔟 获取工具集详情
```
GET /api/user/toolkits/{toolkit_id}
@@ -760,18 +544,16 @@ GET /api/user/toolkits/{toolkit_id}
"tools": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-api",
"description": "天气查询 API",
"url": "https://api.weather.com/current",
"method": "GET",
"name": "天气查询",
"description": "查询城市天气",
"url": "https://api.weather.com/forecast",
"status": "active"
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "stock-api",
"description": "股票查询 API",
"name": "股票查询",
"description": "查询股票价格",
"url": "https://api.stock.com/price",
"method": "GET",
"status": "active"
}
],
@@ -784,7 +566,7 @@ GET /api/user/toolkits/{toolkit_id}
---
### 1️⃣2️⃣ 更新工具集
### 1️⃣1️⃣ 更新工具集
```
PUT /api/user/toolkits/{toolkit_id}
@@ -800,7 +582,7 @@ PUT /api/user/toolkits/{toolkit_id}
---
### 1️⃣3️⃣ 删除工具集
### 1️⃣2️⃣ 删除工具集
```
DELETE /api/user/toolkits/{toolkit_id}
@@ -810,34 +592,166 @@ DELETE /api/user/toolkits/{toolkit_id}
---
## 📌 在自定义 Agent 中使用工具集
## 1️⃣3️⃣ 创建带有外部工具的自定义 Agent
创建自定义 Agent 时,可以通过 `toolkit` 字段指定工具集:
### 接口
```
POST /api/user/custom-agents
```
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ✅ | Agent 名称(1-63字符) |
| `description` | string | ❌ | Agent 描述 |
| `externalTools` | string[] | ❌ | 外部数据工具 ID 列表 |
| `toolkit` | string | ❌ | 工具集 ID |
| `model` | string | ❌ | 使用的模型名称 |
| `cpuRequest` | string | ❌ | CPU 请求量,默认 "100m" |
| `memoryRequest` | string | ❌ | 内存请求量,默认 "128Mi" |
> 💡 **说明**:模板、环境变量等配置由系统自动处理,无需手动指定。
### 请求示例
使用外部数据工具:
```json
{
"name": "my-data-agent",
"template": "custom_agent",
"toolkit": "660e8400-e29b-41d4-a716-446655440002",
"cpuRequest": "500m",
"memoryRequest": "512Mi"
"description": "我的数据处理 Agent",
"externalTools": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
],
"model": "gpt-4"
}
```
也可以同时使用工具集和单独的工具(会自动合并去重):
使用工具集:
```json
{
"name": "my-data-agent",
"toolkit": "660e8400-e29b-41d4-a716-446655440002",
"model": "gpt-4"
}
```
同时使用工具集和单独工具(会自动合并去重):
```json
{
"name": "my-data-agent",
"template": "custom_agent",
"toolkit": "660e8400-e29b-41d4-a716-446655440002",
"externalTools": ["770e8400-e29b-41d4-a716-446655440003"],
"cpuRequest": "500m",
"memoryRequest": "512Mi"
"model": "gpt-4"
}
```
### 响应示例
```json
{
"success": true,
"data": {
"name": "my-data-agent",
"namespace": "ai-agents",
"status": "Pending",
"servicePort": 8080,
"accessInfo": {
"domain": "my-data-agent.example.com",
"domain_url": "https://my-data-agent.example.com"
},
"quotaRemaining": {
"cpu": 0.9,
"memory": 0.875
}
},
"message": "自定义 Agent my-data-agent 创建成功"
}
```
---
**如有问题,请联系开发团队。**
## ❌ 错误响应
### 通用格式
```json
{
"success": false,
"error": "错误信息描述"
}
```
### 常见错误
| HTTP状态码 | 错误说明 |
|-----------|---------|
| 400 | 请求参数无效(如 URL 格式错误、名称过长等) |
| 401 | 未登录或 Token 已过期 |
| 403 | 没有操作权限或配额不足 |
| 404 | 工具或工具集不存在 |
| 409 | 工具名称已存在 |
| 500 | 服务器内部错误 |
---
## ❓ 常见问题
**Q: 我的 API 是 GET 请求,怎么办?**
A: 不需要指定,系统会自动检测。
**Q: 我的 API 需要特殊的请求头怎么办?**
A: 大多数情况下不需要。如果确实需要,请联系技术支持。
**Q: example 字段必须填吗?**
A: 强烈建议填写。这帮助系统理解您的 API 参数结构。
**Q: 认证信息会被暴露吗?**
A: 不会。您的认证凭证会被安全存储,不会在任何响应中返回。
---
## 🔧 高级配置(可选)
对于有特殊需求的用户,可以提供额外的配置:
```json
{
"name": "...",
"url": "...",
"auth": { ... },
"example": { ... },
"advanced": {
"method": "PUT",
"headers": { "X-Custom-Header": "value" },
"timeout": 60
}
}
```
> ⚠️ 注意:大多数情况下不需要使用高级配置,系统会自动处理。
---
## 📊 系统自动处理
以下配置由系统自动推断,您无需关心:
| 配置项 | 自动处理方式 |
|--------|-------------|
| HTTP 方法 | 通过探测 API 自动检测 |
| Content-Type | 根据请求结构自动设置 |
| 认证头名称和位置 | 根据认证类型自动配置 |
| 请求参数结构 | 从 example 字段推断 |
| 响应解析 | 通过测试调用自动检测 |
| 超时和重试 | 使用合理默认值 |
---
**如有问题,请联系开发团队。**
@@ -1,568 +0,0 @@
# Agent 发送给 Agent Manager 的数据结构文档
本文档详细说明 mcp-server 中**平台 Agent** 和**自定义 Agent** 发送给 Agent Manager 服务端的请求数据结构和参数说明。
## 目录
1. [统一接口说明](#统一接口说明)
2. [平台 Agent 数据结构](#平台-agent-数据结构)
3. [自定义 Agent 数据结构](#自定义-agent-数据结构)
4. [请求参数详细说明](#请求参数详细说明)
5. [代码位置](#代码位置)
---
## 统一接口说明
无论是平台 Agent 还是自定义 Agent,最终都通过 **`POST /agents`** 接口发送请求到 Agent Manager。
**接口路径**: `POST {AGENT_MANAGER_URL}/agents`
**代码位置**: `services/mcp-server/app/agent_manager_client.py`
**统一方法**: `create_agent()`
---
## 平台 Agent 数据结构
### 请求体结构
```json
{
"name": "echo-agent-alice1234-abc123",
"template": "echo_agent",
"config": {
"user_id": "alice",
"cpu_request": "100m",
"cpu_limit": "500m",
"memory_request": "128Mi",
"memory_limit": "512Mi",
"replicas": 1
},
"env": {
"LLM_BASE_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io",
"OPENAI_API_BASE": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io",
"OPENAI_API_KEY": "sk-...",
"MODEL_NAME": "gpt-4",
"LITELLM_MODEL": "gpt-4"
}
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | ✅ | Agent 实例名称(1-63字符,小写字母、数字、连字符) |
| `template` | string | ✅ | 平台模板名称(如 `echo_agent`, `jina_search_agent`) |
| `config` | object | ✅ | 资源配置对象 |
| `config.user_id` | string | ✅ | 用户 ID(用于资源隔离和计费) |
| `config.cpu_request` | string | ✅ | CPU 请求量(如 `"100m"`) |
| `config.cpu_limit` | string | ✅ | CPU 限制量(如 `"500m"`) |
| `config.memory_request` | string | ✅ | 内存请求量(如 `"128Mi"`) |
| `config.memory_limit` | string | ✅ | 内存限制量(如 `"512Mi"`) |
| `config.replicas` | integer | ✅ | 副本数量(平台 Agent 默认为 1) |
| `env` | object | ✅ | 环境变量(至少包含 `LLM_BASE_URL`) |
| `env.LLM_BASE_URL` | string | ✅ | LiteLLM 服务地址(固定值,自动注入) |
| `env.OPENAI_API_BASE` | string | 否 | LiteLLM API 基础地址(如果用户指定了模型) |
| `env.OPENAI_API_KEY` | string | 否 | LiteLLM API 密钥(如果用户指定了模型) |
| `env.MODEL_NAME` | string | 否 | 模型名称(如果用户指定了模型) |
| `env.LITELLM_MODEL` | string | 否 | LiteLLM 模型名称(如果用户指定了模型) |
### 代码实现位置
**主要调用位置**:
- `services/mcp-server/app/routes/user.py` - `deploy_platform_agent()` (行 2261-2512)
- `services/mcp-server/app/routes/user.py` - `deploy_platform_agent_instances()` (行 1310-1509)
**关键代码片段**:
```808:847:services/mcp-server/app/agent_manager_client.py
payload: Dict[str, Any] = {
"name": name,
"template": template
}
if config:
payload["config"] = config.to_dict()
# 构建环境变量,确保 LLM_BASE_URL 始终被传递(平台 Agent 和自定义 Agent 都需要)
final_env = {"LLM_BASE_URL": LLM_BASE_URL}
if env:
# 用户传入的环境变量会覆盖默认值(但通常不应覆盖 LLM_BASE_URL)
final_env.update(env)
payload["env"] = final_env
```
**平台 Agent 创建示例**:
```2421:2445:services/mcp-server/app/routes/user.py
# 创建平台 Agent 实例
agent_config = AgentConfig(
user_id=str(user_id),
cpu_request=quota.cpu_per_pod or "100m",
cpu_limit=quota.cpu_per_pod or "500m",
memory_request=quota.memory_per_pod or "128Mi",
memory_limit=quota.memory_per_pod or "512Mi",
replicas=1, # 平台 Agent 默认单副本
)
# 如果有环境变量,使用 create_agent;否则使用 create_platform_agent
if env_vars:
result = await client.create_agent(
name=instance_name,
template=req.agentType,
config=agent_config,
env=env_vars
)
else:
result = await client.create_platform_agent(
name=instance_name,
template=req.agentType,
user_id=str(user_id),
config=agent_config
)
```
---
## 自定义 Agent 数据结构
### 请求体结构
```json
{
"name": "my-mysql-agent",
"template": "mysql_agent",
"config": {
"user_id": "alice",
"cpu_request": "1000m",
"cpu_limit": "2000m",
"memory_request": "1Gi",
"memory_limit": "2Gi",
"replicas": 1
},
"env": {
"LLM_BASE_URL": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io",
"FRAMEWORK_TYPE": "MCP",
"MYSQL_HOST": "mysql.example.com",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "password123",
"MYSQL_DATABASE": "mydb",
"OPENAI_API_BASE": "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io",
"OPENAI_API_KEY": "sk-...",
"MODEL_NAME": "gpt-4",
"LITELLM_MODEL": "gpt-4",
"TOOLS": "[\"tool-uuid-1\", \"tool-uuid-2\"]",
"TOOLS_CONFIG": "[{\"id\": \"tool-uuid-1\", \"name\": \"mysql_query\", \"endpoint\": \"...\", ...}]",
"AGENT_ID": "my-mysql-agent-1704067200",
"AGENT_ROLE": "data_analyzer",
"AGENT_CAPABILITIES": "[\"sql_query\", \"data_analysis\"]"
}
}
```
### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | ✅ | Agent 名称(1-63字符,小写字母、数字、连字符) |
| `template` | string | ✅ | 自定义模板名称(如 `mysql_agent`, `postgresql_agent`) |
| `config` | object | ✅ | 资源配置对象 |
| `config.user_id` | string | ✅ | 用户 ID |
| `config.cpu_request` | string | ✅ | CPU 请求量(用户自定义,如 `"1000m"`) |
| `config.cpu_limit` | string | ✅ | CPU 限制量(用户自定义,如 `"2000m"`) |
| `config.memory_request` | string | ✅ | 内存请求量(用户自定义,如 `"1Gi"`) |
| `config.memory_limit` | string | ✅ | 内存限制量(用户自定义,如 `"2Gi"`) |
| `config.replicas` | integer | ✅ | 副本数量(自定义 Agent 默认为 1) |
| `env` | object | ✅ | 环境变量(必需,包含数据库连接、API Key 等) |
| `env.LLM_BASE_URL` | string | ✅ | LiteLLM 服务地址(固定值,自动注入) |
| `env.FRAMEWORK_TYPE` | string | ✅ | 框架类型(`MCP` / `A2A` / `langchain`) |
| `env.ENDPOINT` | string | 否 | 用户自定义终结点 |
| `env.API_KEY` | string | 否 | 用户 API 密钥 |
| `env.MYSQL_HOST` | string | 否 | MySQL 主机地址(模板相关) |
| `env.MYSQL_USER` | string | 否 | MySQL 用户名(模板相关) |
| `env.MYSQL_PASSWORD` | string | 否 | MySQL 密码(模板相关) |
| `env.MYSQL_DATABASE` | string | 否 | MySQL 数据库名(模板相关) |
| `env.OPENAI_API_BASE` | string | 否 | LiteLLM API 基础地址(如果指定了模型) |
| `env.OPENAI_API_KEY` | string | 否 | LiteLLM API 密钥(如果指定了模型或自动注入) |
| `env.MODEL_NAME` | string | 否 | 模型名称(如果指定了模型) |
| `env.LITELLM_MODEL` | string | 否 | LiteLLM 模型名称(如果指定了模型) |
| `env.TOOLS` | string | 否 | 工具 ID 列表(JSON 字符串格式) |
| `env.TOOLS_CONFIG` | string | 否 | 工具详细配置(JSON 字符串格式,包含 endpoint、method、schema 等) |
| `env.AGENT_ID` | string | 否 | A2A 框架专用:Agent ID |
| `env.AGENT_ROLE` | string | 否 | A2A 框架专用:Agent 角色(如 `data_analyzer`) |
| `env.AGENT_CAPABILITIES` | string | 否 | A2A 框架专用:Agent 能力列表(JSON 字符串格式) |
### 环境变量构建优先级
自定义 Agent 的环境变量按以下优先级合并(从低到高):
1. **工具的 `env_config`**(如果选择了工具)
2. **请求中的 `envConfig`**(用户手动配置)
3. **系统自动注入**(如 `LLM_BASE_URL`、模型配置等)
### 代码实现位置
**主要调用位置**:
- `services/mcp-server/app/routes/user.py` - `create_custom_agent()` (行 2871-3341)
**关键代码片段**:
```3067:3087:services/mcp-server/app/routes/user.py
# ========== 构建环境变量 ==========
# 1. 先使用工具的环境变量配置作为基础
env_vars = tool_env_config.copy()
# 2. 再合并请求中的 envConfig(请求中的优先)
if req.envConfig:
env_vars.update(req.envConfig)
# 3. 注入框架模板类型
env_vars["FRAMEWORK_TYPE"] = framework_template
if req.endpoint:
env_vars["ENDPOINT"] = req.endpoint
if req.apiKey:
env_vars["API_KEY"] = req.apiKey
logger.info(
f"环境变量构建完成: tool_env_keys={list(tool_env_config.keys())}, "
f"req_env_keys={list((req.envConfig or {}).keys())}, "
f"final_env_keys={list(env_vars.keys())}"
)
# =================================
```
**工具配置传递**:
```3089:3145:services/mcp-server/app/routes/user.py
# ========== 工具配置传递给 Agent Manager ==========
import json
if req.tools:
# 1. 传递工具 ID 列表(JSON 字符串格式)
env_vars["TOOLS"] = json.dumps(req.tools)
# 2. 查询工具详情,构建完整工具配置
try:
tool_ids = []
for tid in req.tools:
try:
tool_ids.append(PyUUID(tid))
except (ValueError, TypeError):
logger.warning(f"无效的工具 ID 格式: {tid}")
if tool_ids:
tool_result = await db.execute(
select(Tool).where(
Tool.id.in_(tool_ids),
or_(
Tool.is_public == True,
Tool.owner_id == PyUUID(user_id)
)
)
)
tools = tool_result.scalars().all()
# 构建工具配置列表(包含 Agent 运行时需要的信息)
tools_config = []
for tool in tools:
tool_config = {
"id": str(tool.id),
"name": tool.name,
"description": tool.description,
"endpoint": tool.endpoint,
"method": tool.method,
"schema": tool.schema,
"timeout": tool.timeout,
"auth_type": tool.auth_type,
}
# 如果是用户自己的工具,传递认证配置
if tool.owner_id and str(tool.owner_id) == user_id and tool.auth_config:
tool_config["auth_config"] = tool.auth_config
tools_config.append(tool_config)
# 传递工具详细配置(供 Agent 运行时使用)
if tools_config:
env_vars["TOOLS_CONFIG"] = json.dumps(tools_config)
logger.info(
f"工具配置已准备: user_id={user_id}, tool_count={len(tools_config)}, "
f"tool_names={[t['name'] for t in tools_config]}"
)
except Exception as e:
logger.warning(f"查询工具详情失败,仅传递工具 ID 列表: {str(e)}")
# ================================================
```
**A2A 框架配置**:
```3147:3168:services/mcp-server/app/routes/user.py
# ========== A2A 框架配置 ==========
if framework_template == "A2A":
import time as time_module
# 生成 Agent ID(使用名称和时间戳确保唯一性)
agent_id = f"{req.name}-{int(time_module.time())}"
env_vars["AGENT_ID"] = agent_id
# 设置 Agent 角色
agent_role = req.agentRole or env_vars.get("AGENT_ROLE", "custom_agent")
env_vars["AGENT_ROLE"] = agent_role
# 设置 Agent 能力
capabilities = req.agentCapabilities or req.tools or []
if capabilities:
env_vars["AGENT_CAPABILITIES"] = json.dumps(capabilities)
logger.info(
f"A2A 配置已准备: agent_id={agent_id}, agent_role={agent_role}, "
f"capabilities={capabilities}"
)
# ==================================
```
**自定义 Agent 创建调用**:
```3240:3260:services/mcp-server/app/routes/user.py
try:
client = get_agent_manager_client()
# 创建 Agent 配置
agent_config = AgentConfig(
user_id=str(user_id),
cpu_request=req.cpuRequest,
cpu_limit=req.cpuLimit or req.cpuRequest,
memory_request=req.memoryRequest,
memory_limit=req.memoryLimit or req.memoryRequest,
replicas=1, # 自定义 Agent 默认单副本
)
# 创建自定义 Agent(使用从工具获取的模板名称)
result = await client.create_custom_agent(
name=req.name,
template=template_name, # 使用从工具获取或请求中指定的模板
user_id=str(user_id),
env_vars=env_vars,
config=agent_config
)
```
---
## 请求参数详细说明
### AgentConfig 对象
**定义位置**: `services/mcp-server/app/agent_manager_client.py` (行 33-62)
```python
@dataclass
class AgentConfig:
user_id: Optional[str] = None
cpu_request: Optional[str] = "100m"
cpu_limit: Optional[str] = "500m"
memory_request: Optional[str] = "128Mi"
memory_limit: Optional[str] = "512Mi"
replicas: Optional[int] = 1
```
**转换为字典方法**:
```47:62:services/mcp-server/app/agent_manager_client.py
def to_dict(self) -> Dict[str, Any]:
"""Converts to API request format"""
result = {}
if self.user_id:
result["user_id"] = self.user_id
if self.cpu_request:
result["cpu_request"] = self.cpu_request
if self.cpu_limit:
result["cpu_limit"] = self.cpu_limit
if self.memory_request:
result["memory_request"] = self.memory_request
if self.memory_limit:
result["memory_limit"] = self.memory_limit
if self.replicas is not None:
result["replicas"] = self.replicas
return result
```
### 环境变量说明
#### 固定环境变量
| 变量名 | 值 | 说明 | 来源 |
|--------|-----|------|------|
| `LLM_BASE_URL` | `https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io` | LiteLLM 服务地址 | 环境变量 `LLM_BASE_URL`,默认值见代码 |
**代码位置**: `services/mcp-server/app/agent_manager_client.py` (行 20-21)
```20:21:services/mcp-server/app/agent_manager_client.py
# LLM_BASE_URL - 所有 Agent(平台和自定义)都必须传递的固定参数
LLM_BASE_URL = os.getenv("LLM_BASE_URL", "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io")
```
#### 可选环境变量(根据用户配置注入)
| 变量名 | 说明 | 注入条件 |
|--------|------|----------|
| `OPENAI_API_BASE` | LiteLLM API 基础地址 | 用户指定了模型或自动注入 |
| `OPENAI_API_KEY` | LiteLLM API 密钥 | 用户指定了模型或自动注入 |
| `MODEL_NAME` | 模型名称 | 用户指定了模型 |
| `LITELLM_MODEL` | LiteLLM 模型名称 | 用户指定了模型 |
| `FRAMEWORK_TYPE` | 框架类型(MCP/A2A/langchain) | 自定义 Agent 必填 |
| `ENDPOINT` | 用户自定义终结点 | 用户提供了 endpoint |
| `API_KEY` | 用户 API 密钥 | 用户提供了 apiKey |
| `TOOLS` | 工具 ID 列表(JSON 字符串) | 用户选择了工具 |
| `TOOLS_CONFIG` | 工具详细配置(JSON 字符串) | 用户选择了工具 |
| `AGENT_ID` | A2A Agent ID | 框架类型为 A2A |
| `AGENT_ROLE` | A2A Agent 角色 | 框架类型为 A2A |
| `AGENT_CAPABILITIES` | A2A Agent 能力列表(JSON 字符串) | 框架类型为 A2A |
#### 模板相关环境变量
根据不同的模板类型,可能需要不同的环境变量。例如:
- **MySQL Agent**: `MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE`
- **PostgreSQL Agent**: `PG_HOST`, `PG_USER`, `PG_PASSWORD`, `PG_DATABASE`
- **其他模板**: 参考 Agent Manager 的模板定义
**获取模板环境变量要求**:
```742:759:services/mcp-server/app/agent_manager_client.py
async def get_template(self, template_name: str) -> TemplateInfo:
"""
Get template details
Call: GET /templates/{template_name}
Args:
template_name: Template name
Returns:
Template information
"""
data = await self._request("GET", f"/templates/{template_name}")
return TemplateInfo(
template=data["template"],
port=data.get("port"),
env_info=data.get("env_info", {})
)
```
### 工具配置格式
#### TOOLS 环境变量
**格式**: JSON 字符串数组
**示例**:
```json
["tool-uuid-1", "tool-uuid-2", "tool-uuid-3"]
```
#### TOOLS_CONFIG 环境变量
**格式**: JSON 字符串数组,每个元素包含工具的完整配置
**示例**:
```json
[
{
"id": "tool-uuid-1",
"name": "mysql_query",
"description": "MySQL 查询工具",
"endpoint": "https://api.example.com/mysql/query",
"method": "POST",
"schema": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
},
"timeout": 30,
"auth_type": "bearer",
"auth_config": {
"token": "secret-token"
}
}
]
```
**代码位置**: `services/mcp-server/app/routes/user.py` (行 3117-3137)
---
## 代码位置
### 核心文件
1. **Agent Manager 客户端**
- 文件: `services/mcp-server/app/agent_manager_client.py`
- 主要类: `AgentManagerClient`
- 主要方法: `create_agent()`, `create_platform_agent()`, `create_custom_agent()`
2. **平台 Agent 路由**
- 文件: `services/mcp-server/app/routes/user.py`
- 主要函数:
- `deploy_platform_agent()` (行 2261-2512)
- `deploy_platform_agent_instances()` (行 1310-1509)
3. **自定义 Agent 路由**
- 文件: `services/mcp-server/app/routes/user.py`
- 主要函数: `create_custom_agent()` (行 2871-3341)
4. **数据模型定义**
- 文件: `services/mcp-server/app/schemas.py`
- 主要类:
- `CreateCustomAgentRequest` (行 815-851)
- `UsePlatformAgentRequest` (行 794-799)
### 关键方法调用链
#### 平台 Agent
```
deploy_platform_agent()
└─> AgentManagerClient.create_agent()
└─> AgentManagerClient._request("POST", "/agents", json=payload)
```
#### 自定义 Agent
```
create_custom_agent()
└─> AgentManagerClient.create_custom_agent()
└─> AgentManagerClient.create_agent()
└─> AgentManagerClient._request("POST", "/agents", json=payload)
```
---
## 总结
### 平台 Agent vs 自定义 Agent 对比
| 特性 | 平台 Agent | 自定义 Agent |
|------|-----------|-------------|
| **模板来源** | 平台预定义模板 | 用户选择的数据存储模板 |
| **环境变量** | 最少(仅 LLM_BASE_URL + 可选的模型配置) | 丰富(包含数据库连接、API Key、工具配置等) |
| **资源配置** | 从配额获取 | 用户自定义 |
| **框架类型** | 固定(由模板决定) | 用户选择(MCP/A2A/langchain) |
| **工具配置** | 不支持 | 支持(通过 TOOLS 和 TOOLS_CONFIG) |
| **A2A 配置** | 不支持 | 支持(如果 frameworkTemplate="A2A") |
### 共同点
1. 都通过 `POST /agents` 接口发送请求
2. 都包含 `name`, `template`, `config`, `env` 四个主要字段
3. 都自动注入 `LLM_BASE_URL` 环境变量
4. 都支持模型配置注入(如果用户指定了模型)
---
**文档生成时间**: 2025-01-XX
**代码版本**: 基于当前代码库状态
-9
View File
@@ -1,9 +0,0 @@
FROM python:3.11-slim
WORKDIR /workspace
RUN apt-get update \
&& apt-get install -y --no-install-recommends git curl build-essential \
&& rm -rf /var/lib/apt/lists/*
CMD ["sleep", "infinity"]
+1 -1
View File
@@ -65,7 +65,7 @@ services:
- NATS_URL=nats://nats:4222
- LITELLM_URL=${LITELLM_URL:-https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io}
- LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY:-sk-1f06b8f0d2e34c9b8a9f3d75a1c4e9b7-7e3a2c6bd9f441d8}
- AGENT_MANAGER_URL=${AGENT_MANAGER_URL:-http://host.docker.internal:8000}
- AGENT_MANAGER_URL=${AGENT_MANAGER_URL:-http://20.212.121.126}
- ENVIRONMENT=production
- ENABLE_TEST_MODE=false
- SMTP_SERVER=${SMTP_SERVER:-smtp.189.cn}
+28 -4
View File
@@ -1,36 +1,60 @@
# ConfigMap for Taiji AI-PAD
# 包含非敏感配置信息
apiVersion: v1
kind: ConfigMap
metadata:
name: taiji-config
namespace: taiji-ai
labels:
app: taiji-ai-pad
environment: production
data:
# ===========================================
# 应用环境配置
# ===========================================
APP_ENV: "production"
ENVIRONMENT: "production"
LOG_LEVEL: "INFO"
DEBUG: "false"
# NATS配置
# ===========================================
# NATS配置 (K8s内部服务)
# ===========================================
NATS_URL: "nats://nats:4222"
# LiteLLM网关配置
# ===========================================
# LiteLLM网关配置 (Azure Container Apps)
# ===========================================
LITELLM_URL: "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io"
LLM_BASE_URL: "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io"
# Agent Manager 配置(K8s Agent 管理服务,在 agent-manager namespace)
# ===========================================
# Agent Manager 配置 (AKS内部服务)
# 服务部署在 agent-manager namespace
# ===========================================
AGENT_MANAGER_URL: "http://agent-manager.agent-manager.svc.cluster.local:80"
AGENT_K8S_NAMESPACE: "ai-agents"
# ===========================================
# OpenRouter配置
# ===========================================
OPENROUTER_BASE_URL: "https://openrouter.ai/api/v1"
# ===========================================
# RapidAPI配置
# ===========================================
RAPIDAPI_HOST: "rapidapi.com"
# ===========================================
# JWT配置
# ===========================================
JWT_ALGORITHM: "HS256"
JWT_EXPIRE_MINUTES: "1440"
# ===========================================
# SMTP邮箱配置
# ===========================================
SMTP_SERVER: "smtp.189.cn"
SMTP_PORT: "465"
SMTP_EMAIL: "taijiagent@189.cn"
SMTP_USE_SSL: "true"
SMTP_USE_SSL: "true"
+317
View File
@@ -0,0 +1,317 @@
#!/bin/bash
# MCP Server 部署脚本 - Azure AKS
# 用法: ./deploy-mcp-server.sh [build|deploy|all|status|logs|rollback]
set -e
# ===== 配置变量 =====
ACR_NAME="taiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
IMAGE_NAME="mcp-server"
IMAGE_TAG="${IMAGE_TAG:-latest}"
NAMESPACE="taiji-ai"
K8S_DIR="$(dirname "$0")"
MCP_SERVER_DIR="$(dirname "$0")/../services/mcp-server"
# ===== 颜色输出 =====
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# ===== 检查先决条件 =====
check_prerequisites() {
log_info "检查先决条件..."
# 检查 kubectl
if ! command -v kubectl &> /dev/null; then
log_error "kubectl 未安装"
exit 1
fi
# 检查 az CLI
if ! command -v az &> /dev/null; then
log_error "Azure CLI 未安装"
exit 1
fi
# 检查 docker
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装"
exit 1
fi
# 检查 kubectl 连接
if ! kubectl cluster-info &> /dev/null; then
log_error "无法连接到 Kubernetes 集群,请先运行: az aks get-credentials --resource-group <RG> --name <AKS_NAME>"
exit 1
fi
log_success "先决条件检查通过"
}
# ===== 登录 ACR =====
login_acr() {
log_info "登录 Azure Container Registry..."
az acr login --name ${ACR_NAME}
log_success "ACR 登录成功"
}
# ===== 构建镜像 =====
build_image() {
log_info "构建 Docker 镜像..."
cd "${MCP_SERVER_DIR}"
# 使用 ACR 构建(推荐)
log_info "使用 ACR Tasks 构建镜像..."
az acr build \
--registry ${ACR_NAME} \
--image ${IMAGE_NAME}:${IMAGE_TAG} \
--file Dockerfile \
.
# 也打上 latest 标签
if [ "${IMAGE_TAG}" != "latest" ]; then
az acr import \
--name ${ACR_NAME} \
--source ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} \
--image ${IMAGE_NAME}:latest \
--force
fi
log_success "镜像构建完成: ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
}
# ===== 本地构建镜像(备选) =====
build_image_local() {
log_info "本地构建并推送 Docker 镜像..."
cd "${MCP_SERVER_DIR}"
# 本地构建
docker build -t ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} .
# 推送到 ACR
docker push ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}
# 也打上 latest 标签
if [ "${IMAGE_TAG}" != "latest" ]; then
docker tag ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:latest
docker push ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:latest
fi
log_success "镜像推送完成: ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}"
}
# ===== 创建命名空间 =====
create_namespace() {
log_info "创建命名空间 ${NAMESPACE}..."
kubectl apply -f "${K8S_DIR}/namespace.yaml" || true
log_success "命名空间已创建"
}
# ===== 应用配置 =====
apply_configs() {
log_info "应用 ConfigMap 和 Secrets..."
# 应用 ConfigMap
kubectl apply -f "${K8S_DIR}/configmap.yaml"
# 应用 Secrets
kubectl apply -f "${K8S_DIR}/secrets.yaml"
log_success "配置已应用"
}
# ===== 部署服务 =====
deploy_service() {
log_info "部署 MCP Server..."
# 应用部署配置
kubectl apply -f "${K8S_DIR}/mcp-server.yaml"
# 等待部署完成
log_info "等待部署完成..."
kubectl rollout status deployment/mcp-server -n ${NAMESPACE} --timeout=300s
log_success "MCP Server 部署完成"
}
# ===== 查看状态 =====
show_status() {
log_info "MCP Server 部署状态:"
echo ""
echo "=== Deployment ==="
kubectl get deployment mcp-server -n ${NAMESPACE} -o wide
echo ""
echo "=== Pods ==="
kubectl get pods -n ${NAMESPACE} -l app=mcp-server -o wide
echo ""
echo "=== Service ==="
kubectl get svc mcp-server -n ${NAMESPACE}
echo ""
echo "=== HPA ==="
kubectl get hpa mcp-server-hpa -n ${NAMESPACE} 2>/dev/null || echo "HPA 未配置"
echo ""
echo "=== Recent Events ==="
kubectl get events -n ${NAMESPACE} --field-selector involvedObject.name=mcp-server --sort-by='.lastTimestamp' | tail -10
}
# ===== 查看日志 =====
show_logs() {
log_info "MCP Server 日志 (最近 100 行):"
kubectl logs -n ${NAMESPACE} -l app=mcp-server --tail=100 -f
}
# ===== 回滚部署 =====
rollback() {
log_warn "回滚 MCP Server 部署..."
kubectl rollout undo deployment/mcp-server -n ${NAMESPACE}
kubectl rollout status deployment/mcp-server -n ${NAMESPACE} --timeout=300s
log_success "回滚完成"
}
# ===== 验证连接 =====
verify_connections() {
log_info "验证服务连接..."
# 获取一个 Pod 名称
POD_NAME=$(kubectl get pods -n ${NAMESPACE} -l app=mcp-server -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -z "$POD_NAME" ]; then
log_error "没有运行中的 Pod"
return 1
fi
log_info "使用 Pod: ${POD_NAME}"
# 检查健康状态
log_info "检查健康状态..."
kubectl exec -n ${NAMESPACE} ${POD_NAME} -- curl -s http://localhost:8000/health || log_warn "健康检查失败"
log_success "连接验证完成"
}
# ===== 打印配置信息 =====
print_config() {
echo ""
log_info "===== 当前配置 ====="
echo "ACR: ${ACR_LOGIN_SERVER}"
echo "镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
echo "命名空间: ${NAMESPACE}"
echo ""
log_info "===== 关键配置(来自 ConfigMap/Secrets) ====="
echo "PostgreSQL: postgres 数据库 (生产环境)"
echo "Redis: Azure Cache for Redis (SSL, 端口 10000)"
echo "Agent Manager: http://agent-manager.agent-manager.svc.cluster.local:80"
echo "LiteLLM: https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io"
echo ""
}
# ===== 完整部署 =====
full_deploy() {
check_prerequisites
print_config
login_acr
build_image
create_namespace
apply_configs
deploy_service
show_status
log_success "===== MCP Server 完整部署完成 ====="
}
# ===== 仅部署(不构建) =====
deploy_only() {
check_prerequisites
print_config
create_namespace
apply_configs
deploy_service
show_status
log_success "===== MCP Server 部署完成 ====="
}
# ===== 帮助信息 =====
show_help() {
echo "MCP Server AKS 部署脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " build 只构建镜像并推送到 ACR"
echo " deploy 只部署服务(不构建镜像)"
echo " all 完整部署(构建 + 部署)"
echo " status 查看部署状态"
echo " logs 查看服务日志"
echo " rollback 回滚到上一版本"
echo " verify 验证服务连接"
echo " config 显示配置信息"
echo " help 显示此帮助信息"
echo ""
echo "环境变量:"
echo " IMAGE_TAG 镜像标签 (默认: latest)"
echo ""
echo "示例:"
echo " $0 all # 完整部署"
echo " IMAGE_TAG=v1.0.0 $0 all # 使用指定版本部署"
echo " $0 deploy # 只部署不构建"
echo " $0 status # 查看状态"
}
# ===== 主程序 =====
case "${1:-help}" in
build)
check_prerequisites
login_acr
build_image
;;
build-local)
check_prerequisites
login_acr
build_image_local
;;
deploy)
deploy_only
;;
all)
full_deploy
;;
status)
show_status
;;
logs)
show_logs
;;
rollback)
rollback
;;
verify)
verify_connections
;;
config)
print_config
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
+78
View File
@@ -0,0 +1,78 @@
# Ingress 配置 - MCP Server
# 支持 Azure Application Gateway Ingress Controller 或 NGINX Ingress Controller
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mcp-server-ingress
namespace: taiji-ai
labels:
app: mcp-server
annotations:
# 使用 NGINX Ingress Controller (如果使用 AGIC,请更换注解)
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
# CORS 配置
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
# Let's Encrypt 证书 (需要 cert-manager)
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
# TLS 配置 (如果有证书)
# tls:
# - hosts:
# - api.taiji-ai.com
# secretName: mcp-server-tls
rules:
- host: mcp.taiji-ai.com
http:
paths:
# API 路由
- path: /
pathType: Prefix
backend:
service:
name: mcp-server
port:
number: 8000
---
# Azure Application Gateway Ingress Controller 配置 (备选)
# 如果使用 AGIC,请使用以下配置替换上面的 Ingress
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
# name: mcp-server-ingress-agic
# namespace: taiji-ai
# annotations:
# kubernetes.io/ingress.class: azure/application-gateway
# appgw.ingress.kubernetes.io/ssl-redirect: "true"
# appgw.ingress.kubernetes.io/connection-draining: "true"
# appgw.ingress.kubernetes.io/connection-draining-timeout: "30"
# appgw.ingress.kubernetes.io/backend-protocol: "http"
# spec:
# tls:
# - hosts:
# - api.taiji-ai.com
# secretName: mcp-server-tls
# rules:
# - host: api.taiji-ai.com
# http:
# paths:
# - path: /
# pathType: Prefix
# backend:
# service:
# name: mcp-server
# port:
# number: 8000
+145 -28
View File
@@ -1,4 +1,5 @@
# MCP Server Deployment
# MCP Server Deployment for Azure AKS
# 生产环境配置 - PostgreSQL 使用 postgres 数据库,Redis 使用 Azure Cache for Redis
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -6,15 +7,27 @@ metadata:
namespace: taiji-ai
labels:
app: mcp-server
version: v1
environment: production
spec:
replicas: 2
selector:
matchLabels:
app: mcp-server
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: mcp-server
version: v1
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: mcp-server
@@ -23,7 +36,23 @@ spec:
ports:
- containerPort: 8000
name: http
protocol: TCP
env:
# 应用环境配置
- name: ENVIRONMENT
value: "production"
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: taiji-config
key: APP_ENV
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: taiji-config
key: LOG_LEVEL
# 数据库配置 (Azure Database for PostgreSQL - 生产库 postgres)
- name: DATABASE_URL
valueFrom:
secretKeyRef:
@@ -34,26 +63,61 @@ spec:
secretKeyRef:
name: taiji-secrets
key: async-database-url
# Redis配置 (Azure Cache for Redis with SSL)
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: taiji-secrets
key: redis-url
# NATS配置 (K8s内部服务)
- name: NATS_URL
valueFrom:
configMapKeyRef:
name: taiji-config
key: NATS_URL
# LiteLLM网关配置
- name: LITELLM_URL
valueFrom:
configMapKeyRef:
name: taiji-config
key: LITELLM_URL
- name: LLM_BASE_URL
valueFrom:
configMapKeyRef:
name: taiji-config
key: LITELLM_URL
- name: LITELLM_MASTER_KEY
valueFrom:
secretKeyRef:
name: taiji-secrets
key: litellm-master-key
- name: LITELLM_API_KEY
valueFrom:
secretKeyRef:
name: taiji-secrets
key: litellm-master-key
# Agent Manager 配置 (AKS内部服务)
- name: AGENT_MANAGER_URL
valueFrom:
configMapKeyRef:
name: taiji-config
key: AGENT_MANAGER_URL
- name: AGENT_K8S_NAMESPACE
valueFrom:
configMapKeyRef:
name: taiji-config
key: AGENT_K8S_NAMESPACE
# JWT配置
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: taiji-secrets
key: jwt-secret
- name: JWT_SECRET_KEY
valueFrom:
secretKeyRef:
@@ -69,26 +133,7 @@ spec:
configMapKeyRef:
name: taiji-config
key: JWT_EXPIRE_MINUTES
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: taiji-config
key: APP_ENV
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: taiji-config
key: LOG_LEVEL
- name: AGENT_MANAGER_URL
valueFrom:
configMapKeyRef:
name: taiji-config
key: AGENT_MANAGER_URL
- name: AGENT_K8S_NAMESPACE
valueFrom:
configMapKeyRef:
name: taiji-config
key: AGENT_K8S_NAMESPACE
# SMTP邮箱配置
- name: SMTP_SERVER
valueFrom:
@@ -115,13 +160,8 @@ spec:
secretKeyRef:
name: taiji-secrets
key: smtp-password
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
# 健康检查
livenessProbe:
httpGet:
path: /health
@@ -130,6 +170,7 @@ spec:
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 5
readinessProbe:
httpGet:
path: /health
@@ -138,16 +179,92 @@ spec:
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 5
# 资源限制
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
# 挂载卷
volumeMounts:
- name: logs
mountPath: /app/logs
# 卷定义
volumes:
- name: logs
emptyDir: {}
# 重启策略
restartPolicy: Always
# ACR 镜像拉取凭据
imagePullSecrets:
- name: acr-secret
# 服务账户(如果需要访问K8s API)
# serviceAccountName: mcp-server-sa
---
# MCP Server Service
apiVersion: v1
kind: Service
metadata:
name: mcp-server
namespace: taiji-ai
labels:
app: mcp-server
spec:
type: ClusterIP
selector:
app: mcp-server
ports:
- name: http
port: 8000
targetPort: 8000
protocol: TCP
---
# HorizontalPodAutoscaler - 自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mcp-server-hpa
namespace: taiji-ai
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mcp-server
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
# PodDisruptionBudget - 确保高可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mcp-server-pdb
namespace: taiji-ai
spec:
minAvailable: 1
selector:
matchLabels:
app: mcp-server
+23 -4
View File
@@ -1,32 +1,51 @@
# Kubernetes Secrets for Taiji AI-PAD
# 注意:这些值使用 base64 编码
# 注意:生产环境请使用 Azure Key Vault 或 kubectl create secret 命令
# 生成命令: echo -n "your-value" | base64
apiVersion: v1
kind: Secret
metadata:
name: taiji-secrets
namespace: taiji-ai
labels:
app: taiji-ai-pad
environment: production
type: Opaque
stringData:
# ===========================================
# 数据库配置 (Azure Database for PostgreSQL)
# 生产环境使用 postgres 数据库,测试环境使用 taiji 数据库
# 生产环境使用 postgres 数据库
# ===========================================
database-url: "postgresql://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres?sslmode=require"
async-database-url: "postgresql+asyncpg://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres"
# Redis配置 (Azure Cache for Redis)
# ===========================================
# Redis配置 (Azure Cache for Redis with SSL)
# 端口 10000 使用 SSL 连接
# ===========================================
redis-url: "rediss://:nkJgt1ERFpdeYrEFNyFtsc5K4ycvx2jIeAzCaGGf1OQ%3D@taiji.southeastasia.redis.azure.net:10000/0?ssl_cert_reqs=none"
# ===========================================
# JWT配置
# ===========================================
jwt-secret: "your-super-secret-jwt-key-change-this-in-production"
# ===========================================
# LiteLLM配置
# ===========================================
litellm-master-key: "sk-litellm-taiji-prod-8f3a9b2c4d5e6f7g"
# ===========================================
# OpenRouter配置
# ===========================================
openrouter-api-key: "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"
# ===========================================
# RapidAPI配置
# ===========================================
rapidapi-key: "33902cc39dmsha572ec6ae920fb5p13c196jsn8a11209a7e67"
# ===========================================
# SMTP邮箱配置
smtp-password: "eR)8hD@1Q)3sU%2q"
# ===========================================
smtp-password: "eR)8hD@1Q)3sU%2q"
+414
View File
@@ -0,0 +1,414 @@
#!/bin/bash
# ============================================================
# Taiji AI-PAD AKS 集群迁移脚本
# 从旧订阅 (Xmind运营学习专用01) 迁移到新订阅 (Xmind运营学习专用2026)
# ============================================================
set -e
# ==================== 配置变量 ====================
# 旧集群配置
OLD_SUBSCRIPTION="旧订阅ID" # 请替换为实际的旧订阅ID
OLD_RESOURCE_GROUP="Xmind运营学习专用01"
OLD_AKS_NAME="taiji-ai-pda"
# 新集群配置
NEW_SUBSCRIPTION="新订阅ID" # 请替换为实际的新订阅ID
NEW_RESOURCE_GROUP="Xmind运营学习专用2026"
NEW_AKS_NAME="taiji-ai-pda"
# ACR 配置
ACR_NAME="taiji"
ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"
# 需要迁移的命名空间列表(用空格分隔)
NAMESPACES="taiji-ai"
# 备份目录
BACKUP_DIR="./migration-backup-$(date +%Y%m%d-%H%M%S)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ==================== 辅助函数 ====================
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
check_prerequisites() {
log_info "检查必要工具..."
command -v az >/dev/null 2>&1 || { log_error "需要安装 Azure CLI"; exit 1; }
command -v kubectl >/dev/null 2>&1 || { log_error "需要安装 kubectl"; exit 1; }
command -v jq >/dev/null 2>&1 || { log_error "需要安装 jq"; exit 1; }
log_success "所有必要工具已安装"
}
# ==================== 阶段 1: 从旧集群导出资源 ====================
export_from_old_cluster() {
log_info "========== 阶段 1: 从旧集群导出资源 =========="
# 创建备份目录
mkdir -p "${BACKUP_DIR}"
# 切换到旧订阅
log_info "切换到旧订阅..."
az account set --subscription "${OLD_SUBSCRIPTION}"
# 获取旧集群凭据
log_info "获取旧 AKS 集群凭据..."
az aks get-credentials \
--resource-group "${OLD_RESOURCE_GROUP}" \
--name "${OLD_AKS_NAME}" \
--overwrite-existing \
--context "old-cluster"
# 设置 kubectl 上下文
kubectl config use-context "old-cluster" 2>/dev/null || \
kubectl config use-context "${OLD_AKS_NAME}"
log_info "当前集群信息:"
kubectl cluster-info
# 导出每个命名空间的资源
for NS in ${NAMESPACES}; do
log_info "导出命名空间 ${NS} 的资源..."
mkdir -p "${BACKUP_DIR}/${NS}"
# 导出命名空间定义
kubectl get namespace ${NS} -o yaml > "${BACKUP_DIR}/${NS}/namespace.yaml" 2>/dev/null || true
# 导出 Deployments
kubectl get deployments -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/deployments.yaml" 2>/dev/null || true
# 导出 Services
kubectl get services -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/services.yaml" 2>/dev/null || true
# 导出 ConfigMaps
kubectl get configmaps -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/configmaps.yaml" 2>/dev/null || true
# 导出 Secrets
kubectl get secrets -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/secrets.yaml" 2>/dev/null || true
# 导出 Ingress
kubectl get ingress -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/ingress.yaml" 2>/dev/null || true
# 导出 PersistentVolumeClaims
kubectl get pvc -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/pvc.yaml" 2>/dev/null || true
# 导出 ServiceAccounts
kubectl get serviceaccounts -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/serviceaccounts.yaml" 2>/dev/null || true
# 导出 HorizontalPodAutoscalers
kubectl get hpa -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/hpa.yaml" 2>/dev/null || true
# 导出 NetworkPolicies
kubectl get networkpolicies -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/networkpolicies.yaml" 2>/dev/null || true
# 导出 CronJobs
kubectl get cronjobs -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/cronjobs.yaml" 2>/dev/null || true
# 导出 StatefulSets
kubectl get statefulsets -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/statefulsets.yaml" 2>/dev/null || true
# 导出 DaemonSets
kubectl get daemonsets -n ${NS} -o yaml > "${BACKUP_DIR}/${NS}/daemonsets.yaml" 2>/dev/null || true
log_success "命名空间 ${NS} 资源导出完成"
done
# 导出集群级别的资源
log_info "导出集群级别资源..."
mkdir -p "${BACKUP_DIR}/cluster-resources"
# 导出 ClusterRoles
kubectl get clusterroles -o yaml > "${BACKUP_DIR}/cluster-resources/clusterroles.yaml" 2>/dev/null || true
# 导出 ClusterRoleBindings
kubectl get clusterrolebindings -o yaml > "${BACKUP_DIR}/cluster-resources/clusterrolebindings.yaml" 2>/dev/null || true
# 导出 StorageClasses
kubectl get storageclasses -o yaml > "${BACKUP_DIR}/cluster-resources/storageclasses.yaml" 2>/dev/null || true
# 导出 PersistentVolumes
kubectl get pv -o yaml > "${BACKUP_DIR}/cluster-resources/persistentvolumes.yaml" 2>/dev/null || true
# 记录当前 Pod 状态
log_info "记录当前 Pod 状态..."
for NS in ${NAMESPACES}; do
kubectl get pods -n ${NS} -o wide > "${BACKUP_DIR}/${NS}/pods-status.txt" 2>/dev/null || true
kubectl get svc -n ${NS} -o wide > "${BACKUP_DIR}/${NS}/services-status.txt" 2>/dev/null || true
done
# 获取 LoadBalancer IP
log_info "记录 LoadBalancer IP..."
kubectl get svc --all-namespaces -o wide | grep LoadBalancer > "${BACKUP_DIR}/loadbalancer-ips.txt" 2>/dev/null || true
log_success "阶段 1 完成: 所有资源已导出到 ${BACKUP_DIR}"
}
# ==================== 阶段 2: 准备新集群 ====================
prepare_new_cluster() {
log_info "========== 阶段 2: 准备新集群 =========="
# 切换到新订阅
log_info "切换到新订阅..."
az account set --subscription "${NEW_SUBSCRIPTION}"
# 获取新集群凭据
log_info "获取新 AKS 集群凭据..."
az aks get-credentials \
--resource-group "${NEW_RESOURCE_GROUP}" \
--name "${NEW_AKS_NAME}" \
--overwrite-existing \
--context "new-cluster"
# 设置 kubectl 上下文
kubectl config use-context "new-cluster" 2>/dev/null || \
kubectl config use-context "${NEW_AKS_NAME}"
log_info "新集群信息:"
kubectl cluster-info
# 附加 ACR 到新 AKS
log_info "附加 ACR 到新 AKS 集群..."
az aks update \
--resource-group "${NEW_RESOURCE_GROUP}" \
--name "${NEW_AKS_NAME}" \
--attach-acr "${ACR_NAME}" || log_warn "ACR 附加可能已存在或需要手动处理"
log_success "阶段 2 完成: 新集群已准备就绪"
}
# ==================== 阶段 3: 部署到新集群 ====================
deploy_to_new_cluster() {
log_info "========== 阶段 3: 部署到新集群 =========="
# 确保使用新集群上下文
kubectl config use-context "new-cluster" 2>/dev/null || \
kubectl config use-context "${NEW_AKS_NAME}"
# 使用项目中的 k8s 配置部署
log_info "使用项目 k8s 配置部署..."
# 创建命名空间
kubectl apply -f k8s/namespace.yaml
# 部署 Secrets 和 ConfigMap
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/configmap.yaml
# 部署 NATS
kubectl apply -f k8s/nats.yaml
# 等待 NATS 就绪
log_info "等待 NATS 就绪..."
kubectl wait --for=condition=ready pod -l app=nats -n taiji-ai --timeout=120s || true
# 部署应用服务
kubectl apply -f k8s/litellm-gateway.yaml
kubectl apply -f k8s/data-ingestion.yaml
kubectl apply -f k8s/mcp-server.yaml
kubectl apply -f k8s/api-gateway.yaml
# 部署监控
kubectl apply -f k8s/monitoring.yaml
# 如果有 MCP Server 的独立 ingress,也部署
if [ -f "services/mcp-server/k8s/ingress.yaml" ]; then
kubectl apply -f services/mcp-server/k8s/deployment.yaml || true
kubectl apply -f services/mcp-server/k8s/secrets.yaml || true
kubectl apply -f services/mcp-server/k8s/ingress.yaml || true
fi
# 等待所有服务就绪
log_info "等待所有服务就绪..."
for NS in ${NAMESPACES}; do
kubectl wait --for=condition=ready pods --all -n ${NS} --timeout=300s || log_warn "部分 Pod 可能未就绪"
done
log_success "阶段 3 完成: 应用已部署到新集群"
}
# ==================== 阶段 4: 验证新集群 ====================
verify_new_cluster() {
log_info "========== 阶段 4: 验证新集群 =========="
# 确保使用新集群上下文
kubectl config use-context "new-cluster" 2>/dev/null || \
kubectl config use-context "${NEW_AKS_NAME}"
log_info "检查 Pod 状态..."
for NS in ${NAMESPACES}; do
echo ""
log_info "命名空间 ${NS} 的 Pod 状态:"
kubectl get pods -n ${NS} -o wide
echo ""
log_info "命名空间 ${NS} 的 Service 状态:"
kubectl get svc -n ${NS} -o wide
done
# 获取新的 LoadBalancer IP
log_info "获取新的 LoadBalancer IP..."
NEW_LB_IP=""
for i in {1..30}; do
NEW_LB_IP=$(kubectl get svc api-gateway -n taiji-ai -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null)
if [ -n "$NEW_LB_IP" ]; then
break
fi
log_info "等待 LoadBalancer IP 分配... ($i/30)"
sleep 10
done
if [ -n "$NEW_LB_IP" ]; then
log_success "新 LoadBalancer IP: ${NEW_LB_IP}"
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}新集群访问地址:${NC}"
echo -e " - API Gateway: http://${NEW_LB_IP}"
echo -e " - MCP Server: http://${NEW_LB_IP}/api/mcp/"
echo -e " - Data Ingestion: http://${NEW_LB_IP}/api/data/"
echo -e " - LiteLLM Gateway: http://${NEW_LB_IP}/api/llm/"
echo -e "${GREEN}========================================${NC}"
# 保存新 IP 到文件
echo "${NEW_LB_IP}" > "${BACKUP_DIR}/new-loadbalancer-ip.txt"
else
log_warn "LoadBalancer IP 尚未分配,请稍后检查"
fi
log_success "阶段 4 完成: 新集群验证完毕"
}
# ==================== 阶段 5: DNS 切换指南 ====================
dns_switch_guide() {
log_info "========== 阶段 5: DNS 切换指南 =========="
echo ""
echo -e "${YELLOW}========================================${NC}"
echo -e "${YELLOW}DNS 切换步骤:${NC}"
echo -e "${YELLOW}========================================${NC}"
echo ""
echo "1. 获取新集群的 LoadBalancer IP:"
echo " kubectl get svc api-gateway -n taiji-ai -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"
echo ""
echo "2. 登录 Azure Portal 或你的 DNS 提供商控制台"
echo ""
echo "3. 更新 DNS A 记录,将域名指向新 IP"
echo ""
echo "4. 如果使用 Azure DNS Zone:"
echo " az network dns record-set a update \\"
echo " --resource-group <DNS_RESOURCE_GROUP> \\"
echo " --zone-name <YOUR_DOMAIN> \\"
echo " --name <SUBDOMAIN> \\"
echo " --set aRecords[0].ipv4Address=<NEW_IP>"
echo ""
echo "5. 验证 DNS 解析:"
echo " nslookup <YOUR_DOMAIN>"
echo " dig <YOUR_DOMAIN>"
echo ""
echo "6. 等待 DNS 传播 (通常 5-30 分钟,取决于 TTL)"
echo ""
echo -e "${YELLOW}========================================${NC}"
}
# ==================== 阶段 6: 清理旧集群 ====================
cleanup_old_cluster() {
log_info "========== 阶段 6: 清理旧集群 (可选) =========="
echo ""
log_warn "警告: 以下操作将删除旧集群的资源,请确保新集群已完全正常运行!"
echo ""
read -p "确认要清理旧集群吗? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
log_info "跳过旧集群清理"
return
fi
# 切换到旧订阅
az account set --subscription "${OLD_SUBSCRIPTION}"
# 获取旧集群凭据
az aks get-credentials \
--resource-group "${OLD_RESOURCE_GROUP}" \
--name "${OLD_AKS_NAME}" \
--overwrite-existing
# 缩容旧集群的 Deployments
log_info "缩容旧集群的 Deployments..."
for NS in ${NAMESPACES}; do
kubectl scale deployment --all --replicas=0 -n ${NS} || true
done
log_success "旧集群已缩容,可以在确认一切正常后删除旧订阅"
}
# ==================== 主函数 ====================
main() {
echo ""
echo -e "${GREEN}============================================================${NC}"
echo -e "${GREEN} Taiji AI-PAD AKS 集群迁移工具 ${NC}"
echo -e "${GREEN} 从: ${OLD_RESOURCE_GROUP} -> 到: ${NEW_RESOURCE_GROUP} ${NC}"
echo -e "${GREEN}============================================================${NC}"
echo ""
check_prerequisites
echo ""
echo "请选择要执行的操作:"
echo " 1) 完整迁移 (阶段1-5)"
echo " 2) 仅导出旧集群资源 (阶段1)"
echo " 3) 仅部署到新集群 (阶段2-3)"
echo " 4) 仅验证新集群 (阶段4)"
echo " 5) 查看 DNS 切换指南 (阶段5)"
echo " 6) 清理旧集群 (阶段6)"
echo ""
read -p "请输入选项 [1-6]: " choice
case $choice in
1)
export_from_old_cluster
prepare_new_cluster
deploy_to_new_cluster
verify_new_cluster
dns_switch_guide
;;
2)
export_from_old_cluster
;;
3)
prepare_new_cluster
deploy_to_new_cluster
;;
4)
verify_new_cluster
;;
5)
dns_switch_guide
;;
6)
cleanup_old_cluster
;;
*)
log_error "无效选项"
exit 1
;;
esac
echo ""
log_success "迁移操作完成!"
echo ""
}
# 运行主函数
main "$@"
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
# ============================================================
# AKS 快速迁移命令参考
#
# 使用前请先修改以下变量为实际值
# ============================================================
# ========== 请修改以下变量 ==========
OLD_SUBSCRIPTION="请替换为旧订阅ID"
NEW_SUBSCRIPTION="请替换为新订阅ID"
OLD_RESOURCE_GROUP="Xmind运营学习专用01"
NEW_RESOURCE_GROUP="Xmind运营学习专用2026"
AKS_NAME="taiji-ai-pda"
ACR_NAME="taiji"
NAMESPACE="taiji-ai"
# DNS 相关 (可选)
DNS_RESOURCE_GROUP="你的DNS资源组"
DNS_ZONE="你的域名"
DNS_RECORD_NAME="子域名"
# ====================================
echo "========================================="
echo "AKS 迁移快速命令参考"
echo "========================================="
echo ""
echo "步骤 1: 登录 Azure"
echo "-----------------------------------------"
echo "az login"
echo ""
echo "步骤 2: 查看订阅列表"
echo "-----------------------------------------"
echo "az account list --output table"
echo ""
echo "步骤 3: 连接旧集群并查看资源"
echo "-----------------------------------------"
echo "az account set --subscription \"${OLD_SUBSCRIPTION}\""
echo "az aks get-credentials --resource-group \"${OLD_RESOURCE_GROUP}\" --name \"${AKS_NAME}\" --overwrite-existing"
echo "kubectl get all -n ${NAMESPACE}"
echo ""
echo "步骤 4: 连接新集群"
echo "-----------------------------------------"
echo "az account set --subscription \"${NEW_SUBSCRIPTION}\""
echo "az aks get-credentials --resource-group \"${NEW_RESOURCE_GROUP}\" --name \"${AKS_NAME}\" --overwrite-existing"
echo ""
echo "步骤 5: 附加 ACR 到新集群"
echo "-----------------------------------------"
echo "az aks update --resource-group \"${NEW_RESOURCE_GROUP}\" --name \"${AKS_NAME}\" --attach-acr ${ACR_NAME}"
echo ""
echo "步骤 6: 部署到新集群"
echo "-----------------------------------------"
echo "cd /home/taiji/tools/taiji-AI-PAD"
echo "kubectl apply -f k8s/namespace.yaml"
echo "kubectl apply -f k8s/secrets.yaml"
echo "kubectl apply -f k8s/configmap.yaml"
echo "kubectl apply -f k8s/nats.yaml"
echo "kubectl apply -f k8s/litellm-gateway.yaml"
echo "kubectl apply -f k8s/data-ingestion.yaml"
echo "kubectl apply -f k8s/mcp-server.yaml"
echo "kubectl apply -f k8s/api-gateway.yaml"
echo "kubectl apply -f k8s/monitoring.yaml"
echo ""
echo "步骤 7: 验证部署"
echo "-----------------------------------------"
echo "kubectl get pods -n ${NAMESPACE}"
echo "kubectl get svc -n ${NAMESPACE}"
echo ""
echo "步骤 8: 获取新 LoadBalancer IP"
echo "-----------------------------------------"
echo "kubectl get svc api-gateway -n ${NAMESPACE} -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"
echo ""
echo "步骤 9: 更新 DNS (Azure DNS 示例)"
echo "-----------------------------------------"
echo "NEW_IP=\$(kubectl get svc api-gateway -n ${NAMESPACE} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
echo "az network dns record-set a add-record --resource-group \"${DNS_RESOURCE_GROUP}\" --zone-name \"${DNS_ZONE}\" --record-set-name \"${DNS_RECORD_NAME}\" --ipv4-address \${NEW_IP}"
echo ""
echo "步骤 10: 验证 DNS"
echo "-----------------------------------------"
echo "nslookup ${DNS_ZONE}"
echo ""
echo "========================================="
+88 -10
View File
@@ -944,6 +944,84 @@ class AgentManagerClient:
env=env_vars
)
async def generate_agent_from_tools(
self,
agent_name: str,
description: str,
tools: List[Dict[str, Any]],
user_id: str,
tenant_id: Optional[str] = None,
auto_deploy: bool = True
) -> Dict[str, Any]:
"""
使用外部数据工具生成 Agent
调用 Agent Manager 的 POST /external-tools/agents/create-with-tools 接口
不需要预定义模板,根据工具配置动态生成 Agent
Args:
agent_name: Agent 名称
description: Agent 描述
tools: 工具配置列表,每个工具包含:
- name: 工具名称
- description: 工具描述
- url: API 端点 URL
- method: HTTP 方法
- user_id: 用户 ID
- headers: (可选) 自定义请求头
- auth: (可选) 认证配置
- request_params: (可选) URL 查询参数定义
- request_body: (可选) 请求体定义
- response_mapping: (可选) 响应字段映射
- timeout: (可选) 超时时间
- retry: (可选) 重试配置
user_id: 用户 ID
tenant_id: (可选) 租户 ID
auto_deploy: 是否自动部署,默认 True
Returns:
生成结果,包含 agent_id、status 等信息
Example:
result = await client.generate_agent_from_tools(
agent_name="my-api-agent",
description="调用外部 API 的 Agent",
tools=[
{
"name": "天气查询",
"description": "查询城市天气",
"url": "https://api.weather.com/forecast",
"method": "GET",
"user_id": "user-001",
"auth": {"type": "api_key", "key": "sk-xxx", "in": "header", "name": "X-API-Key"},
"request_params": {"type": "object", "properties": {"city": {"type": "string"}}}
}
],
user_id="user-001",
auto_deploy=True
)
"""
logger.info(
"generating_agent_from_tools",
agent_name=agent_name,
tool_count=len(tools),
user_id=user_id,
auto_deploy=auto_deploy
)
payload = {
"agent_name": agent_name,
"description": description,
"tools": tools,
"user_id": user_id,
"auto_deploy": auto_deploy
}
if tenant_id:
payload["tenant_id"] = tenant_id
return await self._request("POST", "/tools/generate-agent", json=payload)
async def delete_agent(self, agent_name: str) -> Dict[str, Any]:
"""
Delete Agent Pod
@@ -1243,7 +1321,7 @@ class AgentManagerClient:
user_id=user_id
)
return await self._request("POST", "/tools/generate", json=payload)
return await self._request("POST", "/external-tools/generate", json=payload)
async def update_external_tool(
self,
@@ -1265,7 +1343,7 @@ class AgentManagerClient:
"""
更新外部数据工具
调用: PUT /tools/{tool_ref_id}
调用: PUT /external-tools/{tool_ref_id}
Agent Manager 会重新生成工具文件,可能返回新的 tool_ref_id。
@@ -1313,13 +1391,13 @@ class AgentManagerClient:
user_id=user_id
)
return await self._request("PUT", f"/tools/{tool_ref_id}", json=payload)
return await self._request("PUT", f"/external-tools/{tool_ref_id}", json=payload)
async def delete_external_tool(self, tool_ref_id: str) -> Dict[str, Any]:
"""
删除外部数据工具
调用: DELETE /tools/{tool_ref_id}
调用: DELETE /external-tools/{tool_ref_id}
Agent Manager 会删除对应的工具文件和配置。
@@ -1333,7 +1411,7 @@ class AgentManagerClient:
}
"""
logger.info("deleting_external_tool", tool_ref_id=tool_ref_id)
return await self._request("DELETE", f"/tools/{tool_ref_id}")
return await self._request("DELETE", f"/external-tools/{tool_ref_id}")
async def test_external_tool(
self,
@@ -1343,7 +1421,7 @@ class AgentManagerClient:
"""
测试外部数据工具连接
调用: POST /tools/{tool_ref_id}/test
调用: POST /external-tools/{tool_ref_id}/test
Agent Manager 会尝试调用工具的 API 并返回测试结果。
@@ -1365,7 +1443,7 @@ class AgentManagerClient:
payload["test_params"] = test_params
logger.info("testing_external_tool", tool_ref_id=tool_ref_id)
return await self._request("POST", f"/tools/{tool_ref_id}/test", json=payload)
return await self._request("POST", f"/external-tools/{tool_ref_id}/test", json=payload)
async def create_agent_with_tools(
self,
@@ -1378,13 +1456,13 @@ class AgentManagerClient:
"""
创建带有外部数据工具的 Agent
调用: POST /agents(新增 tool_refs 字段)
调用: POST /external-tools/agents/create-with-tools (Agent Manager v2.0)
Agent Manager 会根据 tool_refs 加载对应的工具文件,部署到 AKS。
Args:
name: Agent 名称
template: Agent 模板(通常为 "custom_agent")
template: Agent 模板(如 echo_agent)
tool_refs: 外部数据工具标识列表
config: 资源配置
env: 环境变量
@@ -1415,7 +1493,7 @@ class AgentManagerClient:
config=config.to_dict() if config else None
)
data = await self._request("POST", "/agents", json=payload)
data = await self._request("POST", "/external-tools/agents/create-with-tools", json=payload)
return AgentCreateResult(
name=data["name"],
+260 -45
View File
@@ -3,10 +3,14 @@
用户可以创建外部数据工具,工具配置发送给 Agent Manager 生成 Pydantic 工具文件,
MCP-Server 只存储工具基本信息和关联标识(tool_ref_id)。
v2 简化设计:
- 用户只需提供 name, url, auth, example
- 系统自动推断:HTTP 方法、参数结构、认证头配置
"""
import structlog
from typing import Optional
from typing import Optional, Dict, Any, Tuple
from uuid import UUID as PyUUID
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query
from sqlalchemy.ext.asyncio import AsyncSession
@@ -26,6 +30,7 @@ from app.schemas import (
UpdateToolkitRequest,
ToolkitInfo,
ToolkitDetail,
ExternalToolAuthConfig,
)
from app.auth import require_auth
from app.agent_manager_client import get_agent_manager_client
@@ -35,6 +40,159 @@ logger = structlog.get_logger(__name__)
router = APIRouter(prefix="/api/user/external-tools", tags=["External Data Tools"])
# ============= 辅助函数:自动推断配置 =============
def infer_json_schema_from_example(example: Dict[str, Any]) -> Dict[str, Any]:
"""
从请求示例推断 JSON Schema
示例:{"city": "北京", "count": 10}
=> {
"type": "object",
"properties": {
"city": {"type": "string", "description": "city 参数"},
"count": {"type": "integer", "description": "count 参数"}
},
"required": ["city", "count"]
}
"""
if not example:
return None
def infer_type(value: Any) -> Dict[str, Any]:
"""推断单个值的类型"""
if value is None:
return {"type": "string", "nullable": True}
elif isinstance(value, bool):
return {"type": "boolean"}
elif isinstance(value, int):
return {"type": "integer"}
elif isinstance(value, float):
return {"type": "number"}
elif isinstance(value, str):
return {"type": "string"}
elif isinstance(value, list):
if len(value) > 0:
item_schema = infer_type(value[0])
return {"type": "array", "items": item_schema}
return {"type": "array", "items": {"type": "string"}}
elif isinstance(value, dict):
return infer_json_schema_from_example(value)
else:
return {"type": "string"}
properties = {}
required = []
for key, value in example.items():
prop_schema = infer_type(value)
prop_schema["description"] = f"{key} 参数"
properties[key] = prop_schema
# 所有在 example 中出现的字段都标记为必填
required.append(key)
return {
"type": "object",
"properties": properties,
"required": required
}
def build_full_auth_config(auth: Optional[ExternalToolAuthConfig]) -> Optional[Dict[str, Any]]:
"""
从简化的认证配置构建完整的 Agent Manager 认证配置
自动推断规则:
- api_key: header 位置,名称尝试 X-API-Key, Authorization, api_key
- bearer: header 位置,名称 Authorization,值前缀 Bearer
- basic: header 位置,名称 Authorization,Base64 编码
"""
if not auth or auth.type == "none":
return None
secret = auth.get_secret_value()
if auth.type == "api_key":
return {
"type": "api_key",
"key": secret,
"in": auth.in_location or "header", # 默认 header
"name": auth.name or "X-API-Key" # 默认 X-API-Key
}
elif auth.type == "bearer":
return {
"type": "bearer",
"key": secret,
"in": "header",
"name": "Authorization"
}
elif auth.type == "basic":
return {
"type": "basic",
"username": auth.username,
"password": auth.password
}
return None
def resolve_tool_config(req: CreateExternalToolRequest) -> Tuple[str, Dict[str, str], Dict[str, Any], Dict[str, Any], int, Dict[str, Any]]:
"""
从简化请求解析完整的工具配置
返回: (method, headers, request_params, request_body, timeout, retry)
"""
# 1. 解析 HTTP 方法(优先级:advanced > 兼容字段 > 默认 POST)
method = "POST"
if req.advanced and req.advanced.method:
method = req.advanced.method
elif req.method:
method = req.method
# 2. 解析 headers(优先级:advanced > 兼容字段 > 默认)
headers = {"Content-Type": "application/json"}
if req.advanced and req.advanced.headers:
headers.update(req.advanced.headers)
elif req.headers:
headers.update(req.headers)
# 3. 解析请求参数/体
request_params = None
request_body = None
# 优先使用兼容字段(如果用户提供了 JSON Schema)
if req.request_params:
request_params = req.request_params
if req.request_body:
request_body = req.request_body
# 如果用户提供了 example,从中推断
if req.example and not request_params and not request_body:
inferred_schema = infer_json_schema_from_example(req.example)
if method in ("GET", "DELETE"):
request_params = inferred_schema
else:
request_body = inferred_schema
# 4. 解析超时(优先级:advanced > 兼容字段 > 默认 30)
timeout = 30
if req.advanced and req.advanced.timeout:
timeout = req.advanced.timeout
elif req.timeout:
timeout = req.timeout
# 5. 解析重试配置
retry = None
if req.advanced and req.advanced.retry:
retry = req.advanced.retry.dict()
elif req.retry:
retry = req.retry.dict()
return method, headers, request_params, request_body, timeout, retry
# ============= 创建外部数据工具 =============
@router.post("", response_model=SuccessResponse)
@@ -44,22 +202,34 @@ async def create_external_tool(
db: AsyncSession = Depends(get_db)
):
"""
创建外部数据工具
创建外部数据工具(简化版 v2)
1. 验证请求参数
2. 发送配置给 Agent Manager 生成 Pydantic 工具文件
3. 存储工具基本信息和 tool_ref_id 到数据库
用户只需提供最少信息:
- name: 工具名称
- url: API 地址
- auth: 认证配置(type + secret)
- example: 请求参数示例(可选但推荐)
系统自动推断:
- HTTP 方法(默认 POST)
- 请求参数结构(从 example 推断 JSON Schema)
- 认证头名称和位置
"""
user_id = principal.get("user_id")
tenant_id = principal.get("tenant_id")
channel_id = principal.get("channel_id")
# 解析完整配置(从简化输入推断)
method, headers, request_params, request_body, timeout, retry = resolve_tool_config(req)
auth_config = build_full_auth_config(req.auth)
logger.info(
"creating_external_tool",
user_id=user_id,
name=req.name,
url=req.url,
method=req.method
method=method,
has_example=req.example is not None
)
# 检查工具名称是否已存在
@@ -85,19 +255,21 @@ async def create_external_tool(
name=req.name,
description=req.description or "",
url=req.url,
method=req.method,
method=method,
user_id=user_id,
tenant_id=tenant_id,
headers=req.headers,
auth=req.auth.dict(by_alias=True) if req.auth else None,
request_params=req.request_params,
request_body=req.request_body,
response_mapping=req.response_mapping,
timeout=req.timeout,
retry=req.retry.dict() if req.retry else None
headers=headers,
auth=auth_config,
request_params=request_params,
request_body=request_body,
response_mapping=req.response_mapping, # 兼容字段
timeout=timeout,
retry=retry
)
tool_ref_id = am_result.get("tool_ref_id")
# Agent Manager 返回格式: {"success": true, "data": {"tool_ref_id": "xxx", ...}}
am_data = am_result.get("data", {})
tool_ref_id = am_data.get("tool_ref_id")
am_status = "active" if am_result.get("success") else "error"
error_message = am_result.get("message") if not am_result.get("success") else None
@@ -108,13 +280,22 @@ async def create_external_tool(
am_status = "error"
error_message = f"Agent Manager 生成工具失败: {str(e)}"
# 创建工具记录
# 创建工具记录(保存完整配置,用于创建自定义 Agent)
tool = ExternalDataTool(
name=req.name,
description=req.description,
url=req.url,
method=req.method,
method=method,
auth_type=req.auth.type if req.auth else "none",
# 保存完整配置
headers=headers,
auth_config=auth_config,
request_params=request_params,
request_body=request_body,
response_mapping=req.response_mapping,
timeout=timeout,
retry_config=retry,
# Agent Manager 关联
tool_ref_id=tool_ref_id,
status=am_status,
error_message=error_message,
@@ -144,7 +325,7 @@ async def create_external_tool(
"status": am_status,
"created_at": tool.created_at.isoformat() if tool.created_at else None
},
message="外部数据工具创建成功" if am_status == "active" else f"工具创建完成,但生成失败: {error_message}"
message="工具创建成功,可以开始使用了" if am_status == "active" else f"工具创建完成,但生成失败: {error_message}"
)
@@ -257,6 +438,7 @@ async def list_external_tools(
"url": tool.url,
"method": tool.method,
"auth_type": tool.auth_type,
"tool_ref_id": tool.tool_ref_id, # Agent Manager 返回的工具标识
"status": tool.status,
"usage_count": tool.usage_count,
"created_at": tool.created_at.isoformat() if tool.created_at else None
@@ -368,11 +550,42 @@ async def update_external_tool(
detail={"error": "tool_not_found", "message": "工具不存在"}
)
# 解析更新配置(支持部分更新)
# 使用请求中的值,如果没有则保持原值
new_name = req.name if req.name else tool.name
new_description = req.description if req.description is not None else tool.description
new_url = req.url if req.url else tool.url
# 解析配置(使用新的辅助函数,兼容旧格式)
# 创建一个临时的 CreateExternalToolRequest 来复用 resolve_tool_config
from app.schemas import CreateExternalToolRequest as TempReq
temp_req = TempReq(
name=new_name,
url=new_url,
auth=req.auth,
example=req.example,
advanced=req.advanced,
method=req.method,
headers=req.headers,
request_params=req.request_params,
request_body=req.request_body,
response_mapping=req.response_mapping,
timeout=req.timeout,
retry=req.retry
)
method, headers, request_params, request_body, timeout, retry = resolve_tool_config(temp_req)
auth_config = build_full_auth_config(req.auth)
# 如果没有提供新的 method,使用原来的
if not req.method and not (req.advanced and req.advanced.method):
method = tool.method
logger.info(
"updating_external_tool",
tool_id=tool_id,
user_id=user_id,
name=req.name
name=new_name,
has_example=req.example is not None
)
# 调用 Agent Manager 更新工具
@@ -383,39 +596,41 @@ async def update_external_tool(
# 已有 tool_ref_id,调用更新接口
am_result = await client.update_external_tool(
tool_ref_id=tool.tool_ref_id,
name=req.name,
description=req.description or "",
url=req.url,
method=req.method,
name=new_name,
description=new_description or "",
url=new_url,
method=method,
user_id=user_id,
tenant_id=tenant_id,
headers=req.headers,
auth=req.auth.dict(by_alias=True) if req.auth else None,
request_params=req.request_params,
request_body=req.request_body,
headers=headers,
auth=auth_config,
request_params=request_params,
request_body=request_body,
response_mapping=req.response_mapping,
timeout=req.timeout,
retry=req.retry.dict() if req.retry else None
timeout=timeout,
retry=retry
)
else:
# 没有 tool_ref_id,调用生成接口
am_result = await client.generate_external_tool(
name=req.name,
description=req.description or "",
url=req.url,
method=req.method,
name=new_name,
description=new_description or "",
url=new_url,
method=method,
user_id=user_id,
tenant_id=tenant_id,
headers=req.headers,
auth=req.auth.dict(by_alias=True) if req.auth else None,
request_params=req.request_params,
request_body=req.request_body,
headers=headers,
auth=auth_config,
request_params=request_params,
request_body=request_body,
response_mapping=req.response_mapping,
timeout=req.timeout,
retry=req.retry.dict() if req.retry else None
timeout=timeout,
retry=retry
)
new_tool_ref_id = am_result.get("tool_ref_id")
# Agent Manager 返回格式: {"success": true, "data": {"tool_ref_id": "xxx", ...}}
am_data = am_result.get("data", {})
new_tool_ref_id = am_data.get("tool_ref_id")
am_status = "active" if am_result.get("success") else "error"
error_message = am_result.get("message") if not am_result.get("success") else None
@@ -426,11 +641,11 @@ async def update_external_tool(
error_message = f"Agent Manager 更新工具失败: {str(e)}"
# 更新数据库记录
tool.name = req.name
tool.description = req.description
tool.url = req.url
tool.method = req.method
tool.auth_type = req.auth.type if req.auth else "none"
tool.name = new_name
tool.description = new_description
tool.url = new_url
tool.method = method
tool.auth_type = req.auth.type if req.auth else tool.auth_type
tool.tool_ref_id = new_tool_ref_id
tool.status = am_status
tool.error_message = error_message
+137 -292
View File
@@ -119,44 +119,48 @@ async def get_tools_stats(
获取工具统计数据(用于数据与工具页面)
统计指标:
- totalTools: 用户创建的工具总数
- generatedTools: 用户创建的工具总数(与totalTools相同)
- activeTools: 所有Agent使用的工具总数(去重后)
- totalTools: 用户创建的工具总数(内置工具 + 外部数据工具)
- generatedTools: 外部数据工具数量(已生成的工具)
- activeTools: 活跃的外部数据工具数量(状态为 active)
"""
from models import ExternalDataTool
user_id = principal.get("user_id")
# 统计用户创建的工具数量
# 1. 统计内置工具数量(Tool 表)
result = await db.execute(
select(func.count(Tool.id))
.where(Tool.owner_id == user_id)
)
total_tools = result.scalar() or 0
builtin_tools_count = result.scalar() or 0
# 统计所有Agent使用的工具数(从AgentBillingRecord.tools_used中统计)
# 2. 统计外部数据工具数量(ExternalDataTool 表)
result = await db.execute(
select(AgentBillingRecord.tools_used)
select(func.count(ExternalDataTool.id))
.where(ExternalDataTool.owner_id == user_id)
)
external_tools_count = result.scalar() or 0
# 3. 统计活跃的外部数据工具(状态为 active)
result = await db.execute(
select(func.count(ExternalDataTool.id))
.where(
and_(
AgentBillingRecord.user_id == user_id,
AgentBillingRecord.tools_used.isnot(None),
AgentBillingRecord.end_time.is_(None) # 只统计运行中的Agent
ExternalDataTool.owner_id == user_id,
ExternalDataTool.status == "active"
)
)
)
active_external_tools = result.scalar() or 0
# 收集所有工具ID并去重
all_tools = set()
for row in result.scalars().all():
if row and isinstance(row, list):
all_tools.update(row)
active_tools = len(all_tools)
# 总工具数 = 内置工具 + 外部数据工具
total_tools = builtin_tools_count + external_tools_count
return SuccessResponse(
data={
"totalTools": total_tools,
"generatedTools": total_tools,
"activeTools": active_tools,
"generatedTools": external_tools_count, # 外部数据工具(已生成)
"activeTools": active_external_tools, # 活跃的外部数据工具
}
)
@@ -2755,115 +2759,102 @@ async def get_custom_agent_templates(
principal: dict = Depends(require_auth),
):
"""
获取创建自定义 Agent 所需的模板信息
获取创建自定义 Agent 所需的信息
返回两类信息:
1. frameworkTemplates: 框架类型列表(A2A/langchain/MCP),用于指定 Agent 的运行框架
2. dataTemplates: 数据存储模板列表(从 Agent Manager 获取),用于指定 Agent 镜像类型
**重要说明**:
自定义 Agent 现在主要通过【外部数据工具】来定义能力,不再依赖预定义的数据库模板(mysql_agent 等已废弃)。
业务流程:
1. 前端获取模板列表
2. 用户选择数据存储模板(如 mysql_agent)和框架类型(如 MCP)
3. 创建 Agent 时传递:
- template: 数据存储模板名称(如 "mysql_agent")
- frameworkTemplate: 框架类型(如 "MCP")
4. mcp-server 转发给 Agent Manager 服务进行部署
创建自定义 Agent 的推荐流程:
1. 先创建外部数据工具(POST /api/user/external-tools)
2. 可选:将多个工具组合成工具集(POST /api/user/external-toolkits)
3. 创建自定义 Agent 时,选择外部数据工具或工具集(externalTools 或 toolkit 参数)
返回信息:
1. frameworkTemplates: 框架类型列表(MCP/A2A/langchain)
2. platformTemplates: 平台模板列表(可选使用,如需要特定基础镜像)
3. createModes: 创建模式说明
返回格式:
{
"frameworkTemplates": ["A2A", "langchain", "MCP"],
"dataTemplates": [
{
"template": "mysql_agent",
"port": 8080,
"env_info": {
"required": {"MYSQL_HOST": "...", ...},
"optional": {"MYSQL_PORT": "..."}
}
},
...
]
"frameworkTemplates": ["MCP", "A2A", "langchain"],
"platformTemplates": [...], // 可选的平台模板
"createModes": {
"recommended": "external_tools",
"modes": [
{"mode": "external_tools", "description": "使用外部数据工具(推荐)"},
{"mode": "template", "description": "使用平台模板(高级)"}
]
}
}
"""
from app.agent_manager_client import get_agent_manager_client, AgentManagerError
# 固定的框架类型
framework_templates = ["A2A", "langchain", "MCP"]
# 框架类型(MCP 为默认推荐)
framework_templates = ["MCP", "A2A", "langchain"]
# 从 Agent Manager 获取数据存储模板列表
data_templates = []
# 从 Agent Manager 获取可用的平台模板(供高级用户选用)
platform_templates = []
try:
client = get_agent_manager_client()
custom_templates = await client.list_custom_templates()
templates = await client.list_templates()
for t in custom_templates:
data_templates.append({
for t in templates:
platform_templates.append({
"template": t.template,
"port": t.port,
"env_info": t.env_info or {},
"description": _get_template_description(t.template)
})
except AgentManagerError as e:
logger.warning(f"无法从 Agent Manager 获取数据存储模板列表: {str(e)}")
# 返回默认的模板列表(基于 Agent Manager 文档,仅包含实际支持的自定义模板)
data_templates = [
{
"template": "mysql_agent",
"port": None,
"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"
}
},
"description": "MySQL 数据库 Agent,支持 SQL 查询和数据操作"
},
{
"template": "postgresql_agent",
"port": None,
"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"
}
},
"description": "PostgreSQL 数据库 Agent,支持 SQL 查询和数据操作"
}
]
logger.warning(f"无法从 Agent Manager 获取平台模板列表: {str(e)}")
except Exception as e:
logger.warning(f"获取数据存储模板失败: {str(e)}")
data_templates = []
logger.warning(f"获取平台模板失败: {str(e)}")
return SuccessResponse(data={
"frameworkTemplates": framework_templates,
"dataTemplates": data_templates
"platformTemplates": platform_templates,
"createModes": {
"recommended": "external_tools",
"modes": [
{
"mode": "external_tools",
"description": "使用外部数据工具创建(推荐)",
"params": ["externalTools", "toolkit"],
"note": "无需指定 template,系统自动生成 Agent"
},
{
"mode": "template",
"description": "使用平台模板创建(高级)",
"params": ["template"],
"note": "需要配置环境变量"
}
]
},
# 兼容旧版前端:dataTemplates 返回空数组(数据库模板已废弃)
"dataTemplates": []
})
def _get_template_description(template_name: str) -> str:
"""
获取模板描述
注意:此函数仅用于自定义模板(从 Agent Manager /templates/custom 获取的模板)。
根据 Agent Manager 文档,自定义模板仅包括:
- mysql_agent
- postgresql_agent
"""
descriptions = {
"mysql_agent": "MySQL 数据库 Agent,支持 SQL 查询和数据操作",
"postgresql_agent": "PostgreSQL 数据库 Agent,支持 SQL 查询和数据操作",
# 平台模板描述
"echo_agent": "Echo Agent,用于测试",
"search_agent": "搜索 Agent,支持网页搜索",
"search_agent_mcp": "搜索 Agent (MCP 协议)",
"search_agent_a2a": "搜索 Agent (A2A 协议)",
"jina_search_agent": "Jina 搜索 Agent",
"azure_blob_agent": "Azure Blob 存储 Agent",
"azure_blob_agent_mcp": "Azure Blob 存储 Agent (MCP)",
"azure_blob_agent_a2a": "Azure Blob 存储 Agent (A2A)",
"a2a_litellm_agent": "A2A LiteLLM Agent",
"code_ai_agent": "代码 AI Agent",
"microsoft_learn_agent": "Microsoft Learn 文档 Agent",
# 旧版数据库模板(已废弃,仅保留描述供参考)
"mysql_agent": "MySQL 数据库 Agent(已废弃,请使用外部数据工具)",
"postgresql_agent": "PostgreSQL 数据库 Agent(已废弃,请使用外部数据工具)",
}
return descriptions.get(template_name, f"{template_name} Agent")
@@ -2988,162 +2979,23 @@ async def create_custom_agent(
detail=f"渠道内存配额已满。渠道配额: {channel_memory_quota:.2f} GB,当前总使用: {channel_total_memory_used:.2f} GB,请求: {memory_request:.2f} GB"
)
# 验证框架模板类型
framework_template = req.frameworkTemplate or "MCP"
allowed_frameworks = ["A2A", "langchain", "MCP"]
if framework_template not in allowed_frameworks:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"不支持的框架模板: {framework_template}。支持的框架: {', '.join(allowed_frameworks)}"
)
# ========== 新增:从工具获取模板和配置 ==========
from uuid import UUID as PyUUID
from sqlalchemy import or_
template_name = req.template # 默认使用请求中的 template
tool_env_config = {} # 工具的环境变量配置
if req.tools and len(req.tools) > 0:
# 查询第一个工具(主工具,决定 Agent 类型)
primary_tool_id = req.tools[0]
try:
tool_result = await db.execute(
select(Tool).where(
Tool.id == PyUUID(primary_tool_id),
or_(
Tool.is_public == True,
Tool.owner_id == PyUUID(user_id)
)
)
)
primary_tool = tool_result.scalar_one_or_none()
if primary_tool:
# 如果工具有模板且请求中没有指定模板,使用工具的模板
if primary_tool.template and not req.template:
template_name = primary_tool.template
logger.info(
f"从工具获取模板: tool_id={primary_tool_id}, "
f"template={template_name}"
)
# 获取工具的环境变量配置
if primary_tool.env_config:
tool_env_config = primary_tool.env_config.copy()
logger.info(
f"从工具获取环境变量配置: tool_id={primary_tool_id}, "
f"env_keys={list(tool_env_config.keys())}"
)
except Exception as e:
logger.warning(f"查询工具失败: {str(e)}")
# 如果没有从请求或工具获取到模板,报错
if not template_name:
# ================================================
# 自定义 Agent 必须使用外部数据工具或工具集
# ================================================
if not req.externalTools and not req.toolkit:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="必须指定 template 参数或选择包含模板配置的工具"
detail="创建自定义 Agent 必须指定 externalTools(外部数据工具)或 toolkit(工具集)"
)
# ================================================
# 验证自定义 Agent 模板
try:
client = get_agent_manager_client()
custom_templates = await client.list_custom_templates()
template_names = [t.template for t in custom_templates]
if template_name not in template_names:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"无效的模板名称: {template_name}。支持的模板: {', '.join(template_names)}"
)
except HTTPException:
raise
except AgentManagerError as e:
logger.warning(f"无法获取自定义模板列表,跳过验证: {str(e)}")
except Exception as e:
logger.warning(f"模板验证失败,跳过验证: {str(e)}")
# 环境变量(由系统自动注入)
env_vars = {}
# ========== 构建环境变量 ==========
# 1. 先使用工具的环境变量配置作为基础
env_vars = tool_env_config.copy()
# 2. 再合并请求中的 envConfig(请求中的优先)
if req.envConfig:
env_vars.update(req.envConfig)
# 3. 注入框架模板类型
env_vars["FRAMEWORK_TYPE"] = framework_template
if req.endpoint:
env_vars["ENDPOINT"] = req.endpoint
if req.apiKey:
env_vars["API_KEY"] = req.apiKey
logger.info(
f"环境变量构建完成: tool_env_keys={list(tool_env_config.keys())}, "
f"req_env_keys={list((req.envConfig or {}).keys())}, "
f"final_env_keys={list(env_vars.keys())}"
)
# =================================
# ========== 工具配置传递给 Agent Manager ==========
import json
if req.tools:
# 1. 传递工具 ID 列表(JSON 字符串格式)
env_vars["TOOLS"] = json.dumps(req.tools)
# 2. 查询工具详情,构建完整工具配置
try:
tool_ids = []
for tid in req.tools:
try:
tool_ids.append(PyUUID(tid))
except (ValueError, TypeError):
logger.warning(f"无效的工具 ID 格式: {tid}")
if tool_ids:
tool_result = await db.execute(
select(Tool).where(
Tool.id.in_(tool_ids),
or_(
Tool.is_public == True,
Tool.owner_id == PyUUID(user_id)
)
)
)
tools = tool_result.scalars().all()
# 构建工具配置列表(包含 Agent 运行时需要的信息)
tools_config = []
for tool in tools:
tool_config = {
"id": str(tool.id),
"name": tool.name,
"description": tool.description,
"endpoint": tool.endpoint,
"method": tool.method,
"schema": tool.schema,
"timeout": tool.timeout,
"auth_type": tool.auth_type,
}
# 如果是用户自己的工具,传递认证配置
if tool.owner_id and str(tool.owner_id) == user_id and tool.auth_config:
tool_config["auth_config"] = tool.auth_config
tools_config.append(tool_config)
# 传递工具详细配置(供 Agent 运行时使用)
if tools_config:
env_vars["TOOLS_CONFIG"] = json.dumps(tools_config)
logger.info(
f"工具配置已准备: user_id={user_id}, tool_count={len(tools_config)}, "
f"tool_names={[t['name'] for t in tools_config]}"
)
except Exception as e:
logger.warning(f"查询工具详情失败,仅传递工具 ID 列表: {str(e)}")
# ================================================
# ========== 外部数据工具配置传递给 Agent Manager ==========
from models import ExternalDataTool, ExternalToolkit
@@ -3243,29 +3095,6 @@ async def create_custom_agent(
)
# ================================================
# ========== A2A 框架配置 ==========
if framework_template == "A2A":
import time as time_module
# 生成 Agent ID(使用名称和时间戳确保唯一性)
agent_id = f"{req.name}-{int(time_module.time())}"
env_vars["AGENT_ID"] = agent_id
# 设置 Agent 角色
agent_role = req.agentRole or env_vars.get("AGENT_ROLE", "custom_agent")
env_vars["AGENT_ROLE"] = agent_role
# 设置 Agent 能力
capabilities = req.agentCapabilities or req.tools or []
if capabilities:
env_vars["AGENT_CAPABILITIES"] = json.dumps(capabilities)
logger.info(
f"A2A 配置已准备: agent_id={agent_id}, agent_role={agent_role}, "
f"capabilities={capabilities}"
)
# ==================================
# 如果指定了模型,查询租户的 LiteLLM Key 并注入环境变量
model_name = req.model
if model_name:
@@ -3339,29 +3168,45 @@ async def create_custom_agent(
try:
client = get_agent_manager_client()
# 创建 Agent 配置
agent_config = AgentConfig(
user_id=str(user_id),
cpu_request=req.cpuRequest,
cpu_limit=req.cpuLimit or req.cpuRequest,
memory_request=req.memoryRequest,
memory_limit=req.memoryLimit or req.memoryRequest,
replicas=1, # 自定义 Agent 默认单副本
)
# 创建自定义 Agent(使用从工具获取的模板名称)
result = await client.create_custom_agent(
name=req.name,
template=template_name, # 使用从工具获取或请求中指定的模板
user_id=str(user_id),
env_vars=env_vars,
config=agent_config
)
logger.info(
f"自定义 Agent 创建请求: name={req.name}, template={template_name}, "
f"env_keys={list(env_vars.keys())}"
)
# ========== 自定义 Agent:使用外部数据工具创建 ==========
# 工具已在 Agent Manager 生成(有 tool_ref_id),直接使用 tool_refs 创建 Agent
# =======================================================
if not external_tool_refs:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="没有可用的外部数据工具(需要 tool_ref_id)"
)
logger.info(
f"创建自定义 Agent: name={req.name}, tool_refs={external_tool_refs}"
)
# 创建 Agent 配置
agent_config = AgentConfig(
user_id=str(user_id),
cpu_request=req.cpuRequest,
cpu_limit=req.cpuRequest, # limit 默认和 request 一致
memory_request=req.memoryRequest,
memory_limit=req.memoryRequest, # limit 默认和 request 一致
replicas=1,
)
# 调用 create_agent_with_tools(使用 /external-tools/agents/create-with-tools 接口)
# template 默认和 name 一致
template_name = req.name
result = await client.create_agent_with_tools(
name=req.name,
template=template_name,
tool_refs=external_tool_refs,
config=agent_config,
env=env_vars
)
logger.info(
f"自定义 Agent 创建成功: name={req.name}, "
f"status={result.status}, tool_count={len(external_tool_refs)}"
)
try:
# 更新配额使用量
@@ -3374,14 +3219,14 @@ async def create_custom_agent(
billing_record = AgentBillingRecord(
user_id=user_id,
channel_id=channel_id,
agent_type=template_name, # 使用实际使用的模板名称
agent_type=template_name, # template 默认和 name 一致
agent_name=req.name,
template_name=framework_template, # 记录框架模板类型
template_name=template_name,
is_platform_agent=False,
start_time=datetime.utcnow(),
cpu_used=req.cpuRequest,
memory_used=req.memoryRequest,
tools_used=req.tools if req.tools else [],
tools_used=[],
# ========== 保存访问信息 ==========
external_ip=access_info.get("external_ip"),
domain=access_info.get("domain"),
+73 -66
View File
@@ -815,45 +815,22 @@ class PlatformAgentInstance(BaseModel):
class CreateCustomAgentRequest(BaseModel):
"""创建自定义 Agent 请求
支持两种方式指定 Agent 模板:
1. 直接指定 template 参数
2. 选择包含模板配置的工具(tools 列表),系统会从第一个工具获取模板
环境变量配置优先级(从低到高):
1. 工具的 env_config
2. 请求中的 envConfig
使用外部数据工具或工具集创建自定义 Agent。
模板由系统自动处理,默认和 name 一致。
"""
name: str = Field(..., description="Agent 名称")
template: Optional[str] = Field(None, description="模板名称(可选,可从工具自动获取)")
frameworkTemplate: Optional[str] = Field("MCP", description="框架模板类型(A2A/langchain/MCP),默认MCP")
description: Optional[str] = Field(None, description="描述")
name: str = Field(..., description="Agent 名称(1-63字符)")
description: Optional[str] = Field(None, description="Agent 描述")
# 外部数据工具配置(必须指定 externalTools 或 toolkit 之一)
externalTools: Optional[List[str]] = Field(None, description="外部数据工具 ID 列表")
toolkit: Optional[str] = Field(None, description="工具集 ID")
# 模型配置
model: Optional[str] = Field(None, description="使用的模型名称")
# 资源配置
cpuRequest: str = Field("100m", description="CPU 请求量")
cpuLimit: Optional[str] = Field(None, description="CPU 限制量")
memoryRequest: str = Field("128Mi", description="内存请求量")
memoryLimit: Optional[str] = Field(None, description="内存限制量")
# 工具配置(第一个工具为主工具,可决定 Agent 类型)
tools: Optional[List[str]] = Field(None, description="选择的工具ID列表,第一个工具为主工具,可从中获取模板和环境变量配置")
# 外部数据工具配置(用户创建的自定义外部 API 工具)
externalTools: Optional[List[str]] = Field(None, description="外部数据工具 ID 列表,部署时会将 tool_ref_id 传给 Agent Manager")
# 工具集配置(用户创建的工具组合,最多 8 个工具)
toolkit: Optional[str] = Field(None, description="工具集 ID,使用工具集中的所有工具")
# 用户配置
endpoint: Optional[str] = Field(None, description="用户终结点")
apiKey: Optional[str] = Field(None, description="用户 API 密钥")
envConfig: Dict[str, str] = Field(default_factory=dict, description="环境变量配置(会与工具的 env_config 合并,此处优先)")
# 模型配置(可选)
model: Optional[str] = Field(None, description="使用的模型名称,用于自动注入 LiteLLM 环境变量")
# A2A 框架配置(可选,仅 frameworkTemplate="A2A" 时使用)
agentRole: Optional[str] = Field(None, description="A2A Agent 角色(如 data_analyzer, storage_manager)")
agentCapabilities: Optional[List[str]] = Field(None, description="A2A Agent 能力列表(如 [\"data_analysis\", \"web_browsing\"])")
cpuRequest: str = Field("100m", description="CPU 请求量,默认 100m")
memoryRequest: str = Field("128Mi", description="内存请求量,默认 128Mi")
class UpdateCustomAgentEnvRequest(BaseModel):
@@ -972,16 +949,26 @@ class AgentManagerCallbackResponse(BaseModel):
# ============= 外部数据工具 =============
class ExternalToolAuthConfig(BaseModel):
"""外部工具认证配置"""
type: str = Field(..., pattern="^(api_key|bearer|basic|none)$", description="认证类型")
key: Optional[str] = Field(None, description="API Key 或 Bearer Token")
in_location: Optional[str] = Field(None, alias="in", description="Key 位置: header/query/body")
name: Optional[str] = Field(None, description="Key 名称(如 X-API-Key)")
"""外部工具认证配置(简化版)
用户只需提供认证类型和凭证,系统自动推断 header 名称和位置。
"""
type: str = Field(..., pattern="^(api_key|bearer|basic|none)$", description="认证类型: api_key/bearer/basic/none")
secret: Optional[str] = Field(None, description="API Key 或 Bearer Token 密钥")
username: Optional[str] = Field(None, description="Basic Auth 用户名")
password: Optional[str] = Field(None, description="Basic Auth 密码")
# 以下字段为向后兼容保留,新用户无需填写
key: Optional[str] = Field(None, description="[兼容] API Key 或 Bearer Token,优先使用 secret")
in_location: Optional[str] = Field(None, alias="in", description="[高级] Key 位置: header/query,通常自动推断")
name: Optional[str] = Field(None, description="[高级] Key 名称,通常自动推断")
class Config:
populate_by_name = True
def get_secret_value(self) -> Optional[str]:
"""获取密钥值,优先使用 secret,兼容旧的 key 字段"""
return self.secret or self.key
class ExternalToolRetryConfig(BaseModel):
@@ -990,49 +977,68 @@ class ExternalToolRetryConfig(BaseModel):
delay_seconds: int = Field(1, ge=0, le=60, description="重试延迟(秒)")
class CreateExternalToolRequest(BaseModel):
"""创建外部数据工具请求
class ExternalToolAdvancedConfig(BaseModel):
"""外部工具高级配置(可选)
用户上传的外部数据工具配置,将发送给 Agent Manager 生成 Pydantic 工具文件。
大多数情况下不需要使用,系统会自动处理。
"""
method: Optional[str] = Field(None, pattern="^(GET|POST|PUT|DELETE|PATCH)$", description="强制指定 HTTP 方法")
headers: Optional[Dict[str, str]] = Field(None, description="自定义请求头")
timeout: Optional[int] = Field(None, ge=1, le=300, description="超时时间(秒)")
retry: Optional[ExternalToolRetryConfig] = Field(None, description="重试配置")
class CreateExternalToolRequest(BaseModel):
"""创建外部数据工具请求(简化版 v2)
只需提供最少信息,系统自动推断其余配置:
- 从 example 推断参数结构(JSON Schema)
- 从认证类型自动配置认证头
- 自动检测 HTTP 方法(默认 POST)
"""
name: str = Field(..., min_length=1, max_length=100, description="工具名称")
description: Optional[str] = Field(None, description="工具描述")
url: str = Field(..., min_length=1, max_length=500, description="API 端点 URL")
method: str = Field("POST", pattern="^(GET|POST|PUT|DELETE|PATCH)$", description="HTTP 方法")
# 请求配置
headers: Optional[Dict[str, str]] = Field(None, description="自定义请求头")
# 简化认证配置
auth: Optional[ExternalToolAuthConfig] = Field(None, description="认证配置")
# 参数定义(JSON Schema 格式)
request_params: Optional[Dict[str, Any]] = Field(None, description="URL 查询参数定义")
request_body: Optional[Dict[str, Any]] = Field(None, description="请求体定义")
# 请求示例(推荐提供,用于推断参数结构)
example: Optional[Dict[str, Any]] = Field(None, description="请求参数示例,用于自动推断参数结构")
# 响应配置
response_mapping: Optional[Dict[str, Any]] = Field(None, description="响应字段映射")
# 高级配置(可选,大多数情况不需要)
advanced: Optional[ExternalToolAdvancedConfig] = Field(None, description="高级配置(可选)")
# 运行配置
timeout: int = Field(30, ge=1, le=300, description="超时时间(秒)")
retry: Optional[ExternalToolRetryConfig] = Field(None, description="重试配置")
# 以下字段为向后兼容保留
method: Optional[str] = Field(None, pattern="^(GET|POST|PUT|DELETE|PATCH)$", description="[兼容] HTTP 方法,通常自动检测")
headers: Optional[Dict[str, str]] = Field(None, description="[兼容] 自定义请求头")
request_params: Optional[Dict[str, Any]] = Field(None, description="[兼容] URL 查询参数定义(JSON Schema)")
request_body: Optional[Dict[str, Any]] = Field(None, description="[兼容] 请求体定义(JSON Schema)")
response_mapping: Optional[Dict[str, Any]] = Field(None, description="[兼容] 响应字段映射")
timeout: Optional[int] = Field(None, ge=1, le=300, description="[兼容] 超时时间(秒)")
retry: Optional[ExternalToolRetryConfig] = Field(None, description="[兼容] 重试配置")
class UpdateExternalToolRequest(BaseModel):
"""更新外部数据工具请求
需要传递完整配置,因为 MCP-Server 不存储完整配置。
支持部分更新,只需传递要修改的字段。
"""
name: str = Field(..., min_length=1, max_length=100, description="工具名称")
name: Optional[str] = Field(None, min_length=1, max_length=100, description="工具名称")
description: Optional[str] = Field(None, description="工具描述")
url: str = Field(..., min_length=1, max_length=500, description="API 端点 URL")
method: str = Field("POST", pattern="^(GET|POST|PUT|DELETE|PATCH)$", description="HTTP 方法")
headers: Optional[Dict[str, str]] = Field(None, description="自定义请求头")
url: Optional[str] = Field(None, min_length=1, max_length=500, description="API 端点 URL")
auth: Optional[ExternalToolAuthConfig] = Field(None, description="认证配置")
request_params: Optional[Dict[str, Any]] = Field(None, description="URL 查询参数定义")
request_body: Optional[Dict[str, Any]] = Field(None, description="请求体定义")
response_mapping: Optional[Dict[str, Any]] = Field(None, description="响应字段映射")
timeout: int = Field(30, ge=1, le=300, description="超时时间(秒)")
retry: Optional[ExternalToolRetryConfig] = Field(None, description="重试配置")
example: Optional[Dict[str, Any]] = Field(None, description="请求参数示例")
advanced: Optional[ExternalToolAdvancedConfig] = Field(None, description="高级配置")
# 以下字段为向后兼容保留
method: Optional[str] = Field(None, pattern="^(GET|POST|PUT|DELETE|PATCH)$", description="[兼容] HTTP 方法")
headers: Optional[Dict[str, str]] = Field(None, description="[兼容] 自定义请求头")
request_params: Optional[Dict[str, Any]] = Field(None, description="[兼容] URL 查询参数定义")
request_body: Optional[Dict[str, Any]] = Field(None, description="[兼容] 请求体定义")
response_mapping: Optional[Dict[str, Any]] = Field(None, description="[兼容] 响应字段映射")
timeout: Optional[int] = Field(None, ge=1, le=300, description="[兼容] 超时时间")
retry: Optional[ExternalToolRetryConfig] = Field(None, description="[兼容] 重试配置")
class ExternalToolInfo(BaseModel):
@@ -1043,6 +1049,7 @@ class ExternalToolInfo(BaseModel):
url: str
method: str
auth_type: str
tool_ref_id: Optional[str] = None # Agent Manager 返回的工具标识
status: str # pending, active, error
usage_count: int = 0
created_at: str
+10 -2
View File
@@ -204,7 +204,6 @@ class Agent(BaseModel, Base):
Index("idx_agent_owner", owner_id),
Index("idx_agent_status", status),
Index("idx_agent_pod_name", pod_name),
Index("idx_agent_template", template),
Index("idx_agent_k8s_status", k8s_status),
Index("idx_agent_health_status", health_status),
)
@@ -1382,11 +1381,20 @@ class ExternalDataTool(BaseModel, Base):
name = Column(String(100), nullable=False)
description = Column(Text)
# API 配置(仅用于展示,实际配置存储在 Agent Manager)
# API 配置
url = Column(String(500), nullable=False)
method = Column(String(10), nullable=False, default="POST")
auth_type = Column(String(20), default="none") # api_key, bearer, basic, none
# 完整配置(用于创建自定义 Agent 时传递给 Agent Manager)
headers = Column(JSON) # 自定义请求头
auth_config = Column(JSON) # 完整认证配置 {"type": "...", "key": "...", "in": "...", "name": "..."}
request_params = Column(JSON) # URL 查询参数定义 (JSON Schema)
request_body = Column(JSON) # 请求体定义 (JSON Schema)
response_mapping = Column(JSON) # 响应字段映射
timeout = Column(Integer, default=30) # 超时时间(秒)
retry_config = Column(JSON) # 重试配置 {"max_retries": 3, "retry_delay": 1}
# Agent Manager 关联(核心字段)
tool_ref_id = Column(String(100), unique=True) # AM 返回的工具标识,部署时传给 AM
status = Column(String(20), default="pending") # pending, active, error