forked from xiaohei/taiji-AI-PAD
备份
This commit is contained in:
@@ -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
@@ -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
|
||||
**代码版本**: 基于当前代码库状态
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
Executable
+317
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
Executable
+414
@@ -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 "$@"
|
||||
|
||||
@@ -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 "========================================="
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user