更新外部数据工具和工具集

This commit is contained in:
zhanggangyong
2026-01-23 10:57:13 +00:00
parent d59dcfa941
commit fe69a70a3a
23 changed files with 3782 additions and 2630 deletions
@@ -1,479 +0,0 @@
# 数据工具与自定义Agent - 功能修复文档
> **创建日期**: 2026-01-13
> **状态**: 待修复
> **优先级**: 高
---
## 1. 问题概述
### 当前实现与预期业务逻辑不一致
**预期业务逻辑**:
1. 前端获取 `dataTemplates`(Agent 模板列表,如 `mysql_agent`、`postgresql_agent`)
2. 用户选择模板,根据模板的 `env_info` 填写配置值(如 MySQL 连接信息),保存为**工具**
3. 用户创建自定义 Agent 时选择已创建的工具
4. MCP-Server 从工具中获取模板类型和配置,传递给 Agent Manager 创建对应的 Agent
**当前实现问题**:
- Tool 模型设计为通用 API 调用配置(endpoint、method、schema)
- **缺少 `template` 字段**:无法关联 Agent 模板
- **缺少 `env_config` 字段**:无法存储模板所需的环境变量配置
- 创建 Agent 时,工具只作为附加的 API 能力,而不是决定 Agent 类型的关键信息
---
## 2. 正确的业务流程
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 正确的业务流程 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ① 获取模板列表 ② 创建工具(基于模板) ③ 创建Agent(选择工具) │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ GET │ │ POST │ │ POST │ │
│ │ /templates │──────→│ /tools/create │───────────→│ /custom-agents│ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ 返回 dataTemplates 保存到 Tool 表 从工具获取 template │
│ [mysql_agent, - template: mysql_agent 和 envConfig, │
│ postgresql_agent] - envConfig: { 传递给 Agent Manager │
│ MYSQL_HOST: "...", │
│ 含 env_info: MYSQL_USER: "...", │
│ - required ... │
│ - optional } │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
### 关键概念
| 概念 | 说明 |
|------|------|
| **模板 (template)** | Agent Manager 提供的镜像类型,如 `mysql_agent`,包含 `env_info` 定义所需配置 |
| **工具 (Tool)** | 用户基于模板创建的配置实例,包含 `template` + 填写好的 `envConfig` |
| **自定义 Agent** | 根据工具的模板类型和配置创建的 K8s Pod |
---
## 3. 需要修改的内容
### 3.1 数据库模型修改
**文件**: `services/mcp-server/models.py`
**修改 Tool 模型,添加字段**:
```python
class Tool(BaseModel, Base):
"""工具模型"""
__tablename__ = "tools"
name = Column(String(100), nullable=False)
description = Column(Text)
category = Column(String(50)) # database, api, function 等
# ========== 新增:模板相关字段 ==========
template = Column(String(100)) # 模板名称,如 "mysql_agent", "postgresql_agent"
env_config = Column(JSON, default=dict) # 环境变量配置,根据模板 env_info 填写
# =========================================
# 工具定义(保留,用于自定义 API 工具)
schema = Column(JSON) # 改为可空,模板工具不需要
endpoint = Column(String(500))
method = Column(String(10), default="POST")
# ... 其他字段保持不变
```
### 3.2 创建工具接口修改
**文件**: `services/mcp-server/app/routes/user.py`
**修改 `POST /api/user/tools/create` 接口**:
```python
@router.post("/tools/create", response_model=SuccessResponse)
async def create_tool(
req: dict,
principal: dict = Depends(require_auth),
db: AsyncSession = Depends(get_db)
):
"""
创建工具(基于模板)
请求体示例(MySQL 工具):
{
"name": "my-mysql-tool",
"description": "我的MySQL数据库连接工具",
"template": "mysql_agent",
"envConfig": {
"MYSQL_HOST": "mysql.example.com",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "password123",
"MYSQL_DATABASE": "mydb",
"MYSQL_PORT": "3306",
"OPENAI_API_KEY": "sk-xxx"
}
}
"""
user_id = principal.get("user_id")
# 验证模板名称
template_name = req.get("template")
if template_name:
# 从 Agent Manager 获取模板列表并验证
try:
from app.agent_manager_client import get_agent_manager_client, AgentManagerError
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 AgentManagerError:
pass # Agent Manager 不可用时跳过验证
# 检查工具名称是否已存在
result = await db.execute(
select(Tool).where(
and_(
Tool.owner_id == user_id,
Tool.name == req.get("name")
)
)
)
existing_tool = result.scalar_one_or_none()
if existing_tool:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"工具名称 '{req.get('name')}' 已存在"
)
# 创建工具
tool = Tool(
name=req.get("name"),
description=req.get("description"),
category="database" if template_name else req.get("type", "api"),
template=template_name, # 新增
env_config=req.get("envConfig", {}), # 新增
schema=req.get("config", {}).get("schema") if not template_name else None,
endpoint=req.get("config", {}).get("endpoint") if not template_name else None,
method=req.get("config", {}).get("method", "GET") if not template_name else None,
auth_config={"apiKey": req.get("config", {}).get("apiKey")} if req.get("config", {}).get("apiKey") else {},
owner_id=user_id,
is_active=True,
is_public=False,
)
db.add(tool)
await db.commit()
await db.refresh(tool)
return SuccessResponse(
data={
"id": str(tool.id),
"name": tool.name,
"template": tool.template,
},
message="工具创建成功"
)
```
### 3.3 创建自定义 Agent 接口修改
**文件**: `services/mcp-server/app/routes/user.py`
**修改 `POST /api/user/custom-agents` 接口的工具处理逻辑**:
```python
@router.post("/custom-agents", response_model=SuccessResponse)
async def create_custom_agent(
req: CreateCustomAgentRequest,
principal: dict = Depends(require_auth),
db: AsyncSession = Depends(get_db)
):
"""
创建自定义 Agent
新逻辑:从选中的工具获取模板类型和环境变量配置
"""
# ... 配额检查等代码保持不变 ...
# ========== 新增:从工具获取模板和配置 ==========
template_name = req.template # 默认使用请求中的 template
env_vars = req.envConfig or {}
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 and primary_tool.template:
# 使用工具的模板类型
template_name = primary_tool.template
# 合并工具的环境变量配置
if primary_tool.env_config:
env_vars = {**primary_tool.env_config, **env_vars}
logger.info(
f"从工具获取配置: tool_id={primary_tool_id}, "
f"template={template_name}, env_keys={list(env_vars.keys())}"
)
except Exception as e:
logger.warning(f"查询工具失败: {str(e)}")
# ================================================
# 验证模板
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 AgentManagerError as e:
logger.warning(f"无法获取模板列表,跳过验证: {str(e)}")
# ... 后续代码使用 template_name 和 env_vars 创建 Agent ...
```
### 3.4 获取用户工具列表接口修改
**文件**: `services/mcp-server/app/routes/user.py`
**修改 `GET /api/user/tools` 响应格式**:
```python
@router.get("/tools", response_model=SuccessResponse)
async def get_user_tools(
principal: dict = Depends(require_auth),
db: AsyncSession = Depends(get_db)
):
"""获取用户创建的所有工具"""
user_id = principal.get("user_id")
result = await db.execute(
select(Tool)
.where(Tool.owner_id == user_id)
.order_by(desc(Tool.created_at))
)
tools = result.scalars().all()
tools_data = []
for tool in tools:
tools_data.append({
"id": str(tool.id),
"name": tool.name,
"description": tool.description,
"template": tool.template, # 新增
"category": tool.category,
"envConfig": tool.env_config, # 新增(注意:敏感信息如密码应脱敏)
"created_at": tool.created_at.isoformat() if tool.created_at else None,
"updated_at": tool.updated_at.isoformat() if tool.updated_at else None,
"is_active": tool.is_active,
})
return SuccessResponse(data={"tools": tools_data})
```
### 3.5 数据库迁移
需要创建数据库迁移脚本,添加新字段:
```sql
-- 添加 template 字段
ALTER TABLE tools ADD COLUMN template VARCHAR(100);
-- 添加 env_config 字段
ALTER TABLE tools ADD COLUMN env_config JSON DEFAULT '{}';
-- 创建索引
CREATE INDEX idx_tool_template ON tools(template);
```
---
## 4. 更新后的接口文档
### 4.1 创建工具(基于模板)
**接口**: `POST /api/user/tools/create`
**请求参数**:
```typescript
interface CreateToolRequest {
name: string; // 工具名称(必填)
description?: string; // 工具描述
template: string; // 模板名称(必填,来自 dataTemplates[].template)
envConfig: Record<string, string>; // 环境变量配置(必填,根据模板 env_info 填写)
}
```
**请求示例(MySQL 工具)**:
```json
{
"name": "my-mysql-tool",
"description": "我的MySQL数据库连接工具",
"template": "mysql_agent",
"envConfig": {
"MYSQL_HOST": "mysql.example.com",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "password123",
"MYSQL_DATABASE": "mydb",
"MYSQL_PORT": "3306",
"OPENAI_API_KEY": "sk-xxx"
}
}
```
**请求示例(PostgreSQL 工具)**:
```json
{
"name": "my-pgsql-tool",
"description": "我的PostgreSQL数据库连接工具",
"template": "postgresql_agent",
"envConfig": {
"POSTGRES_HOST": "postgres.example.com",
"POSTGRES_USER": "postgres",
"POSTGRES_PASSWORD": "password123",
"POSTGRES_DATABASE": "mydb",
"POSTGRES_PORT": "5432",
"OPENAI_API_KEY": "sk-xxx"
}
}
```
**响应示例**:
```json
{
"success": true,
"data": {
"id": "d14cd898-e2cf-4150-a7f9-53b962c1c9a2",
"name": "my-mysql-tool",
"template": "mysql_agent"
},
"message": "工具创建成功"
}
```
### 4.2 创建自定义 Agent(选择工具)
**接口**: `POST /api/user/custom-agents`
**请求参数变化**:
```typescript
interface CreateCustomAgentRequest {
name: string; // Agent名称(必填)
tools: string[]; // 工具ID列表(必填,第一个工具决定 Agent 类型)
// 以下参数变为可选(可从工具自动获取)
template?: string; // 模板名称(可选,默认从工具获取)
envConfig?: Record<string, string>; // 额外环境变量(可选,会与工具配置合并)
// 资源配置
cpuRequest?: string; // 默认 "100m"
memoryRequest?: string; // 默认 "128Mi"
// 其他可选参数
frameworkTemplate?: string; // 默认 "MCP"
model?: string;
}
```
**请求示例**:
```json
{
"name": "my-mysql-agent",
"tools": ["d14cd898-e2cf-4150-a7f9-53b962c1c9a2"],
"cpuRequest": "500m",
"memoryRequest": "1Gi",
"model": "gpt-4"
}
```
**说明**:
- `template` 和 `envConfig` 从工具自动获取
- 请求中的 `envConfig` 会与工具配置合并(请求中的优先)
---
## 5. 修改检查清单
- [ ] **models.py**: 添加 `template` 和 `env_config` 字段到 Tool 模型
- [ ] **数据库迁移**: 创建迁移脚本添加新字段
- [ ] **user.py - create_tool**: 修改创建工具接口,支持 template 和 envConfig
- [ ] **user.py - create_custom_agent**: 修改创建 Agent 接口,从工具获取模板和配置
- [ ] **user.py - get_user_tools**: 修改响应格式,包含 template 和 envConfig
- [ ] **schemas.py**: 更新请求/响应模型定义
- [ ] **接口文档**: 更新前端接口文档
---
## 6. 测试用例
### 6.1 创建工具
```bash
# 创建 MySQL 工具
curl -X POST /api/user/tools/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-mysql-tool",
"template": "mysql_agent",
"envConfig": {
"MYSQL_HOST": "mysql.example.com",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "password123",
"MYSQL_DATABASE": "mydb"
}
}'
```
### 6.2 创建 Agent(选择工具)
```bash
# 创建 Agent,选择已创建的工具
curl -X POST /api/user/custom-agents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-mysql-agent",
"tools": ["d14cd898-e2cf-4150-a7f9-53b962c1c9a2"],
"cpuRequest": "500m",
"memoryRequest": "1Gi"
}'
```
---
**如有问题,请联系大智开发团队。**
+843
View File
@@ -0,0 +1,843 @@
# 外部数据工具 - 接口文档
> **版本**: 2026-01-23 v1.0
> **基础路径**: `/api/user/external-tools`
> **认证方式**: Bearer Token(在请求头添加 `Authorization: Bearer <JWT Token>`)
---
## 📊 业务流程
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 外部数据工具流程 │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ① 创建外部工具 ② Agent Manager 生成 ③ 创建自定义 Agent │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ POST │ ───→ │ 生成 Pydantic │ ───→ │ POST │ │
│ │ /external-tools│ │ 工具代码文件 │ │ /custom-agents │ │
│ └───────────────┘ └───────────────┘ │ +externalTools │ │
│ │ │ └───────────────┘ │
│ ↓ ↓ │ │
│ 保存基本信息 返回 tool_ref_id 传递 tool_ref_ids │
│ 到 PostgreSQL 给 Agent Manager │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
### 核心概念
| 概念 | 说明 |
|------|------|
| **外部数据工具** | 用户创建的连接外部 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>
```
---
## 📑 接口列表
### 外部数据工具接口
| 序号 | 接口 | 方法 | 说明 |
|:---:|------|------|------|
| 1 | `/api/user/external-tools` | POST | 创建外部数据工具 |
| 2 | `/api/user/external-tools/upload` | POST | 上传 JSON 文件创建工具 |
| 3 | `/api/user/external-tools` | GET | 获取工具列表 |
| 4 | `/api/user/external-tools/{tool_id}` | GET | 获取工具详情 |
| 5 | `/api/user/external-tools/{tool_id}` | PUT | 更新工具配置 |
| 6 | `/api/user/external-tools/{tool_id}` | DELETE | 删除工具 |
| 7 | `/api/user/external-tools/{tool_id}/test` | POST | 测试工具连接 |
### 工具集接口
| 序号 | 接口 | 方法 | 说明 |
|:---:|------|------|------|
| 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 | 删除工具集 |
### 自定义 Agent 接口
| 序号 | 接口 | 方法 | 说明 |
|:---:|------|------|------|
| 8 | `/api/user/custom-agents` | POST | 创建自定义 Agent(支持外部工具/工具集) |
---
## 1️⃣ 创建外部数据工具
### 接口
```
POST /api/user/external-tools
```
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ✅ | 工具名称(1-100字符) |
| `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)
#### API Key 认证
```json
{
"auth": {
"type": "api_key",
"key": "sk-xxxxxxxxxxxx",
"in": "header",
"name": "X-API-Key"
}
}
```
#### Bearer Token 认证
```json
{
"auth": {
"type": "bearer",
"key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
#### Basic Auth 认证
```json
{
"auth": {
"type": "basic",
"username": "admin",
"password": "password123"
}
}
```
### 请求示例
```json
{
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"auth": {
"type": "api_key",
"key": "sk-xxxxxxxxxxxx",
"in": "header",
"name": "X-API-Key"
},
"request_params": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称",
"required": true
}
}
},
"timeout": 30
}
```
### 响应示例
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"tool_ref_id": "tool-weather-abc123",
"status": "active",
"created_at": "2026-01-23T10:00:00Z"
},
"message": "外部数据工具创建成功"
}
```
---
## 2️⃣ 上传 JSON 文件创建工具
### 接口
```
POST /api/user/external-tools/upload
Content-Type: multipart/form-data
```
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `file` | File | ✅ | JSON 配置文件(.json) |
### JSON 文件格式
```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
}
}
},
"timeout": 10
}
```
### 响应示例
同创建接口
---
## 3️⃣ 获取工具列表
### 接口
```
GET /api/user/external-tools
```
### 查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `status` | string | ❌ | 过滤状态:active/pending/error |
| `page` | integer | ❌ | 页码,默认 1 |
| `page_size` | integer | ❌ | 每页数量,默认 20 |
### 响应示例
```json
{
"success": true,
"data": {
"tools": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"auth_type": "api_key",
"status": "active",
"usage_count": 15,
"created_at": "2026-01-23T10:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}
}
```
---
## 4️⃣ 获取工具详情
### 接口
```
GET /api/user/external-tools/{tool_id}
```
### 路径参数
| 参数 | 类型 | 说明 |
|-----|------|------|
| `tool_id` | string | 工具ID(UUID) |
### 响应示例
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool",
"description": "查询天气信息的外部数据工具",
"url": "https://api.weather.com/v1/forecast",
"method": "POST",
"auth_type": "api_key",
"tool_ref_id": "tool-weather-abc123",
"status": "active",
"usage_count": 15,
"created_at": "2026-01-23T10:00:00Z",
"updated_at": "2026-01-23T10:00:00Z"
}
}
```
> **注意**:MCP-Server 只存储基本展示信息,不存储完整配置和敏感信息。
---
## 5️⃣ 更新工具配置
### 接口
```
PUT /api/user/external-tools/{tool_id}
```
### 请求参数
与创建接口相同,需要传递完整配置(因为 MCP-Server 不存储完整配置)。
### 响应示例
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-query-tool-v2",
"tool_ref_id": "tool-weather-abc123-v2",
"status": "active",
"updated_at": "2026-01-23T11:00:00Z"
},
"message": "外部数据工具更新成功"
}
```
---
## 6️⃣ 删除工具
### 接口
```
DELETE /api/user/external-tools/{tool_id}
```
### 响应示例
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000"
},
"message": "外部数据工具删除成功"
}
```
---
## 7️⃣ 测试工具连接
### 接口
```
POST /api/user/external-tools/{tool_id}/test
```
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `test_params` | object | ❌ | 测试参数 |
### 请求示例
```json
{
"test_params": {
"city": "北京"
}
}
```
### 响应示例
```json
{
"success": true,
"data": {
"connected": true,
"response_time_ms": 156,
"status_code": 200,
"sample_response": {
"status": "ok",
"data": {
"city": "北京",
"temperature": "15°C"
}
}
},
"message": "工具连接测试成功"
}
```
---
## 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。
### 9️⃣ 创建工具集
```
POST /api/user/toolkits
```
#### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ✅ | 工具集名称(1-100字符) |
| `description` | string | ❌ | 工具集描述 |
| `tool_ids` | string[] | ✅ | 外部数据工具 ID 列表(1-8 个) |
#### 请求示例
```json
{
"name": "数据分析工具集",
"description": "包含数据查询和分析相关工具",
"tool_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
]
}
```
#### 响应示例
```json
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440002",
"name": "数据分析工具集",
"description": "包含数据查询和分析相关工具",
"tool_count": 2,
"created_at": "2026-01-23T10:00:00Z"
},
"message": "工具集创建成功"
}
```
---
### 🔟 获取工具集列表
```
GET /api/user/toolkits
```
#### 查询参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `page` | integer | ❌ | 页码,默认 1 |
| `page_size` | integer | ❌ | 每页数量,默认 20,最大 100 |
#### 响应示例
```json
{
"success": true,
"data": {
"toolkits": [
{
"id": "660e8400-e29b-41d4-a716-446655440002",
"name": "数据分析工具集",
"description": "包含数据查询和分析相关工具",
"tool_count": 2,
"usage_count": 5,
"created_at": "2026-01-23T10:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 20
}
}
```
---
### 1️⃣1️⃣ 获取工具集详情
```
GET /api/user/toolkits/{toolkit_id}
```
#### 响应示例
```json
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440002",
"name": "数据分析工具集",
"description": "包含数据查询和分析相关工具",
"tool_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
],
"tools": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "weather-api",
"description": "天气查询 API",
"url": "https://api.weather.com/current",
"method": "GET",
"status": "active"
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "stock-api",
"description": "股票查询 API",
"url": "https://api.stock.com/price",
"method": "GET",
"status": "active"
}
],
"usage_count": 5,
"created_at": "2026-01-23T10:00:00Z",
"updated_at": "2026-01-23T12:00:00Z"
}
}
```
---
### 1️⃣2️⃣ 更新工具集
```
PUT /api/user/toolkits/{toolkit_id}
```
#### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|-----|------|:---:|------|
| `name` | string | ❌ | 工具集名称 |
| `description` | string | ❌ | 工具集描述 |
| `tool_ids` | string[] | ❌ | 工具 ID 列表(1-8 个) |
---
### 1️⃣3️⃣ 删除工具集
```
DELETE /api/user/toolkits/{toolkit_id}
```
> ⚠️ **注意**:删除工具集不会删除其中的工具,只是解除组合关系。
---
## 📌 在自定义 Agent 中使用工具集
创建自定义 Agent 时,可以通过 `toolkit` 字段指定工具集:
```json
{
"name": "my-data-agent",
"template": "custom_agent",
"toolkit": "660e8400-e29b-41d4-a716-446655440002",
"cpuRequest": "500m",
"memoryRequest": "512Mi"
}
```
也可以同时使用工具集和单独的工具(会自动合并去重):
```json
{
"name": "my-data-agent",
"template": "custom_agent",
"toolkit": "660e8400-e29b-41d4-a716-446655440002",
"externalTools": ["770e8400-e29b-41d4-a716-446655440003"],
"cpuRequest": "500m",
"memoryRequest": "512Mi"
}
```
---
**如有问题,请联系开发团队。**
-501
View File
@@ -1,501 +0,0 @@
# Agent 域名访问改动计划
> **版本**: 2026-01-14 v1
> **状态**: ✅ 已完成
> **相关服务**: mcp-server, agent-manager
> **完成时间**: 2026-01-14
---
## 📋 背景与需求
### 业务变更说明
1. **Agent Manager 服务升级**:部署好的每个 Pod 现在会自动绑定域名和外网 IP
2. **访问方式变更**:租户后续将使用域名访问属于自己的平台 Agent 和自定义 Agent
3. **数据存储需求**:需要记录 Agent Manager 返回的访问信息(domain、external_ip 等)
### Agent Manager 返回的 access_info 结构
```json
{
"access_info": {
"external_ip": "135.171.210.24",
"ip_url": "http://135.171.210.24:80",
"domain": "my-agent.taijiagent.com",
"domain_url": "http://my-agent.taijiagent.com",
"recommended": "http://my-agent.taijiagent.com"
}
}
```
---
## 🔍 当前代码分析
### 1. 数据库模型现状
#### Agent 模型 (`models.py:125`)
```python
class Agent(BaseModel, Base):
# ... 已有字段
access_url = Column(String(500)) # 访问 URL(单个字段)
endpoints = Column(JSON, default=dict) # 端点信息
# ❌ 缺少 domain、external_ip 等字段
```
#### AgentBillingRecord 模型 (`models.py:1157`)
```python
class AgentBillingRecord(BaseModel, Base):
# ... 已有字段
agent_name = Column(String(100), nullable=False)
# ❌ 缺少 access_info 相关字段(domain、external_ip、access_url)
```
### 2. Agent Manager Client 现状
#### AgentCreateResult (`agent_manager_client.py:97`)
```python
@dataclass
class AgentCreateResult:
name: str
namespace: str
status: str
access_info: Optional[Dict[str, Any]] = None # ✅ 已有,但未完整使用
```
#### AgentStatusResult (`agent_manager_client.py:119`)
```python
@dataclass
class AgentStatusResult:
# ❌ 缺少 domain、external_ip 等字段
access_url: Optional[str] = None # 只有单个 access_url
endpoints: Optional[List[str]] = None
```
### 3. 创建 Agent 代码现状
#### 平台 Agent 创建 (`user.py:1427-1439`)
```python
result = await client.create_agent(...)
# 创建 Agent 记录时
agent = Agent(
name=instance_name,
# ❌ 未保存 result.access_info 中的 domain、external_ip
)
```
#### 自定义 Agent 创建 (`user.py:3146-3157`)
```python
result = await client.create_custom_agent(...)
# ❌ 响应中只返回了 accessInfo,未持久化 domain 到数据库
return SuccessResponse(
data={
"accessInfo": result.access_info, # 只是透传,未存储
}
)
```
### 4. 查询 Agent 列表代码现状
#### 用户 Agent 资源查询 (`user.py:3904-3999`)
```python
@router.get("/resources/agents")
async def get_user_agents_info(...):
# 从 Agent Manager 获取状态
agent_status = await client.get_agent_status(record.agent_name)
agent_info["accessUrl"] = agent_status.access_url # ❌ 只返回 access_url
# ❌ 缺少 domain、external_ip 等字段
```
---
## ✅ 改动方案
### 阶段一:数据库模型改动
#### 1.1 修改 AgentBillingRecord 模型
**文件**: `services/mcp-server/models.py`
```python
class AgentBillingRecord(BaseModel, Base):
# ... 已有字段
# ========== 新增:访问信息字段 ==========
external_ip = Column(String(45), nullable=True) # 外网 IP 地址
domain = Column(String(255), nullable=True) # 域名
domain_url = Column(String(500), nullable=True) # 域名访问地址
access_url = Column(String(500), nullable=True) # 推荐访问地址
service_port = Column(Integer, nullable=True) # 服务端口
namespace = Column(String(100), nullable=True) # K8s 命名空间
```
#### 1.2 创建数据库迁移脚本
**文件**: `services/mcp-server/alembic/versions/xxxx_add_agent_access_info.py`
```python
"""Add agent access info fields
Revision ID: xxxx
"""
def upgrade():
op.add_column('agent_billing_records',
sa.Column('external_ip', sa.String(45), nullable=True))
op.add_column('agent_billing_records',
sa.Column('domain', sa.String(255), nullable=True))
op.add_column('agent_billing_records',
sa.Column('domain_url', sa.String(500), nullable=True))
op.add_column('agent_billing_records',
sa.Column('access_url', sa.String(500), nullable=True))
op.add_column('agent_billing_records',
sa.Column('service_port', sa.Integer, nullable=True))
op.add_column('agent_billing_records',
sa.Column('namespace', sa.String(100), nullable=True))
# 添加索引(可选,用于按域名查询)
op.create_index('idx_agent_billing_domain', 'agent_billing_records', ['domain'])
def downgrade():
op.drop_index('idx_agent_billing_domain', 'agent_billing_records')
op.drop_column('agent_billing_records', 'namespace')
op.drop_column('agent_billing_records', 'service_port')
op.drop_column('agent_billing_records', 'access_url')
op.drop_column('agent_billing_records', 'domain_url')
op.drop_column('agent_billing_records', 'domain')
op.drop_column('agent_billing_records', 'external_ip')
```
---
### 阶段二:Agent Manager Client 改动
#### 2.1 修改 AgentStatusResult
**文件**: `services/mcp-server/app/agent_manager_client.py`
```python
@dataclass
class AgentStatusResult:
# ... 已有字段
# ========== 新增:访问信息字段 ==========
external_ip: Optional[str] = None # 外网 IP
domain: Optional[str] = None # 域名
domain_url: Optional[str] = None # 域名访问地址
```
#### 2.2 修改 get_agent_status 方法解析逻辑
**文件**: `services/mcp-server/app/agent_manager_client.py`
在 `get_agent_status` 方法中,需要解析 Agent Manager 返回的 access_info:
```python
async def get_agent_status(self, name: str) -> AgentStatusResult:
# ... 现有逻辑
# 解析 access_info
access_info = data.get("access_info", {})
return AgentStatusResult(
# ... 现有字段
external_ip=access_info.get("external_ip"),
domain=access_info.get("domain"),
domain_url=access_info.get("domain_url"),
access_url=access_info.get("recommended") or access_info.get("domain_url"),
)
```
---
### 阶段三:创建 Agent 代码改动
#### 3.1 平台 Agent 创建改动
**文件**: `services/mcp-server/app/routes/user.py`
**位置**: `list_platform_agents` / `deploy_platform_agent` 函数
```python
# 在创建 billing_record 时保存 access_info
billing_record = AgentBillingRecord(
# ... 已有字段
# ========== 新增:保存访问信息 ==========
external_ip=result.access_info.get("external_ip") if result.access_info else None,
domain=result.access_info.get("domain") if result.access_info else None,
domain_url=result.access_info.get("domain_url") if result.access_info else None,
access_url=result.access_info.get("recommended") if result.access_info else None,
service_port=result.service_port,
namespace=result.namespace,
)
```
#### 3.2 自定义 Agent 创建改动
**文件**: `services/mcp-server/app/routes/user.py`
**位置**: `create_custom_agent` 函数(约第 3146-3213 行)
```python
# 在创建 billing_record 时保存 access_info
billing_record = AgentBillingRecord(
# ... 已有字段
# ========== 新增:保存访问信息 ==========
external_ip=result.access_info.get("external_ip") if result.access_info else None,
domain=result.access_info.get("domain") if result.access_info else None,
domain_url=result.access_info.get("domain_url") if result.access_info else None,
access_url=result.access_info.get("recommended") if result.access_info else None,
service_port=result.service_port,
namespace=result.namespace,
)
```
---
### 阶段四:查询 Agent 列表改动
#### 4.1 用户 Agent 资源查询改动
**文件**: `services/mcp-server/app/routes/user.py`
**位置**: `get_user_agents_info` 函数(约第 3904-3999 行)
```python
@router.get("/resources/agents", response_model=SuccessResponse)
async def get_user_agents_info(...):
for record in billing_records:
agent_info = {
# ... 已有字段
# ========== 新增:访问信息字段(优先使用数据库存储的值) ==========
"externalIp": record.external_ip,
"domain": record.domain,
"domainUrl": record.domain_url,
"accessUrl": record.access_url, # 推荐访问地址
}
# 从 Agent Manager 获取最新状态(实时更新 IP 等信息)
if agent_manager_available and client:
try:
agent_status = await client.get_agent_status(record.agent_name)
# 更新实时状态
agent_info["status"] = agent_status.status
agent_info["healthStatus"] = agent_status.health_status
agent_info["podIp"] = agent_status.pod_ip
# 更新访问信息(如果 Agent Manager 返回了新的值)
if agent_status.external_ip:
agent_info["externalIp"] = agent_status.external_ip
if agent_status.domain:
agent_info["domain"] = agent_status.domain
if agent_status.domain_url:
agent_info["domainUrl"] = agent_status.domain_url
if agent_status.access_url:
agent_info["accessUrl"] = agent_status.access_url
except Exception as e:
logger.warning(f"获取 Agent {record.agent_name} 状态失败: {e}")
```
#### 4.2 自定义 Agent 列表查询改动
**文件**: `services/mcp-server/app/routes/user.py`
**位置**: `list_my_custom_agents` 函数(约第 3462-3516 行)
同样需要添加 domain 等字段的返回。
---
### 阶段五:响应模型改动
#### 5.1 添加/修改 Schema
**文件**: `services/mcp-server/app/schemas.py` 或 `services/mcp-server/schemas.py`
```python
class AgentAccessInfo(BaseModel):
"""Agent 访问信息"""
external_ip: Optional[str] = Field(None, description="外网 IP 地址")
domain: Optional[str] = Field(None, description="域名")
domain_url: Optional[str] = Field(None, description="域名访问地址")
ip_url: Optional[str] = Field(None, description="IP 访问地址")
recommended: Optional[str] = Field(None, description="推荐访问地址")
class AgentResourceInfo(BaseModel):
"""用户 Agent 资源信息"""
name: str
template: str
templateName: Optional[str] = None
status: str
healthStatus: str
# Pod 信息
podIp: Optional[str] = None
hostIp: Optional[str] = None
nodeName: Optional[str] = None
# ========== 新增:访问信息 ==========
externalIp: Optional[str] = Field(None, description="外网 IP 地址")
domain: Optional[str] = Field(None, description="域名")
domainUrl: Optional[str] = Field(None, description="域名访问地址")
accessUrl: Optional[str] = Field(None, description="推荐访问地址(域名优先)")
# 资源配置
servicePort: Optional[int] = None
namespace: str = "ai-agents"
cpu: Optional[str] = None
memory: Optional[str] = None
replicas: int = 1
# 运行信息
startTime: Optional[str] = None
runningSeconds: int = 0
endpoints: Optional[List[str]] = None
```
---
## 📄 接口文档更新
### GET /api/user/resources/agents 响应更新
```json
{
"success": true,
"data": {
"platformAgents": [
{
"name": "echo-agent-b00a7b8e-fa5a66",
"template": "echo_agent",
"templateName": "echo_agent",
"status": "Running",
"healthStatus": "healthy",
"podIp": "10.244.2.103",
"externalIp": "135.171.210.24",
"domain": "echo-agent-b00a7b8e-fa5a66.taijiagent.com",
"domainUrl": "http://echo-agent-b00a7b8e-fa5a66.taijiagent.com",
"accessUrl": "http://echo-agent-b00a7b8e-fa5a66.taijiagent.com",
"servicePort": 80,
"namespace": "agent-echo-agent-b00a7b8e-fa5a66",
"cpu": "100m",
"memory": "256Mi",
"replicas": 1,
"startTime": "2026-01-11T15:17:17.579421",
"runningSeconds": 227937
}
],
"customAgents": [
{
"name": "my-mysql-agent",
"template": "mysql_agent",
"templateName": "MCP",
"status": "Running",
"healthStatus": "healthy",
"podIp": "10.244.1.61",
"externalIp": "135.171.210.25",
"domain": "my-mysql-agent.taijiagent.com",
"domainUrl": "http://my-mysql-agent.taijiagent.com",
"accessUrl": "http://my-mysql-agent.taijiagent.com",
"servicePort": 80,
"namespace": "agent-my-mysql-agent",
"cpu": "500m",
"memory": "1Gi",
"replicas": 1,
"startTime": "2026-01-13T07:32:52.290231",
"runningSeconds": 83003
}
],
"summary": {
"totalPlatformAgents": 1,
"totalCustomAgents": 1
}
},
"message": "Agent 列表获取成功"
}
```
---
## 📝 改动文件清单
| 序号 | 文件路径 | 改动类型 | 改动说明 |
|:---:|---------|---------|---------|
| 1 | `services/mcp-server/models.py` | 修改 | AgentBillingRecord 添加访问信息字段 |
| 2 | `services/mcp-server/alembic/versions/xxx.py` | 新增 | 数据库迁移脚本 |
| 3 | `services/mcp-server/app/agent_manager_client.py` | 修改 | AgentStatusResult 添加 domain 等字段 |
| 4 | `services/mcp-server/app/routes/user.py` | 修改 | 创建 Agent 时保存 access_info |
| 5 | `services/mcp-server/app/routes/user.py` | 修改 | 查询 Agent 列表返回 domain 等字段 |
| 6 | `services/mcp-server/app/schemas.py` | 修改 | 添加/修改响应模型 |
| 7 | `Docs/用户资源信息查询接口文档.md` | 修改 | 更新接口文档 |
| 8 | `Docs/数据工具与自定义Agent-前端接口文档.md` | 修改 | 更新接口文档 |
---
## 🔄 实施步骤
### Step 1: 数据库改动(需要停机)
1. 备份数据库
2. 执行数据库迁移脚本
3. 验证迁移结果
### Step 2: 代码改动
1. 修改 `models.py`
2. 修改 `agent_manager_client.py`
3. 修改 `user.py` 中的创建 Agent 逻辑
4. 修改 `user.py` 中的查询 Agent 逻辑
5. 修改响应模型
### Step 3: 测试验证
1. 单元测试
2. 集成测试(创建 Agent → 查询列表 → 访问域名)
3. 前端联调
### Step 4: 文档更新
1. 更新 API 接口文档
2. 更新前端接口文档
---
## ⚠️ 注意事项
1. **向后兼容**:新增字段均为可选(nullable=True),不影响现有数据
2. **域名生效时间**:域名 DNS 解析可能有延迟(通常 1-5 分钟)
3. **访问优先级**:推荐使用 `accessUrl`(域名优先),Pod IP 会随重启变化
4. **安全考虑**:域名访问可能需要配置 HTTPS(后续考虑)
---
## 📊 预估工时
| 阶段 | 工时估算 |
|-----|---------|
| 数据库改动 | 0.5 天 |
| Agent Manager Client 改动 | 0.5 天 |
| 创建 Agent 代码改动 | 1 天 |
| 查询 Agent 列表改动 | 0.5 天 |
| 测试与联调 | 1 天 |
| 文档更新 | 0.5 天 |
| **总计** | **4 天** |
---
**文档编写**: AI Assistant
**最后更新**: 2026-01-14
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1156,6 +1156,282 @@ class AgentManagerClient:
count=data.get("count", 0)
)
# ==================== External Data Tool Management ====================
# 外部数据工具管理接口
# Agent Manager 负责生成和存储 Pydantic 工具文件,返回 tool_ref_id 给 MCP-Server
async def generate_external_tool(
self,
name: str,
description: str,
url: str,
method: str,
user_id: str,
tenant_id: Optional[str] = None,
headers: Optional[Dict[str, str]] = None,
auth: Optional[Dict[str, Any]] = None,
request_params: Optional[Dict[str, Any]] = None,
request_body: Optional[Dict[str, Any]] = None,
response_mapping: Optional[Dict[str, Any]] = None,
timeout: int = 30,
retry: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
生成外部数据工具
调用: POST /tools/generate
将工具配置发送给 Agent Manager,AM 会:
1. 根据配置生成 Pydantic 工具代码文件
2. 存储工具文件和配置
3. 返回 tool_ref_id(工具标识)
Args:
name: 工具名称
description: 工具描述
url: API 端点 URL
method: HTTP 方法(GET/POST/PUT/DELETE/PATCH)
user_id: 用户 ID
tenant_id: 租户 ID(可选)
headers: 自定义请求头
auth: 认证配置(type, key, in, name 等)
request_params: URL 查询参数定义(JSON Schema 格式)
request_body: 请求体定义(JSON Schema 格式)
response_mapping: 响应字段映射
timeout: 超时时间(秒)
retry: 重试配置
Returns:
{
"success": true,
"tool_ref_id": "tool-xxx-123",
"tool_name": "weather_query_tool",
"status": "active",
"message": "工具生成成功"
}
"""
payload: Dict[str, Any] = {
"name": name,
"description": description,
"url": url,
"method": method,
"user_id": user_id,
}
if tenant_id:
payload["tenant_id"] = tenant_id
if headers:
payload["headers"] = headers
if auth:
payload["auth"] = auth
if request_params:
payload["request_params"] = request_params
if request_body:
payload["request_body"] = request_body
if response_mapping:
payload["response_mapping"] = response_mapping
if timeout:
payload["timeout"] = timeout
if retry:
payload["retry"] = retry
logger.info(
"generating_external_tool",
name=name,
url=url,
method=method,
user_id=user_id
)
return await self._request("POST", "/tools/generate", json=payload)
async def update_external_tool(
self,
tool_ref_id: str,
name: str,
description: str,
url: str,
method: str,
user_id: str,
tenant_id: Optional[str] = None,
headers: Optional[Dict[str, str]] = None,
auth: Optional[Dict[str, Any]] = None,
request_params: Optional[Dict[str, Any]] = None,
request_body: Optional[Dict[str, Any]] = None,
response_mapping: Optional[Dict[str, Any]] = None,
timeout: int = 30,
retry: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
更新外部数据工具
调用: PUT /tools/{tool_ref_id}
Agent Manager 会重新生成工具文件,可能返回新的 tool_ref_id。
Args:
tool_ref_id: 原工具标识
其他参数同 generate_external_tool
Returns:
{
"success": true,
"tool_ref_id": "tool-xxx-123-v2",
"status": "active",
"message": "工具更新成功"
}
"""
payload: Dict[str, Any] = {
"name": name,
"description": description,
"url": url,
"method": method,
"user_id": user_id,
}
if tenant_id:
payload["tenant_id"] = tenant_id
if headers:
payload["headers"] = headers
if auth:
payload["auth"] = auth
if request_params:
payload["request_params"] = request_params
if request_body:
payload["request_body"] = request_body
if response_mapping:
payload["response_mapping"] = response_mapping
if timeout:
payload["timeout"] = timeout
if retry:
payload["retry"] = retry
logger.info(
"updating_external_tool",
tool_ref_id=tool_ref_id,
name=name,
user_id=user_id
)
return await self._request("PUT", f"/tools/{tool_ref_id}", json=payload)
async def delete_external_tool(self, tool_ref_id: str) -> Dict[str, Any]:
"""
删除外部数据工具
调用: DELETE /tools/{tool_ref_id}
Agent Manager 会删除对应的工具文件和配置。
Args:
tool_ref_id: 工具标识
Returns:
{
"success": true,
"message": "工具删除成功"
}
"""
logger.info("deleting_external_tool", tool_ref_id=tool_ref_id)
return await self._request("DELETE", f"/tools/{tool_ref_id}")
async def test_external_tool(
self,
tool_ref_id: str,
test_params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
测试外部数据工具连接
调用: POST /tools/{tool_ref_id}/test
Agent Manager 会尝试调用工具的 API 并返回测试结果。
Args:
tool_ref_id: 工具标识
test_params: 测试参数(可选)
Returns:
{
"success": true,
"connected": true,
"response_time_ms": 156,
"status_code": 200,
"sample_response": {...}
}
"""
payload = {}
if test_params:
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)
async def create_agent_with_tools(
self,
name: str,
template: str,
tool_refs: List[str],
config: Optional[AgentConfig] = None,
env: Optional[Dict[str, str]] = None
) -> AgentCreateResult:
"""
创建带有外部数据工具的 Agent
调用: POST /agents(新增 tool_refs 字段)
Agent Manager 会根据 tool_refs 加载对应的工具文件,部署到 AKS。
Args:
name: Agent 名称
template: Agent 模板(通常为 "custom_agent")
tool_refs: 外部数据工具标识列表
config: 资源配置
env: 环境变量
Returns:
创建结果
"""
payload: Dict[str, Any] = {
"name": name,
"template": template,
"tool_refs": tool_refs
}
if config:
payload["config"] = config.to_dict()
# 构建环境变量
final_env = {"LLM_BASE_URL": LLM_BASE_URL}
if env:
final_env.update(env)
payload["env"] = final_env
logger.info(
"creating_agent_with_tools",
name=name,
template=template,
tool_refs=tool_refs,
config=config.to_dict() if config else None
)
data = await self._request("POST", "/agents", json=payload)
return AgentCreateResult(
name=data["name"],
namespace=data["namespace"],
status=data["status"],
created_at=data["created_at"],
template=data["template"],
service_port=data.get("service_port"),
access_info=data.get("access_info"),
pod_id=data.get("pod_id"),
pod_ip=data.get("pod_ip"),
host_ip=data.get("host_ip"),
node_name=data.get("node_name"),
owner_info=data.get("owner_info")
)
# ==================== Unimplemented Interfaces (Not yet provided by Agent Manager) ====================
# The interfaces called by the following methods are not yet implemented in Agent Manager
# Method signatures are retained for future extension, but will raise NotImplementedError when called
@@ -11,6 +11,7 @@ from . import (
provider_health_management, # 阶段四:供应商健康检查管理
platform_agent_quota, # 平台 Agent 配额管理
billing_webhook, # LiteLLM Token计费webhook
external_tools, # 外部数据工具管理
)
@@ -35,6 +36,8 @@ def register_routes(app: FastAPI) -> None:
agents.router,
sessions.router, # 会话管理路由
tools.router,
external_tools.router, # 外部数据工具管理路由
external_tools.toolkit_router, # 外部数据工具集管理路由
monitoring.router,
metrics.router,
websocket.router,
File diff suppressed because it is too large Load Diff
+99
View File
@@ -3144,6 +3144,105 @@ async def create_custom_agent(
logger.warning(f"查询工具详情失败,仅传递工具 ID 列表: {str(e)}")
# ================================================
# ========== 外部数据工具配置传递给 Agent Manager ==========
from models import ExternalDataTool, ExternalToolkit
external_tool_refs = [] # 存储 tool_ref_id 列表
external_tool_ids_to_process = [] # 需要处理的工具 ID 列表
# 优先处理工具集(如果指定了 toolkit)
if req.toolkit:
logger.info(f"处理工具集: user_id={user_id}, toolkit={req.toolkit}")
try:
toolkit_result = await db.execute(
select(ExternalToolkit).where(
ExternalToolkit.id == PyUUID(req.toolkit),
ExternalToolkit.owner_id == PyUUID(user_id)
)
)
toolkit = toolkit_result.scalar_one_or_none()
if not toolkit:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"工具集不存在或无权限: {req.toolkit}"
)
# 获取工具集中的工具 ID
if toolkit.tool_ids:
external_tool_ids_to_process.extend(toolkit.tool_ids)
logger.info(f"从工具集获取工具: toolkit={toolkit.name}, tools={toolkit.tool_ids}")
# 更新工具集使用次数
toolkit.usage_count = (toolkit.usage_count or 0) + 1
except HTTPException:
raise
except Exception as e:
logger.warning(f"查询工具集失败: {req.toolkit}, error={str(e)}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"无效的工具集 ID: {req.toolkit}"
)
# 合并直接指定的外部工具(如果同时指定了 externalTools)
if req.externalTools and len(req.externalTools) > 0:
for ext_id in req.externalTools:
if ext_id not in external_tool_ids_to_process:
external_tool_ids_to_process.append(ext_id)
# 处理所有需要使用的外部数据工具
if external_tool_ids_to_process:
logger.info(f"处理外部数据工具: user_id={user_id}, tools={external_tool_ids_to_process}")
for ext_tool_id in external_tool_ids_to_process:
try:
ext_tool_result = await db.execute(
select(ExternalDataTool).where(
ExternalDataTool.id == PyUUID(ext_tool_id),
ExternalDataTool.owner_id == PyUUID(user_id)
)
)
ext_tool = ext_tool_result.scalar_one_or_none()
if not ext_tool:
logger.warning(f"外部数据工具不存在或无权限: {ext_tool_id}")
continue
if ext_tool.status != "active":
logger.warning(f"外部数据工具未就绪: {ext_tool_id}, status={ext_tool.status}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"外部数据工具 '{ext_tool.name}' 尚未就绪,状态: {ext_tool.status}"
)
if not ext_tool.tool_ref_id:
logger.warning(f"外部数据工具缺少 tool_ref_id: {ext_tool_id}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"外部数据工具 '{ext_tool.name}' 缺少关联标识,请重新创建"
)
external_tool_refs.append(ext_tool.tool_ref_id)
# 更新工具使用次数
ext_tool.usage_count = (ext_tool.usage_count or 0) + 1
except HTTPException:
raise
except Exception as e:
logger.warning(f"查询外部数据工具失败: {ext_tool_id}, error={str(e)}")
if external_tool_refs:
# 传递外部工具的 tool_ref_id 列表给 Agent Manager
env_vars["EXTERNAL_TOOL_REFS"] = json.dumps(external_tool_refs)
logger.info(
f"外部数据工具配置已准备: user_id={user_id}, "
f"tool_count={len(external_tool_refs)}, "
f"tool_refs={external_tool_refs}"
)
# ================================================
# ========== A2A 框架配置 ==========
if framework_template == "A2A":
import time as time_module
+185
View File
@@ -837,6 +837,12 @@ class CreateCustomAgentRequest(BaseModel):
# 工具配置(第一个工具为主工具,可决定 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 密钥")
@@ -962,3 +968,182 @@ class AgentManagerCallbackResponse(BaseModel):
message: str
recordId: Optional[str] = None
# ============= 外部数据工具 =============
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)")
username: Optional[str] = Field(None, description="Basic Auth 用户名")
password: Optional[str] = Field(None, description="Basic Auth 密码")
class Config:
populate_by_name = True
class ExternalToolRetryConfig(BaseModel):
"""外部工具重试配置"""
max_attempts: int = Field(3, ge=1, le=10, description="最大重试次数")
delay_seconds: int = Field(1, ge=0, le=60, description="重试延迟(秒)")
class CreateExternalToolRequest(BaseModel):
"""创建外部数据工具请求
用户上传的外部数据工具配置,将发送给 Agent Manager 生成 Pydantic 工具文件。
"""
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="请求体定义")
# 响应配置
response_mapping: Optional[Dict[str, Any]] = Field(None, description="响应字段映射")
# 运行配置
timeout: int = Field(30, 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="工具名称")
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="认证配置")
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="重试配置")
class ExternalToolInfo(BaseModel):
"""外部数据工具信息(列表响应)"""
id: str
name: str
description: Optional[str] = None
url: str
method: str
auth_type: str
status: str # pending, active, error
usage_count: int = 0
created_at: str
updated_at: Optional[str] = None
class ExternalToolDetail(BaseModel):
"""外部数据工具详情"""
id: str
name: str
description: Optional[str] = None
url: str
method: str
auth_type: str
tool_ref_id: Optional[str] = None
status: str
error_message: Optional[str] = None
usage_count: int = 0
created_at: str
updated_at: Optional[str] = None
class TestExternalToolRequest(BaseModel):
"""测试外部工具连接请求"""
test_params: Optional[Dict[str, Any]] = Field(None, description="测试参数")
class TestExternalToolResponse(BaseModel):
"""测试外部工具连接响应"""
connected: bool
response_time_ms: Optional[int] = None
status_code: Optional[int] = None
sample_response: Optional[Dict[str, Any]] = None
error_message: Optional[str] = None
class CreateCustomAgentWithToolsRequest(BaseModel):
"""创建带有外部数据工具的自定义 Agent 请求
新版自定义 Agent 创建接口,使用外部数据工具(通过 tool_ref_id 关联)。
"""
name: str = Field(..., min_length=1, max_length=63, description="Agent 名称")
description: Optional[str] = Field(None, description="描述")
# 外部数据工具(核心字段)
external_tools: List[str] = Field(..., min_length=1, description="外部数据工具 ID 列表")
# 资源配置
cpuRequest: str = Field("100m", description="CPU 请求量")
cpuLimit: Optional[str] = Field(None, description="CPU 限制量")
memoryRequest: str = Field("128Mi", description="内存请求量")
memoryLimit: Optional[str] = Field(None, description="内存限制量")
# 模型配置
model: Optional[str] = Field(None, description="使用的模型名称")
# ==================== 外部数据工具集 ====================
class CreateToolkitRequest(BaseModel):
"""创建工具集请求"""
name: str = Field(..., min_length=1, max_length=100, description="工具集名称")
description: Optional[str] = Field(None, description="工具集描述")
tool_ids: List[str] = Field(
...,
min_length=1,
max_length=8,
description="外部数据工具 ID 列表(1-8 个)"
)
class UpdateToolkitRequest(BaseModel):
"""更新工具集请求"""
name: Optional[str] = Field(None, min_length=1, max_length=100, description="工具集名称")
description: Optional[str] = Field(None, description="工具集描述")
tool_ids: Optional[List[str]] = Field(
None,
min_length=1,
max_length=8,
description="外部数据工具 ID 列表(1-8 个)"
)
class ToolkitInfo(BaseModel):
"""工具集基本信息"""
id: str
name: str
description: Optional[str] = None
tool_count: int
usage_count: int = 0
created_at: str
class ToolkitDetail(BaseModel):
"""工具集详情"""
id: str
name: str
description: Optional[str] = None
tool_ids: List[str]
tools: List[ExternalToolInfo] = [] # 包含的工具信息
usage_count: int = 0
created_at: str
updated_at: Optional[str] = None
@@ -0,0 +1,45 @@
-- 017: 添加外部数据工具表
-- 用于存储用户创建的外部数据工具基本信息和 Agent Manager 关联标识
-- 创建外部数据工具表
CREATE TABLE IF NOT EXISTS external_data_tools (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- 基本信息(用于前端展示)
name VARCHAR(100) NOT NULL,
description TEXT,
-- API 配置(仅用于展示,实际配置存储在 Agent Manager)
url VARCHAR(500) NOT NULL,
method VARCHAR(10) NOT NULL DEFAULT 'POST',
auth_type VARCHAR(20) DEFAULT 'none', -- api_key, bearer, basic, none
-- Agent Manager 关联(核心字段)
tool_ref_id VARCHAR(100) UNIQUE, -- AM 返回的工具标识
status VARCHAR(20) DEFAULT 'pending', -- pending, active, error
error_message TEXT, -- 生成失败时的错误信息
-- 归属信息
owner_id UUID NOT NULL REFERENCES users(id),
tenant_id UUID REFERENCES users(id),
channel_id UUID REFERENCES channels(id),
is_active BOOLEAN DEFAULT TRUE,
-- 统计信息
usage_count INTEGER DEFAULT 0,
-- 时间戳
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_external_data_tool_owner ON external_data_tools(owner_id);
CREATE INDEX IF NOT EXISTS idx_external_data_tool_ref ON external_data_tools(tool_ref_id);
CREATE INDEX IF NOT EXISTS idx_external_data_tool_status ON external_data_tools(status);
-- 添加注释
COMMENT ON TABLE external_data_tools IS '外部数据工具表 - 存储用户创建的外部 API 工具配置';
COMMENT ON COLUMN external_data_tools.tool_ref_id IS 'Agent Manager 返回的工具标识,部署 Agent 时传递此标识';
COMMENT ON COLUMN external_data_tools.status IS '工具状态:pending(等待生成), active(可用), error(生成失败)';
@@ -0,0 +1,39 @@
-- 018: 添加外部数据工具集表
-- 用户可以将多个外部数据工具组合成一个工具集,方便部署自定义 Agent
-- 创建外部数据工具集表
CREATE TABLE IF NOT EXISTS external_toolkits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- 基本信息
name VARCHAR(100) NOT NULL,
description TEXT,
-- 工具列表(存储工具 ID 的 JSON 数组,最多 8 个)
tool_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
-- 归属信息
owner_id UUID NOT NULL REFERENCES users(id),
tenant_id UUID REFERENCES users(id),
channel_id UUID REFERENCES channels(id),
is_active BOOLEAN DEFAULT TRUE,
-- 统计信息
usage_count INTEGER DEFAULT 0,
-- 时间戳
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- 约束:同一用户下工具集名称唯一
CONSTRAINT uq_toolkit_name_owner UNIQUE (name, owner_id)
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_external_toolkit_owner ON external_toolkits(owner_id);
-- 添加注释
COMMENT ON TABLE external_toolkits IS '外部数据工具集表 - 存储用户创建的工具组合';
COMMENT ON COLUMN external_toolkits.tool_ids IS '工具 ID 列表,JSON 数组格式,最多 8 个工具';
COMMENT ON COLUMN external_toolkits.usage_count IS '工具集被 Agent 使用次数';
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""
运行 017 迁移:添加外部数据工具表
"""
import asyncio
import os
import sys
from pathlib import Path
# 添加项目根目录到 Python 路径
sys.path.insert(0, str(Path(__file__).parent.parent))
import asyncpg
async def run_migration():
"""执行迁移"""
# 获取数据库连接信息
database_url = os.getenv(
"DATABASE_URL",
"postgresql://postgres:postgres@localhost:5432/mcp_server"
)
# 解析连接字符串
if database_url.startswith("postgresql://"):
# 转换为 asyncpg 格式
database_url = database_url.replace("postgresql://", "postgres://")
print(f"连接数据库: {database_url.split('@')[1] if '@' in database_url else database_url}")
try:
conn = await asyncpg.connect(database_url)
print("数据库连接成功")
# 读取 SQL 文件
sql_file = Path(__file__).parent / "017_add_external_data_tools.sql"
with open(sql_file, "r", encoding="utf-8") as f:
sql = f.read()
print("执行迁移 SQL...")
await conn.execute(sql)
print("迁移执行成功!")
# 验证表是否创建
result = await conn.fetchval(
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'external_data_tools'"
)
if result > 0:
print("✅ external_data_tools 表已创建")
else:
print("❌ external_data_tools 表创建失败")
await conn.close()
except Exception as e:
print(f"迁移失败: {e}")
raise
if __name__ == "__main__":
asyncio.run(run_migration())
+82
View File
@@ -1360,3 +1360,85 @@ class TenantModelKey(BaseModel, Base):
Index("idx_tenant_model_key_status", status),
UniqueConstraint("tenant_id", "model_name", name="uq_tenant_model"),
)
class ExternalDataTool(BaseModel, Base):
"""外部数据工具模型
用户创建的外部数据工具,通过 Agent Manager 生成 Pydantic 工具文件。
MCP-Server 只存储工具的基本信息和 Agent Manager 返回的标识,
工具的实际代码文件由 Agent Manager 管理。
核心流程:
1. 用户上传工具配置(表单或 JSON 文件)
2. MCP-Server 发送配置给 Agent Manager 生成 Pydantic 工具文件
3. Agent Manager 返回 tool_ref_id(工具标识)
4. MCP-Server 存储基本信息 + tool_ref_id
5. 部署 Agent 时,传递 tool_ref_id 列表给 Agent Manager
"""
__tablename__ = "external_data_tools"
# 基本信息(用于前端展示)
name = Column(String(100), nullable=False)
description = Column(Text)
# API 配置(仅用于展示,实际配置存储在 Agent Manager)
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 Manager 关联(核心字段)
tool_ref_id = Column(String(100), unique=True) # AM 返回的工具标识,部署时传给 AM
status = Column(String(20), default="pending") # pending, active, error
error_message = Column(Text) # 生成失败时的错误信息
# 归属信息
owner_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
tenant_id = Column(GUID(), ForeignKey("users.id")) # 租户 ID(可选)
channel_id = Column(GUID(), ForeignKey("channels.id")) # 渠道 ID(可选)
is_active = Column(Boolean, default=True)
# 统计信息
usage_count = Column(Integer, default=0) # 被 Agent 使用次数
# 关联关系
owner = relationship("User", foreign_keys=[owner_id])
__table_args__ = (
Index("idx_external_data_tool_owner", owner_id),
Index("idx_external_data_tool_ref", tool_ref_id),
Index("idx_external_data_tool_status", status),
)
class ExternalToolkit(BaseModel, Base):
"""外部数据工具集模型
用户可以将多个外部数据工具组合成一个工具集,方便部署自定义 Agent。
每个工具集最多包含 8 个工具。
"""
__tablename__ = "external_toolkits"
# 基本信息
name = Column(String(100), nullable=False)
description = Column(Text)
# 工具列表(存储工具 ID 的 JSON 数组,最多 8 个)
tool_ids = Column(JSON, nullable=False, default=list)
# 归属信息
owner_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
tenant_id = Column(GUID(), ForeignKey("users.id"))
channel_id = Column(GUID(), ForeignKey("channels.id"))
is_active = Column(Boolean, default=True)
# 统计信息
usage_count = Column(Integer, default=0) # 被 Agent 使用次数
# 关联关系
owner = relationship("User", foreign_keys=[owner_id])
__table_args__ = (
Index("idx_external_toolkit_owner", owner_id),
UniqueConstraint("name", owner_id, name="uq_toolkit_name_owner"), # 同一用户下工具集名称唯一
)
-43
View File
@@ -1,43 +0,0 @@
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 安装系统依赖与 Node.js (prisma需要)
RUN apt-get update && apt-get install -y \
curl \
nodejs \
npm && \
rm -rf /var/lib/apt/lists/*
# 安装LiteLLM和prisma (使用国内源)
# 注意:Prisma Python 客户端 0.12.0 需要 Prisma CLI 5.8.0
# 需要同时安装 Prisma CLI 和 Python 客户端
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple \
litellm[proxy]==1.17.0 \
redis==5.0.1 \
prometheus-client==0.19.0 \
prisma==0.12.0 && \
(rm -f /usr/local/bin/prisma || true) && \
npm install -g prisma@5.8.0
# 复制配置文件
COPY config/ ./config/
# 创建logs目录
RUN mkdir -p logs
# 设置环境变量
ENV LITELLM_MASTER_KEY=sk-taiji-master-key
ENV LITELLM_CONFIG_PATH=/app/config/litellm.yaml
# 暴露端口
EXPOSE 4000
# 健康检查(使用 API key,增加超时时间因为health端点需要检查所有模型)
HEALTHCHECK --interval=60s --timeout=30s --start-period=30s --retries=3 \
CMD curl -f -H "Authorization: Bearer sk-taiji-master-key" --max-time 25 http://localhost:4000/health || exit 1
# 启动LiteLLM代理
CMD ["litellm", "--config", "/app/config/litellm_simple.yaml", "--port", "4000", "--host", "0.0.0.0"]
-379
View File
@@ -1,379 +0,0 @@
# LiteLLM 网关配置
# taiji-AI-PAD 模型治理层配置
# 基础设置
general_settings:
master_key: "os.environ/LITELLM_MASTER_KEY"
database_url: "os.environ/DATABASE_URL"
# 日志设置
set_verbose: true
json_logs: true
log_raw_request_response: false # 生产环境设为false
# 缓存设置
redis_host: "redis"
redis_port: 6379
redis_password: null
# 速率限制
max_budget: 1000.0 # 美元
budget_duration: "30d"
# ✅ 回调和监控 - 添加webhook用于Token计费
success_callback: ["langfuse", "webhook"]
failure_callback: ["langfuse", "webhook"]
# Webhook配置 - 指向mcp-server的计费webhook端点
webhook_url: "http://mcp-server:8002/api/v1/billing/litellm-callback"
webhook_headers:
Content-Type: "application/json"
# 安全设置
allowed_ips: ["127.0.0.1", "172.20.0.0/16"] # Docker网络
# 模型配置
model_list:
# OpenAI 模型组
- model_name: "gpt-3.5-turbo"
litellm_params:
model: "openai/gpt-3.5-turbo"
api_key: "os.environ/OPENAI_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
max_input_tokens: 16385
max_output_tokens: 4096
input_cost_per_token: 0.0000015
output_cost_per_token: 0.000002
- model_name: "gpt-4"
litellm_params:
model: "openai/gpt-4"
api_key: "os.environ/OPENAI_API_KEY"
max_tokens: 8000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
max_input_tokens: 8192
max_output_tokens: 8192
input_cost_per_token: 0.00003
output_cost_per_token: 0.00006
- model_name: "gpt-4-turbo"
litellm_params:
model: "openai/gpt-4-turbo-preview"
api_key: "os.environ/OPENAI_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 128000
max_output_tokens: 4096
input_cost_per_token: 0.00001
output_cost_per_token: 0.00003
# Anthropic 模型组
- model_name: "claude-3-haiku"
litellm_params:
model: "anthropic/claude-3-haiku-20240307"
api_key: "os.environ/ANTHROPIC_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 200000
max_output_tokens: 4096
input_cost_per_token: 0.00000025
output_cost_per_token: 0.00000125
- model_name: "claude-3-sonnet"
litellm_params:
model: "anthropic/claude-3-sonnet-20240229"
api_key: "os.environ/ANTHROPIC_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 200000
max_output_tokens: 4096
input_cost_per_token: 0.000003
output_cost_per_token: 0.000015
- model_name: "claude-3-opus"
litellm_params:
model: "anthropic/claude-3-opus-20240229"
api_key: "os.environ/ANTHROPIC_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 200000
max_output_tokens: 4096
input_cost_per_token: 0.000015
output_cost_per_token: 0.000075
# 本地/开源模型(如果可用)
- model_name: "llama-3-8b"
litellm_params:
model: "ollama/llama3"
api_base: "http://ollama:11434"
max_tokens: 2000
model_info:
mode: "chat"
supports_function_calling: false
supports_vision: false
max_input_tokens: 8192
max_output_tokens: 2048
input_cost_per_token: 0.0 # 本地模型无成本
output_cost_per_token: 0.0
# OpenRouter 模型组 - 通过 OpenRouter 访问多种模型
# 注意: 使用 openrouter/ 前缀时,LiteLLM 会自动使用 OpenRouter API
- model_name: "openrouter-gpt-4"
litellm_params:
model: "openrouter/openai/gpt-4"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 8000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
max_input_tokens: 8192
max_output_tokens: 8192
input_cost_per_token: 0.00003
output_cost_per_token: 0.00006
- model_name: "openrouter-gpt-3.5-turbo"
litellm_params:
model: "openrouter/openai/gpt-3.5-turbo"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
max_input_tokens: 16385
max_output_tokens: 4096
input_cost_per_token: 0.0000015
output_cost_per_token: 0.000002
- model_name: "openrouter-claude-3.5-sonnet"
litellm_params:
model: "openrouter/anthropic/claude-3.5-sonnet"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 200000
max_output_tokens: 4096
input_cost_per_token: 0.000003
output_cost_per_token: 0.000015
- model_name: "openrouter-claude-3-opus"
litellm_params:
model: "openrouter/anthropic/claude-3-opus"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
max_input_tokens: 200000
max_output_tokens: 4096
input_cost_per_token: 0.000015
output_cost_per_token: 0.000075
# 路由器配置
router_settings:
routing_strategy: "least-busy" # 路由策略: least-busy, round-robin, latency-based
allowed_fails: 3
cooldown_time: 30
retry_after: 10
# 模型组定义
model_group_configs:
- group_name: "gpt-3.5-group"
models:
- model_name: "gpt-3.5-turbo"
weight: 1.0
- group_name: "gpt-4-group"
models:
- model_name: "gpt-4"
weight: 0.7
- model_name: "gpt-4-turbo"
weight: 0.3
- group_name: "claude-group"
models:
- model_name: "claude-3-haiku"
weight: 0.5
- model_name: "claude-3-sonnet"
weight: 0.3
- model_name: "claude-3-opus"
weight: 0.2
- group_name: "fast-models"
models:
- model_name: "gpt-3.5-turbo"
weight: 0.4
- model_name: "claude-3-haiku"
weight: 0.4
- model_name: "llama-3-8b"
weight: 0.2
- group_name: "premium-models"
models:
- model_name: "gpt-4-turbo"
weight: 0.4
- model_name: "claude-3-opus"
weight: 0.3
- model_name: "claude-3-sonnet"
weight: 0.3
- group_name: "openrouter-group"
models:
- model_name: "openrouter-gpt-4"
weight: 0.3
- model_name: "openrouter-gpt-3.5-turbo"
weight: 0.3
- model_name: "openrouter-claude-3.5-sonnet"
weight: 0.25
- model_name: "openrouter-claude-3-opus"
weight: 0.15
# 用户和权限配置
litellm_settings:
# API密钥管理
api_keys:
- key: "sk-taiji-mcp-server"
models: ["gpt-3.5-turbo", "gpt-4", "claude-3-haiku", "claude-3-sonnet", "openrouter-gpt-4", "openrouter-gpt-3.5-turbo", "openrouter-claude-3.5-sonnet"]
max_budget: 100.0
budget_duration: "1d"
metadata:
user_id: "mcp-server"
service: "mcp-server"
- key: "sk-taiji-data-ingestion"
models: ["gpt-3.5-turbo", "claude-3-haiku", "llama-3-8b", "openrouter-gpt-3.5-turbo", "openrouter-claude-3.5-sonnet"]
max_budget: 50.0
budget_duration: "1d"
metadata:
user_id: "data-ingestion"
service: "data-ingestion"
- key: "sk-taiji-agent-dev"
models: ["gpt-3.5-group", "claude-group", "fast-models", "openrouter-group"]
max_budget: 20.0
budget_duration: "1d"
metadata:
user_id: "agent-development"
service: "agent-development"
- key: "sk-taiji-premium"
models: ["premium-models", "gpt-4-group", "openrouter-group", "openrouter-gpt-4", "openrouter-claude-3-opus", "openrouter-claude-3.5-sonnet"]
max_budget: 200.0
budget_duration: "1d"
metadata:
user_id: "premium-user"
service: "premium"
# 回调配置
callbacks:
# 成功回调
success_callback:
- callback_name: "langfuse"
callback_type: "success"
callback_vars:
langfuse_public_key: "os.environ/LANGFUSE_PUBLIC_KEY"
langfuse_secret_key: "os.environ/LANGFUSE_SECRET_KEY"
langfuse_host: "os.environ/LANGFUSE_HOST"
# 失败回调
failure_callback:
- callback_name: "langfuse"
callback_type: "failure"
callback_vars:
langfuse_public_key: "os.environ/LANGFUSE_PUBLIC_KEY"
langfuse_secret_key: "os.environ/LANGFUSE_SECRET_KEY"
langfuse_host: "os.environ/LANGFUSE_HOST"
# 监控和指标
monitoring:
prometheus_port: 4001
health_check_interval: 30
# 自定义指标
custom_metrics:
- name: "taiji_model_requests_total"
type: "counter"
description: "Total model requests"
labels: ["model", "user_id", "status"]
- name: "taiji_model_latency"
type: "histogram"
description: "Model response latency"
labels: ["model", "user_id"]
- name: "taiji_model_cost"
type: "gauge"
description: "Model cost tracking"
labels: ["model", "user_id"]
# 错误处理
error_handling:
# 重试配置
retry_policy:
max_retries: 3
retry_delay: 1.0
exponential_backoff: true
# 超时设置
timeout:
request_timeout: 60
# 回退策略
fallback:
enabled: true
fallback_models:
"gpt-4": ["openrouter-gpt-4", "gpt-4-turbo", "claude-3-sonnet", "openrouter-claude-3-sonnet"]
"claude-3-opus": ["openrouter-claude-3-opus", "claude-3-sonnet", "openrouter-claude-3-sonnet", "gpt-4", "openrouter-gpt-4"]
"gpt-3.5-turbo": ["openrouter-gpt-3.5-turbo", "claude-3-haiku", "llama-3-8b"]
"openrouter-gpt-4": ["gpt-4", "gpt-4-turbo", "openrouter-claude-3.5-sonnet"]
"openrouter-gpt-3.5-turbo": ["gpt-3.5-turbo", "claude-3-haiku"]
"openrouter-claude-3.5-sonnet": ["claude-3-sonnet", "gpt-4", "openrouter-gpt-4"]
"openrouter-claude-3-opus": ["claude-3-opus", "claude-3-sonnet", "openrouter-claude-3.5-sonnet"]
# 日志配置
logging:
level: "INFO"
format: "json"
# 请求日志
log_requests: true
log_responses: false # 生产环境关闭
# 敏感信息过滤
redact_messages_in_logs: true
redact_user_api_key_info: true
@@ -1,107 +0,0 @@
# LiteLLM 简化配置 - 用于测试和开发
# taiji-AI-PAD 模型治理层配置
# 基础设置
general_settings:
master_key: "sk-taiji-master-key"
# 日志设置
set_verbose: true
json_logs: false
log_raw_request_response: false
# 安全设置
allowed_ips: ["127.0.0.1", "172.20.0.0/16", "0.0.0.0/0"] # 允许所有IP用于测试
# 模型配置 - 使用测试模型和 OpenRouter
model_list:
# 测试模型 - 使用 OpenRouter 的 Qwen 模型
- model_name: "test-model"
litellm_params:
model: "openrouter/qwen/qwen-2-7b-instruct"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 500
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: false
supports_vision: false
# OpenRouter 模型 - 通过 OpenRouter 访问
# 注意: 使用 openrouter/ 前缀时,LiteLLM 会自动使用 OpenRouter API
- model_name: "openrouter-gpt-4o-mini"
litellm_params:
model: "openrouter/openai/gpt-4o-mini"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 1000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
- model_name: "openrouter-gpt-3.5-turbo"
litellm_params:
model: "openrouter/openai/gpt-3.5-turbo"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 1000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
# gpt-3.5-turbo 别名 - 向后兼容
- model_name: "gpt-3.5-turbo"
litellm_params:
model: "openrouter/openai/gpt-3.5-turbo"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: false
- model_name: "openrouter-claude-3.5-sonnet"
litellm_params:
model: "openrouter/anthropic/claude-3.5-sonnet"
api_key: "os.environ/OPENROUTER_API_KEY"
max_tokens: 4000
temperature: 0.7
model_info:
mode: "chat"
supports_function_calling: true
supports_vision: true
# 路由器配置
router_settings:
routing_strategy: "round-robin"
allowed_fails: 1
cooldown_time: 10
# 用户配置
litellm_settings:
api_keys:
- key: "sk-test-key"
models: ["test-model", "openrouter-gpt-4o-mini", "openrouter-gpt-3.5-turbo", "openrouter-claude-3.5-sonnet", "gpt-3.5-turbo"]
metadata:
user_id: "test-user"
service: "testing"
- key: "sk-taiji-master-key"
models: ["test-model", "openrouter-gpt-4o-mini", "openrouter-gpt-3.5-turbo", "openrouter-claude-3.5-sonnet", "gpt-3.5-turbo"]
metadata:
user_id: "master"
service: "all"
# 监控
monitoring:
health_check_interval: 30
# 日志配置
logging:
level: "INFO"
format: "text"
log_requests: true
log_responses: false