更新api文档

This commit is contained in:
Ubuntu
2025-12-31 10:56:00 +00:00
parent fb1f5a7b28
commit 56c390077e
18 changed files with 4470 additions and 5309 deletions
@@ -25,430 +25,284 @@
## 1. 健康检查
**GET** `/health`
**功能**: 检查服务健康状态
检查服务健康状态。
**请求方式**: `GET /health`
**请求示例**:
```bash
curl -X GET "http://localhost:8001/health"
```
**参数**: 无
**响应示例**:
```json
{
"status": "healthy",
"timestamp": "2025-12-25T05:04:23.211960",
"services": {
"data_ingestion": "healthy",
"redis": "healthy",
"nats": "healthy",
"rapidapi": "healthy",
"apillama": "healthy"
},
"stats": {
"total_apis": 0,
"processed_apis": 0,
"generated_tools": 20,
"cache_size": 44
}
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| status | string | 服务状态 |
| timestamp | string | 时间戳 |
| services | object | 各子服务状态 |
| stats | object | 统计信息 |
---
## 2. 同步 RapidAPI 端点
**POST** `/rapidapi/sync`
**功能**: 同步 RapidAPI 端点列表
同步 RapidAPI 端点列表(后台任务)。
**请求方式**: `POST /rapidapi/sync`
**查询参数**:
- `category` (string, 可选): API 分类
- `limit` (int, 可选, 默认: 100): 同步数量限制
**请求示例**:
```bash
curl -X POST "http://localhost:8001/rapidapi/sync?category=weather&limit=50"
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| category | string | 否 | API 分类 |
| limit | int | 否 | 同步数量限制,默认100 |
**响应示例**:
```json
{
"message": "RapidAPI端点同步已启动",
"category": "weather",
"limit": 50
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| message | string | 操作结果消息 |
| category | string | 同步的分类 |
| limit | int | 同步数量限制 |
---
## 3. 测试 RapidAPI 端点
**POST** `/rapidapi/test`
**功能**: 测试 RapidAPI 端点调用
测试 RapidAPI 端点调用。
**请求方式**: `POST /rapidapi/test`
**请求体**:
```json
{
"endpoint": "https://rapidapi.com/api/weather/v1/current",
"method": "GET",
"params": {
"location": "Beijing"
},
"headers": {
"X-Custom-Header": "value"
}
}
```
**请求体参数**:
**响应示例**:
```json
{
"success": true,
"status_code": 200,
"data": {
"temperature": 25,
"condition": "sunny"
},
"response_time": 123.45,
"headers": {
"content-type": "application/json"
}
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| endpoint | string | 是 | API 端点 URL |
| method | string | 是 | HTTP 方法 |
| params | object | 否 | 请求参数 |
| headers | object | 否 | 自定义请求头 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| status_code | int | HTTP 状态码 |
| data | object | 响应数据 |
| response_time | float | 响应时间(ms) |
| headers | object | 响应头 |
---
## 4. 解析 OpenAPI 规范
**POST** `/openapi/parse`
**功能**: 解析 OpenAPI/Swagger 规范文档
解析 OpenAPI/Swagger 规范文档,并后台生成工具。
**请求方式**: `POST /openapi/parse`
**查询参数**:
- `url` (string, 必需): OpenAPI 文档 URL
**请求示例**:
```bash
curl -X POST "http://localhost:8001/openapi/parse?url=https://api.example.com/openapi.json"
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| url | string | 是 | OpenAPI 文档 URL |
**响应示例**:
```json
{
"url": "https://api.example.com/openapi.json",
"title": "Example API",
"version": "1.0.0",
"endpoints_count": 15,
"schemas_count": 8,
"parsed_data": {
"version": "3.0.0",
"info": {
"title": "Example API",
"version": "1.0.0"
},
"paths": {},
"parsed": true
},
"parsing_time": 0.234
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| url | string | 文档 URL |
| title | string | API 标题 |
| version | string | API 版本 |
| endpoints_count | int | 端点数量 |
| schemas_count | int | Schema 数量 |
| parsed_data | object | 解析后的数据 |
| parsing_time | float | 解析耗时(秒) |
---
## 5. APILLAMA 处理 API 文档
**POST** `/apillama/process`
**功能**: 处理 API 文档,生成结构化 Schema
使用 APILLAMA 处理 API 文档,生成结构化 Schema。
**请求方式**: `POST /apillama/process`
> **说明**: APILLAMA 使用 OpenRouter API 调用 Llama 3.1 8B Instruct 模型进行文档处理。如果 OpenRouter 不可用或账户余额不足,系统会自动使用 fallback 处理逻辑。
**请求体参数**:
**请求体**:
```json
{
"api_doc": {
"title": "Weather API",
"description": "Get weather information",
"parameters": [
{
"name": "location",
"type": "string",
"description": "City name",
"required": true
}
]
},
"context": {
"service": "Weather service"
},
"output_format": "json_schema",
"include_examples": true,
"enhance_descriptions": true,
"validate_schema": true
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| api_doc | string/object | 是 | API 文档内容 |
| context | object | 否 | 上下文信息 |
| output_format | string | 否 | 输出格式: `pydantic`, `json_schema`, `openapi` |
| include_examples | boolean | 否 | 是否生成示例 |
| enhance_descriptions | boolean | 否 | 是否增强描述 |
| validate_schema | boolean | 否 | 是否校验 Schema |
**请求参数说明**:
- `api_doc` (string | object, 必需): API 文档
- `context` (object, 可选): 上下文信息
- `output_format` (string, 可选): 输出格式 (`pydantic`, `json_schema`, `openapi`)
- `include_examples` (bool, 可选): 是否生成示例
- `enhance_descriptions` (bool, 可选): 是否增强描述
- `validate_schema` (bool, 可选): 是否校验 Schema
**响应字段**:
**环境配置**:
- `OPENROUTER_API_KEY`: OpenRouter API Key(必需,用于 LLM 增强处理)
- `OPENROUTER_BASE_URL`: OpenRouter API 基础 URL(默认: `https://openrouter.ai/api/v1`)
**处理流程**:
1. 如果 OpenRouter 可用,使用 Llama 3.1 8B Instruct 模型进行智能分析和增强
2. 如果 OpenRouter 不可用或返回错误,自动切换到 fallback 处理逻辑
3. Fallback 处理使用规则引擎提取参数和生成基础 Schema
4. 结果会缓存 24 小时(Redis)
**响应示例**:
```json
{
"processed": true,
"output_format": "json_schema",
"schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name"
}
},
"required": ["location"]
},
"description": "Weather API for getting current weather information",
"parameters": [
{
"name": "location",
"type": "string",
"description": "City name",
"required": true,
"location": "query"
}
],
"examples": [
{
"name": "basic_example",
"description": "Basic example request",
"value": {
"location": "example_location"
}
}
],
"processing_time": 1.234,
"confidence_score": 0.95,
"completeness_score": 0.88,
"from_cache": false
}
```
**响应字段说明**:
- `processed` (boolean): 是否成功处理
- `schema` (object): 生成的 Schema(格式取决于 `output_format`)
- `description` (string): 增强后的 API 描述
- `parameters` (array): 提取的参数列表
- `examples` (array): 生成的示例数据
- `processing_time` (float): 处理耗时(秒)
- `confidence_score` (float): 置信度分数(0-1)
- `completeness_score` (float): 完整性分数(0-1)
- `from_cache` (boolean): 是否来自缓存
**错误处理**:
- 如果 OpenRouter API 返回错误(如余额不足),系统会自动使用 fallback 处理
- Fallback 处理仍能生成有效的 Schema,但质量可能略低
- 所有处理结果都会缓存,减少重复调用
| 字段 | 类型 | 说明 |
|------|------|------|
| processed | boolean | 是否成功处理 |
| output_format | string | 输出格式 |
| schema | object | 生成的 Schema |
| description | string | API 描述 |
| parameters | array | 参数列表 |
| examples | array | 示例数据 |
| processing_time | float | 处理耗时(秒) |
| confidence_score | float | 置信度分数(0-1) |
| completeness_score | float | 完整性分数(0-1) |
| from_cache | boolean | 是否来自缓存 |
---
## 6. 生成工具定义
**POST** `/tools/generate`
**功能**: 从 API 端点生成工具定义
从 API 端点生成工具定义(后台任务)。
**请求方式**: `POST /tools/generate`
**请求体**:
```json
{
"url": "https://api.example.com/users",
"method": "GET",
"name": "get_users",
"description": "Get list of users",
"parameters": [
{
"name": "page",
"type": "integer",
"location": "query",
"required": false
}
],
"responses": {},
"security": [],
"tags": ["users"]
}
```
**请求体参数**:
**响应示例**:
```json
{
"message": "工具生成任务已启动",
"endpoint": "https://api.example.com/users",
"method": "GET"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| url | string | 是 | API 端点 URL |
| method | string | 是 | HTTP 方法 |
| name | string | 是 | 工具名称 |
| description | string | 否 | 工具描述 |
| parameters | array | 否 | 参数定义列表 |
| responses | object | 否 | 响应定义 |
| security | array | 否 | 安全配置 |
| tags | array | 否 | 标签列表 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| message | string | 操作结果消息 |
| endpoint | string | API 端点 |
| method | string | HTTP 方法 |
---
## 7. 获取工具列表
**GET** `/tools`
**功能**: 获取已生成的工具列表
获取已生成的工具列表。
**请求方式**: `GET /tools`
**查询参数**:
- `category` (string, 可选): 工具分类
- `limit` (int, 可选, 默认: 100): 返回数量限制
- `offset` (int, 可选, 默认: 0): 偏移量
**请求示例**:
```bash
curl -X GET "http://localhost:8001/tools?category=weather&limit=20&offset=0"
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| category | string | 否 | 工具分类筛选 |
| limit | int | 否 | 返回数量限制,默认100 |
| offset | int | 否 | 偏移量,默认0 |
**响应示例**:
```json
[
{
"name": "get_weather",
"description": "Get weather information",
"category": "weather",
"url": "https://api.example.com/weather",
"method": "GET",
"parameters": [],
"created_at": "2025-12-25T05:00:00Z"
}
]
```
**响应字段**: 返回工具数组,每个工具包含:
| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 工具名称 |
| description | string | 工具描述 |
| category | string | 工具分类 |
| url | string | API URL |
| method | string | HTTP 方法 |
| parameters | array | 参数列表 |
| created_at | string | 创建时间 |
---
## 8. 获取特定工具定义
**GET** `/tools/{tool_name}`
**功能**: 获取指定工具的详细定义
获取特定工具的定义。
**请求方式**: `GET /tools/{tool_name}`
**请求示例**:
```bash
curl -X GET "http://localhost:8001/tools/get_weather"
```
**路径参数**:
**响应示例**:
```json
{
"name": "get_weather",
"description": "Get weather information",
"category": "weather",
"url": "https://api.example.com/weather",
"method": "GET",
"parameters": [],
"created_at": "2025-12-25T05:00:00Z"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tool_name | string | 是 | 工具名称 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 工具名称 |
| description | string | 工具描述 |
| category | string | 工具分类 |
| url | string | API URL |
| method | string | HTTP 方法 |
| parameters | array | 参数列表 |
| created_at | string | 创建时间 |
---
## 9. 删除工具
**DELETE** `/tools/{tool_name}`
**功能**: 删除指定的工具定义
删除指定的工具定义。
**请求方式**: `DELETE /tools/{tool_name}`
**请求示例**:
```bash
curl -X DELETE "http://localhost:8001/tools/get_weather"
```
**路径参数**:
**响应示例**:
```json
{
"message": "工具 get_weather 已删除"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tool_name | string | 是 | 工具名称 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| message | string | 操作结果消息 |
---
## 10. 获取统计信息
**GET** `/stats`
**功能**: 获取服务统计信息
获取服务统计信息。
**请求方式**: `GET /stats`
**请求示例**:
```bash
curl -X GET "http://localhost:8001/stats"
```
**参数**: 无
**响应示例**:
```json
{
"total_apis": 100,
"processed_apis": 85,
"generated_tools": 20,
"failed_processes": 2,
"cache_size": 44,
"last_sync": "2025-12-25T05:00:00Z",
"categories": {
"weather": 15,
"finance": 10,
"general": 5
}
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| total_apis | int | API 总数 |
| processed_apis | int | 已处理 API 数 |
| generated_tools | int | 已生成工具数 |
| failed_processes | int | 失败处理数 |
| cache_size | int | 缓存大小 |
| last_sync | string | 最后同步时间 |
| categories | object | 各分类统计 |
---
## 11. 清除缓存
**POST** `/cache/clear`
**功能**: 清除处理缓存
清除处理缓存(保留工具注册表)。
**请求方式**: `POST /cache/clear`
**请求示例**:
```bash
curl -X POST "http://localhost:8001/cache/clear"
```
**参数**: 无
**响应示例**:
```json
{
"message": "缓存已清理"
}
```
**响应字段**:
> **说明**: 该端点仅清除处理缓存和失败记录,不会删除已生成的工具定义。
| 字段 | 类型 | 说明 |
|------|------|------|
| message | string | 操作结果消息 |
---
## 12. Prometheus Metrics
**GET** `/metrics`
**功能**: 获取 Prometheus 格式的监控指标
获取 Prometheus 格式的监控指标。
**请求方式**: `GET /metrics`
**请求示例**:
```bash
curl -X GET "http://localhost:8001/metrics"
```
**参数**: 无
**响应**: Prometheus 格式的文本数据
---
> 返回 [API接口文档](./API接口文档.md)
File diff suppressed because it is too large Load Diff
@@ -2,8 +2,6 @@
**基础URL**: `http://localhost:4000`
> **说明**: Model Gateway 使用 LiteLLM 代理网关,提供统一的模型访问接口,支持 OpenAI、Anthropic、OpenRouter 等多种模型提供商。
> 返回 [API接口文档](./API接口文档.md)
---
@@ -15,7 +13,6 @@
3. [Chat Completions](#3-chat-completions)
4. [流式 Chat Completions](#4-流式-chat-completions)
5. [API Key 管理](#5-api-key-管理)
6. [模型路由配置](#6-模型路由配置)
---
@@ -27,230 +24,132 @@
Authorization: Bearer <api_key>
```
### 可用的 API Keys
| API Key | 权限 | 可用模型 |
|---------|------|---------|
| `sk-taiji-master-key` | 全部模型 | 所有配置的模型 |
| `sk-taiji-mcp-server` | MCP Server | gpt-3.5-turbo, gpt-4, claude-3-haiku, claude-3-sonnet, openrouter-* |
| `sk-taiji-data-ingestion` | Data Ingestion | gpt-3.5-turbo, claude-3-haiku, llama-3-8b, openrouter-* |
| `sk-taiji-agent-dev` | Agent 开发 | 模型组访问权限 |
| `sk-taiji-premium` | 高级用户 | 所有高级模型 |
---
## 1. 健康检查
**GET** `/health`
**功能**: 检查 LiteLLM 网关健康状态
检查 LiteLLM 网关健康状态和所有模型端点状态。
**请求方式**: `GET /health`
**请求示例**:
```bash
curl -X GET "http://localhost:4000/health" \
-H "Authorization: Bearer sk-taiji-master-key"
```
**请求头**:
- `Authorization: Bearer <api_key>`
**响应示例**:
```json
{
"healthy_endpoints": [
{
"model": "openrouter/openai/gpt-3.5-turbo",
"max_tokens": 1000,
"temperature": 0.7
}
],
"unhealthy_endpoints": [
{
"model": "openrouter/openai/gpt-4o-mini",
"error": "Error code: 402 - Insufficient credits"
}
],
"healthy_count": 1,
"unhealthy_count": 1
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| healthy_endpoints | array | 健康的端点列表 |
| unhealthy_endpoints | array | 不健康的端点列表 |
| healthy_count | int | 健康端点数量 |
| unhealthy_count | int | 不健康端点数量 |
---
## 2. 列出可用模型
**GET** `/v1/models`
**功能**: 获取所有可用的模型列表
获取所有可用的模型列表。
**请求方式**: `GET /v1/models`
**请求示例**:
```bash
curl -X GET "http://localhost:4000/v1/models" \
-H "Authorization: Bearer sk-taiji-master-key"
```
**请求头**:
- `Authorization: Bearer <api_key>`
**响应示例**:
```json
{
"data": [
{
"id": "gpt-3.5-turbo",
"object": "model",
"created": 1677610602,
"owned_by": "openai"
},
{
"id": "openrouter-gpt-3.5-turbo",
"object": "model",
"created": 1677610602,
"owned_by": "openai"
},
{
"id": "openrouter-claude-3.5-sonnet",
"object": "model",
"created": 1677610602,
"owned_by": "openai"
},
{
"id": "test-model",
"object": "model",
"created": 1677610602,
"owned_by": "openai"
}
],
"object": "list"
}
```
**响应字段**:
### 可用模型列表
| 字段 | 类型 | 说明 |
|------|------|------|
| data | array | 模型列表 |
| data[].id | string | 模型 ID |
| data[].object | string | 对象类型 |
| data[].created | int | 创建时间戳 |
| data[].owned_by | string | 所有者 |
| object | string | 响应类型 |
#### OpenAI 模型(通过 OpenRouter)
- `gpt-3.5-turbo` - GPT-3.5 Turbo(别名,实际使用 OpenRouter)
- `openrouter-gpt-3.5-turbo` - GPT-3.5 Turbo
- `openrouter-gpt-4o-mini` - GPT-4o Mini
**可用模型**:
#### Anthropic 模型(通过 OpenRouter)
- `openrouter-claude-3.5-sonnet` - Claude 3.5 Sonnet
- `openrouter-claude-3-opus` - Claude 3 Opus
#### 测试模型
- `test-model` - 测试用模型(OpenRouter Qwen)
| 模型名称 | 说明 |
|---------|------|
| `gpt-3.5-turbo` | GPT-3.5 Turbo |
| `openrouter-gpt-3.5-turbo` | GPT-3.5 Turbo (OpenRouter) |
| `openrouter-gpt-4o-mini` | GPT-4o Mini |
| `openrouter-claude-3.5-sonnet` | Claude 3.5 Sonnet |
| `openrouter-claude-3-opus` | Claude 3 Opus |
| `test-model` | 测试用模型 |
---
## 3. Chat Completions
**POST** `/v1/chat/completions`
**功能**: 发送聊天完成请求
发送聊天完成请求,获取模型响应。
**请求方式**: `POST /v1/chat/completions`
**请求体**:
```json
{
"model": "openrouter-gpt-3.5-turbo",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello, how are you?"
}
],
"temperature": 0.7,
"max_tokens": 150,
"stream": false
}
```
**请求头**:
- `Authorization: Bearer <api_key>`
- `Content-Type: application/json`
**请求参数说明**:
- `model` (string, 必需): 模型名称
- `messages` (array, 必需): 消息数组,每个消息包含 `role` 和 `content`
- `temperature` (float, 可选, 默认: 0.7): 采样温度,范围 0-2
- `max_tokens` (integer, 可选): 最大生成 token 数
- `stream` (boolean, 可选, 默认: false): 是否流式返回
**请求体参数**:
**请求示例**:
```bash
curl -X POST "http://localhost:4000/v1/chat/completions" \
-H "Authorization: Bearer sk-taiji-master-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openrouter-gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "Say hello in Chinese"}
],
"max_tokens": 50
}'
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| model | string | 是 | 模型名称 |
| messages | array | 是 | 消息数组 |
| messages[].role | string | 是 | 角色: `system`, `user`, `assistant` |
| messages[].content | string | 是 | 消息内容 |
| temperature | float | 否 | 采样温度,范围 0-2,默认 0.7 |
| max_tokens | int | 否 | 最大生成 token 数 |
| stream | boolean | 否 | 是否流式返回,默认 false |
**响应示例**:
```json
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1677652288,
"model": "openrouter-gpt-3.5-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 2,
"total_tokens": 12
}
}
```
**响应字段**:
### 错误响应
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 响应 ID |
| object | string | 对象类型 |
| created | int | 创建时间戳 |
| model | string | 使用的模型 |
| choices | array | 响应选项 |
| choices[].index | int | 选项索引 |
| choices[].message | object | 响应消息 |
| choices[].message.role | string | 角色 |
| choices[].message.content | string | 内容 |
| choices[].finish_reason | string | 结束原因 |
| usage | object | Token 使用情况 |
| usage.prompt_tokens | int | 输入 token 数 |
| usage.completion_tokens | int | 输出 token 数 |
| usage.total_tokens | int | 总 token 数 |
**402 错误 - 余额不足**:
```json
{
"detail": "Error code: 402 - {'error': {'message': 'Insufficient credits. Add more using https://openrouter.ai/settings/credits', 'code': 402}}"
}
```
**错误响应**:
**401 错误 - 认证失败**:
```json
{
"detail": "invalid user key"
}
```
| 状态码 | 说明 |
|--------|------|
| 401 | 认证失败 |
| 402 | 余额不足 |
---
## 4. 流式 Chat Completions
**POST** `/v1/chat/completions`
**功能**: 发送流式聊天完成请求
设置 `stream: true` 启用流式响应。
**请求方式**: `POST /v1/chat/completions`
**请求示例**:
```bash
curl -X POST "http://localhost:4000/v1/chat/completions" \
-H "Authorization: Bearer sk-taiji-master-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openrouter-gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "Tell me a short story"}
],
"stream": true
}'
**请求头**:
- `Authorization: Bearer <api_key>`
- `Content-Type: application/json`
**请求体参数**: 同 Chat Completions,设置 `stream: true`
**响应格式**: Server-Sent Events (SSE)
每个事件格式:
```
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"..."}}]}
```
**响应格式** (Server-Sent Events):
结束标记:
```
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1677652288,"model":"openrouter-gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":"Once"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1677652288,"model":"openrouter-gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":" upon"},"finish_reason":null}]}
data: [DONE]
```
@@ -260,182 +159,60 @@ data: [DONE]
### 5.1 创建 API Key
**POST** `/key/generate`
**功能**: 生成新的 API Key
生成新的 API Key。
**请求方式**: `POST /key/generate`
**请求体**:
```json
{
"models": ["gpt-3.5-turbo", "openrouter-gpt-3.5-turbo"],
"max_budget": 100.0,
"budget_duration": "1d",
"metadata": {
"user_id": "user_123",
"service": "custom-service"
}
}
```
**请求体参数**:
**响应示例**:
```json
{
"key": "sk-taiji-custom-abc123",
"models": ["gpt-3.5-turbo", "openrouter-gpt-3.5-turbo"],
"max_budget": 100.0,
"budget_duration": "1d"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| models | array | 否 | 可用模型列表 |
| max_budget | float | 否 | 最大预算 |
| budget_duration | string | 否 | 预算周期 |
| metadata | object | 否 | 元数据 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| key | string | 生成的 API Key |
| models | array | 可用模型列表 |
| max_budget | float | 最大预算 |
| budget_duration | string | 预算周期 |
### 5.2 获取 API Key 信息
**GET** `/key/info`
**功能**: 获取当前 API Key 的信息
获取当前 API Key 的信息。
**请求方式**: `GET /key/info`
**请求示例**:
```bash
curl -X GET "http://localhost:4000/key/info" \
-H "Authorization: Bearer sk-taiji-master-key"
```
**请求头**:
- `Authorization: Bearer <api_key>`
**响应示例**:
```json
{
"key": "sk-taiji-master-key",
"models": ["*"],
"max_budget": 1000.0,
"budget_duration": "30d",
"spent_budget": 245.50,
"remaining_budget": 754.50
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| key | string | API Key |
| models | array | 可用模型列表 |
| max_budget | float | 最大预算 |
| budget_duration | string | 预算周期 |
| spent_budget | float | 已用预算 |
| remaining_budget | float | 剩余预算 |
### 5.3 删除 API Key
**DELETE** `/key/delete`
**功能**: 删除指定的 API Key
删除指定的 API Key。
**请求方式**: `DELETE /key/delete`
**请求体**:
```json
{
"keys": ["sk-taiji-custom-abc123"]
}
```
**请求体参数**:
---
## 6. 模型路由配置
LiteLLM 支持模型组和路由策略,配置在 `litellm.yaml` 中。
### 模型组
- `gpt-3.5-group` - GPT-3.5 模型组
- `gpt-4-group` - GPT-4 模型组
- `claude-group` - Claude 模型组
- `openrouter-group` - OpenRouter 模型组
- `fast-models` - 快速响应模型组
- `premium-models` - 高级模型组
### 路由策略
- `least-busy` - 最少繁忙(默认)
- `round-robin` - 轮询
- `latency-based` - 基于延迟
### 使用模型组
在请求中使用模型组名称:
```json
{
"model": "gpt-3.5-group",
"messages": [...]
}
```
LiteLLM 会自动从模型组中选择合适的模型。
---
## 环境变量配置
### OpenRouter 配置
在 `.env` 文件中配置:
```bash
OPENROUTER_API_KEY=your_openrouter_api_key
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
```
### LiteLLM 配置
```bash
LITELLM_MASTER_KEY=sk-taiji-master-key
LITELLM_CONFIG_PATH=/app/config/litellm_simple.yaml
```
---
## 故障转移
LiteLLM 支持自动故障转移。当主模型不可用时,会自动切换到备用模型。
**配置示例** (在 `litellm.yaml` 中):
```yaml
error_handling:
fallback:
enabled: true
fallback_models:
"gpt-4": ["openrouter-gpt-4", "gpt-4-turbo", "claude-3-sonnet"]
"openrouter-gpt-4": ["gpt-4", "gpt-4-turbo", "openrouter-claude-3.5-sonnet"]
```
---
## 监控与日志
### Prometheus Metrics
**GET** `/metrics`
获取 Prometheus 格式的监控指标。
### 日志
LiteLLM 日志输出到容器日志,可通过以下命令查看:
```bash
docker logs taiji-litellm-gateway --tail 100
```
---
## 常见问题
### Q: 为什么返回 402 错误?
A: OpenRouter 账户余额不足。请访问 https://openrouter.ai/settings/credits 充值。
### Q: 如何切换模型?
A: 在请求的 `model` 字段中指定不同的模型名称即可。
### Q: 如何查看模型使用情况?
A: 使用 `/key/info` 端点查看当前 API Key 的预算使用情况。
### Q: 支持哪些模型提供商?
A: 当前配置支持:
- OpenAI(通过 OpenRouter)
- Anthropic(通过 OpenRouter)
- 其他 OpenRouter 支持的模型
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| keys | array | 是 | 要删除的 API Key 列表 |
---
> 返回 [API接口文档](./API接口文档.md)
@@ -19,174 +19,166 @@
## 1. 获取模型供应商列表
**GET** `/api/providers/models`
**功能**: 获取所有模型供应商列表
**响应示例**:
```json
{
"success": true,
"data": {
"providers": [
{
"id": "provider-uuid-1",
"name": "OpenAI",
"provider": "openai",
"apiUrl": "https://api.openai.com/v1",
"supportedModels": ["gpt-4", "gpt-4o-mini"],
"rpm": 3500,
"tpm": 90000,
"status": "active",
"isActive": true,
"createdAt": "2025-12-01T00:00:00Z"
}
]
}
}
```
**请求方式**: `GET /api/providers/models`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.providers | array | 供应商列表 |
| data.providers[].id | string | 供应商 ID |
| data.providers[].name | string | 供应商名称 |
| data.providers[].provider | string | 供应商类型 |
| data.providers[].apiUrl | string | API 基础 URL |
| data.providers[].supportedModels | array | 支持的模型列表 |
| data.providers[].rpm | int | 每分钟请求数限制 |
| data.providers[].tpm | int | 每分钟 Token 数限制 |
| data.providers[].status | string | 状态 |
| data.providers[].isActive | boolean | 是否活跃 |
| data.providers[].createdAt | string | 创建时间 |
---
## 2. 创建模型供应商
**POST** `/api/providers/models/create`
**功能**: 创建新的模型供应商
**请求体**:
```json
{
"name": "Anthropic",
"provider": "anthropic",
"apiUrl": "https://api.anthropic.com/v1",
"apiKey": "sk-ant-xxxxx",
"supportedModels": ["claude-3-opus", "claude-3-sonnet"],
"rpm": 2000,
"tpm": 80000
}
```
**请求方式**: `POST /api/providers/models/create`
**请求参数说明**:
- `name` (string, 必需): 供应商显示名称
- `provider` (string, 必需): 供应商类型
- `apiUrl` (string, 必需): API基础URL
- `apiKey` (string, 必需): API密钥
- `supportedModels` (array, 必需): 支持的模型列表
- `rpm` (int, 可选): 每分钟请求数限制
- `tpm` (int, 可选): 每分钟Token数限制
**请求体参数**:
**provider可选值**: `openai`, `anthropic`, `azure`, `google`, `aws`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 供应商显示名称 |
| provider | string | 是 | 供应商类型: `openai`, `anthropic`, `azure`, `google`, `aws` |
| apiUrl | string | 是 | API 基础 URL |
| apiKey | string | 是 | API 密钥 |
| supportedModels | array | 是 | 支持的模型列表 |
| rpm | int | 否 | 每分钟请求数限制 |
| tpm | int | 否 | 每分钟 Token 数限制 |
**响应示例**:
```json
{
"success": true,
"data": {
"id": "provider-uuid-2",
"name": "Anthropic",
"provider": "anthropic"
},
"message": "模型供应商创建成功"
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | 供应商 ID |
| data.name | string | 供应商名称 |
| data.provider | string | 供应商类型 |
| message | string | 操作消息 |
---
## 3. 获取供应商详情
**GET** `/api/providers/models/{provider_id}`
**功能**: 获取指定供应商的详细信息
**响应示例**:
```json
{
"success": true,
"data": {
"id": "provider-uuid-1",
"name": "OpenAI",
"provider": "openai",
"apiUrl": "https://api.openai.com/v1",
"supportedModels": ["gpt-4", "gpt-4o-mini"],
"rpm": 3500,
"tpm": 90000,
"status": "active",
"isActive": true,
"createdAt": "2025-12-01T00:00:00Z",
"updatedAt": "2025-12-20T15:30:00Z"
}
}
```
**请求方式**: `GET /api/providers/models/{provider_id}`
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider_id | string | 是 | 供应商 ID |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | 供应商 ID |
| data.name | string | 供应商名称 |
| data.provider | string | 供应商类型 |
| data.apiUrl | string | API 基础 URL |
| data.supportedModels | array | 支持的模型列表 |
| data.rpm | int | 每分钟请求数限制 |
| data.tpm | int | 每分钟 Token 数限制 |
| data.status | string | 状态 |
| data.isActive | boolean | 是否活跃 |
| data.createdAt | string | 创建时间 |
| data.updatedAt | string | 更新时间 |
---
## 4. 更新供应商配置
**PUT** `/api/providers/models/{provider_id}`
**功能**: 更新供应商配置信息
**请求体**:
```json
{
"name": "OpenAI",
"provider": "openai",
"apiUrl": "https://api.openai.com/v1",
"apiKey": "sk-new-key",
"supportedModels": ["gpt-4", "gpt-4o", "gpt-4o-mini"],
"rpm": 4000,
"tpm": 100000
}
```
**请求方式**: `PUT /api/providers/models/{provider_id}`
**响应示例**:
```json
{
"success": true,
"message": "模型供应商更新成功"
}
```
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider_id | string | 是 | 供应商 ID |
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 否 | 供应商显示名称 |
| provider | string | 否 | 供应商类型 |
| apiUrl | string | 否 | API 基础 URL |
| apiKey | string | 否 | API 密钥 |
| supportedModels | array | 否 | 支持的模型列表 |
| rpm | int | 否 | 每分钟请求数限制 |
| tpm | int | 否 | 每分钟 Token 数限制 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| message | string | 操作消息 |
---
## 5. 删除供应商
**DELETE** `/api/providers/models/{provider_id}`
**功能**: 删除供应商(软删除)
**响应示例**:
```json
{
"success": true,
"message": "模型供应商已删除"
}
```
**请求方式**: `DELETE /api/providers/models/{provider_id}`
> **说明**: 删除是软删除,仅标记为不活跃。
**路径参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider_id | string | 是 | 供应商 ID |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| message | string | 操作消息 |
---
## 6. 测试供应商连接
**POST** `/api/providers/models/{provider_id}/test`
**功能**: 测试供应商 API 连接是否正常
测试供应商API连接是否正常。
**请求方式**: `POST /api/providers/models/{provider_id}/test`
**响应示例**:
```json
{
"success": true,
"data": {
"status": "connected",
"latency": 45,
"message": "连接成功"
}
}
```
**路径参数**:
**连接失败响应示例**:
```json
{
"success": false,
"data": {
"status": "failed",
"latency": null,
"message": "API密钥无效"
}
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider_id | string | 是 | 供应商 ID |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.status | string | 连接状态: `connected`, `failed` |
| data.latency | int | 延迟(ms) |
| data.message | string | 状态消息 |
---
File diff suppressed because it is too large Load Diff
@@ -24,8 +24,8 @@
### 代理工厂相关
9. [获取平台Agent列表](#9-获取平台agent列表)
10. [创建自定义Agent](#10-创建自定义agent) ✨ **新增**
11. [获取自定义Agent列表](#11-获取自定义agent列表) ✨ **新增**
10. [创建自定义Agent](#10-创建自定义agent)
11. [获取自定义Agent列表](#11-获取自定义agent列表)
12. [部署Agent](#12-部署agent)
### 编排中心相关
@@ -42,48 +42,49 @@
### 1. 获取仪表板统计
**GET** `/api/user/dashboard/stats`
**功能**: 获取用户仪表板统计数据
**请求头**:
- `Authorization: Bearer <token>`
**请求方式**: `GET /api/user/dashboard/stats`
**响应示例**:
```json
{
"success": true,
"data": {
"activeAgents": 12,
"totalRequests": 1580,
"euBalance": 2450.50,
"systemHealth": 98.5
}
}
```
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.activeAgents | int | 活跃 Agent 数 |
| data.totalRequests | int | 总请求数 |
| data.euBalance | float | EU 余额 |
| data.systemHealth | float | 系统健康度 |
---
### 2. 获取Agent活动数据
**GET** `/api/user/agents/activity`
**功能**: 获取 Agent 活动统计数据
**请求方式**: `GET /api/user/agents/activity`
**请求头**: `Authorization: Bearer <token>`
**查询参数**:
- `period` (string): 时间范围,可选值: `7d`, `30d`, `90d` (默认: `7d`)
**响应示例**:
```json
{
"success": true,
"data": {
"data": [
{
"date": "2025-12-20",
"agentName": "weather-agent",
"requests": 45
}
]
}
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| period | string | 否 | 时间范围: `7d`, `30d`, `90d`,默认 `7d` |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.data | array | 活动数据列表 |
| data.data[].date | string | 日期 |
| data.data[].agentName | string | Agent 名称 |
| data.data[].requests | int | 请求数 |
---
@@ -91,98 +92,97 @@
### 3. 选择网关类型
**POST** `/api/user/gateway/select`
**功能**: 选择服务网关类型
**请求体**:
```json
{
"gatewayType": "MCP"
}
```
**请求方式**: `POST /api/user/gateway/select`
**gatewayType可选值**: `MCP`, `A2A`, `API`
**请求头**: `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"gatewayType": "MCP"
},
"message": "已选择 MCP 网关"
}
```
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| gatewayType | string | 是 | 网关类型: `MCP`, `A2A`, `API` |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.gatewayType | string | 选择的网关类型 |
| message | string | 操作消息 |
---
### 4. 创建网关API
**POST** `/api/user/gateway/api/create`
**功能**: 创建网关 API
**请求体**:
```json
{
"name": "weather-api",
"method": "json",
"content": "{\"endpoint\": \"/weather\", \"params\": {\"city\": \"string\"}}"
}
```
**请求方式**: `POST /api/user/gateway/api/create`
**method可选值**: `json`, `url`
**请求头**: `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"id": "api-uuid-1234",
"name": "weather-api"
},
"message": "API创建成功"
}
```
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | API 名称 |
| method | string | 是 | 方法类型: `json`, `url` |
| content | string | 是 | API 内容 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | API ID |
| data.name | string | API 名称 |
| message | string | 操作消息 |
---
### 5. 获取网关API列表
**GET** `/api/user/gateway/apis`
**功能**: 获取网关 API 列表
**响应示例**:
```json
{
"success": true,
"data": {
"apis": [
{
"id": "api-uuid-1234",
"name": "weather-api",
"method": "json",
"createdAt": "2025-12-25T10:00:00Z"
}
]
}
}
```
**请求方式**: `GET /api/user/gateway/apis`
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.apis | array | API 列表 |
| data.apis[].id | string | API ID |
| data.apis[].name | string | API 名称 |
| data.apis[].method | string | 方法类型 |
| data.apis[].createdAt | string | 创建时间 |
---
### 6. 获取网关监控数据
**GET** `/api/user/gateway/monitoring`
**功能**: 获取网关监控数据
**响应示例**:
```json
{
"success": true,
"data": {
"uptime": 99.9,
"requestsPerMinute": 1250,
"averageLatency": 45,
"errorRate": 0.1
}
}
```
**请求方式**: `GET /api/user/gateway/monitoring`
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.uptime | float | 运行时间(%) |
| data.requestsPerMinute | int | 每分钟请求数 |
| data.averageLatency | int | 平均延迟(ms) |
| data.errorRate | float | 错误率(%) |
---
@@ -190,66 +190,61 @@
### 7. 生成工具
**POST** `/api/user/tools/generate`
**功能**: 生成工具
**请求体**:
```json
{
"name": "calculate-tool",
"description": "数学计算工具",
"frameworkTemplate": "API",
"gateway": "gateway-uuid",
"agentCount": 3,
"cpu": 2.0,
"memory": 4.0,
"maxScale": 10,
"model": "gpt-4o-mini"
}
```
**请求方式**: `POST /api/user/tools/generate`
**响应示例**:
```json
{
"success": true,
"data": {
"id": "tool-uuid-5678",
"name": "calculate-tool"
},
"message": "工具生成成功"
}
```
**请求头**: `Authorization: Bearer <token>`
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 工具名称 |
| description | string | 否 | 工具描述 |
| frameworkTemplate | string | 是 | 框架模板 |
| gateway | string | 是 | 网关 ID |
| agentCount | int | 否 | Agent 数量 |
| cpu | float | 否 | CPU 配置 |
| memory | float | 否 | 内存配置 |
| maxScale | int | 否 | 最大扩展数 |
| model | string | 否 | 使用的模型 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | 工具 ID |
| data.name | string | 工具名称 |
| message | string | 操作消息 |
---
### 8. 创建数据模板
**POST** `/api/user/data-templates/create`
**功能**: 创建数据模板
**请求体示例 (JSON API)**:
```json
{
"name": "orders-api",
"type": "json_api",
"config": {
"apiUrl": "https://api.example.com/orders",
"queryParams": {
"limit": "100"
}
}
}
```
**请求方式**: `POST /api/user/data-templates/create`
**响应示例**:
```json
{
"success": true,
"data": {
"id": "template-uuid-9012",
"name": "orders-api"
},
"message": "数据模板创建成功"
}
```
**请求头**: `Authorization: Bearer <token>`
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 模板名称 |
| type | string | 是 | 模板类型 |
| config | object | 是 | 配置信息 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | 模板 ID |
| data.name | string | 模板名称 |
| message | string | 操作消息 |
---
@@ -257,164 +252,124 @@
### 9. 获取平台Agent列表
**GET** `/api/user/agents/platform`
**功能**: 获取平台 Agent 列表
**响应示例**:
```json
{
"success": true,
"data": {
"data": [
{
"id": "agent-uuid-1",
"name": "weather-agent",
"description": "天气查询Agent",
"category": "数据查询",
"cpu": 2.0,
"memory": 4.0,
"status": "available"
}
]
}
}
```
**请求方式**: `GET /api/user/agents/platform`
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.data | array | Agent 列表 |
| data.data[].id | string | Agent ID |
| data.data[].name | string | Agent 名称 |
| data.data[].description | string | Agent 描述 |
| data.data[].category | string | Agent 分类 |
| data.data[].cpu | float | CPU 配置 |
| data.data[].memory | float | 内存配置 |
| data.data[].status | string | 状态 |
---
### 10. 创建自定义Agent
**POST** `/api/user/agents/custom/create`
**功能**: 创建自定义 Agent
创建租户自定义的Agent。自定义Agent将使用渠道为该租户分配的CPU和内存资源配额。
**请求方式**: `POST /api/user/agents/custom/create`
**请求体**:
```json
{
"name": "my-custom-agent",
"description": "我的自定义客服Agent",
"category": "客服",
"role": "客服助手",
"goal": "帮助用户解答产品相关问题",
"tools": ["search_kb", "create_ticket"],
"config": {
"temperature": 0.7,
"max_tokens": 1000
}
}
```
**请求头**: `Authorization: Bearer <token>`
**请求参数说明**:
- `name` (string, 必需): Agent名称
- `description` (string, 可选): Agent描述
- `category` (string, 可选): Agent分类
- `role` (string, 必需): Agent角色定义
- `goal` (string, 必需): Agent目标描述
- `tools` (array, 可选): Agent可使用的工具列表
- `config` (object, 可选): Agent配置信息
**请求体参数**:
**资源配额说明**:
- 自定义Agent将使用渠道管理员为该租户分配的CPU和内存资源
- 如果租户未被分配customAgentResources,将使用渠道的默认配置
- 资源配额控制确保租户不会超出分配的资源限制
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | Agent 名称 |
| description | string | 否 | Agent 描述 |
| category | string | 否 | Agent 分类 |
| role | string | 是 | Agent 角色定义 |
| goal | string | 是 | Agent 目标描述 |
| tools | array | 否 | 可使用的工具列表 |
| config | object | 否 | 配置信息 |
**响应示例**:
```json
{
"success": true,
"data": {
"id": "agent-uuid-custom-1",
"name": "my-custom-agent",
"type": "custom",
"cpu": 2.0,
"memory": 4.0,
"status": "active"
},
"message": "自定义Agent创建成功"
}
```
**响应字段**:
**错误响应**:
```json
{
"detail": "租户未分配自定义Agent资源配额"
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | Agent ID |
| data.name | string | Agent 名称 |
| data.type | string | Agent 类型 |
| data.cpu | float | CPU 配置 |
| data.memory | float | 内存配置 |
| data.status | string | 状态 |
| message | string | 操作消息 |
---
### 11. 获取自定义Agent列表
**GET** `/api/user/agents/custom`
**功能**: 获取自定义 Agent 列表
获取当前用户创建的所有自定义Agent列表。
**请求方式**: `GET /api/user/agents/custom`
**响应示例**:
```json
{
"success": true,
"data": {
"agents": [
{
"id": "agent-uuid-custom-1",
"name": "my-custom-agent",
"description": "我的自定义客服Agent",
"category": "客服",
"type": "custom",
"cpu": 2.0,
"memory": 4.0,
"status": "active",
"totalExecutions": 145,
"successRate": 98.5,
"avgExecutionTime": 1250.5,
"createdAt": "2025-12-20T10:30:00Z"
}
]
}
}
```
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.agents | array | Agent 列表 |
| data.agents[].id | string | Agent ID |
| data.agents[].name | string | Agent 名称 |
| data.agents[].description | string | Agent 描述 |
| data.agents[].category | string | Agent 分类 |
| data.agents[].type | string | Agent 类型 |
| data.agents[].cpu | float | CPU 配置 |
| data.agents[].memory | float | 内存配置 |
| data.agents[].status | string | 状态 |
| data.agents[].totalExecutions | int | 总执行次数 |
| data.agents[].successRate | float | 成功率 |
| data.agents[].avgExecutionTime | float | 平均执行时间 |
| data.agents[].createdAt | string | 创建时间 |
---
### 12. 部署Agent
**POST** `/api/user/agents/deploy`
**功能**: 部署 Agent
部署平台Agent或自定义Agent。平台Agent使用其自身配置的资源,自定义Agent使用分配的资源配额。
**请求方式**: `POST /api/user/agents/deploy`
**请求体**:
```json
{
"agentId": "agent-uuid-1",
"instances": 3,
"model": "gpt-4o-mini",
"gateway": "MCP"
}
```
**请求头**: `Authorization: Bearer <token>`
**请求参数说明**:
- `agentId` (string, 必需): Agent ID(平台Agent或自定义Agent)
- `instances` (int, 必需): 部署实例数
- `model` (string, 必需): 使用的模型
- `gateway` (string, 必需): 网关类型 (MCP/LiteLLM)
**请求体参数**:
**资源使用说明**:
- **平台Agent**: 使用Agent自身定义的CPU和内存配置
- **自定义Agent**: 使用租户分配的customAgentResources配额
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| agentId | string | 是 | Agent ID |
| instances | int | 是 | 部署实例数 |
| model | string | 是 | 使用的模型 |
| gateway | string | 是 | 网关类型: `MCP`, `LiteLLM` |
**响应示例**:
```json
{
"success": true,
"data": {
"agentId": "agent-uuid-1",
"instances": 3,
"model": "gpt-4o-mini",
"gateway": "MCP",
"userId": "user-uuid"
},
"message": "Agent weather-agent 部署成功"
}
```
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.agentId | string | Agent ID |
| data.instances | int | 实例数 |
| data.model | string | 模型 |
| data.gateway | string | 网关类型 |
| data.userId | string | 用户 ID |
| message | string | 操作消息 |
---
@@ -422,38 +377,33 @@
### 13. 创建工作流
**POST** `/api/user/workflows/create`
**功能**: 创建工作流
**请求体**:
```json
{
"name": "订单处理流程",
"description": "自动化订单处理工作流",
"gateway": "MCP",
"nodes": [
{
"agentId": "agent-uuid-1",
"agentType": "platform",
"agentName": "订单验证Agent",
"order": 1
}
]
}
```
**请求方式**: `POST /api/user/workflows/create`
**注意**: 工作流最多支持3个节点。
**请求头**: `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"id": "workflow-uuid-3456",
"name": "订单处理流程"
},
"message": "工作流创建成功"
}
```
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 工作流名称 |
| description | string | 否 | 工作流描述 |
| gateway | string | 是 | 网关类型 |
| nodes | array | 是 | 节点列表(最多3个) |
| nodes[].agentId | string | 是 | Agent ID |
| nodes[].agentType | string | 是 | Agent 类型 |
| nodes[].agentName | string | 是 | Agent 名称 |
| nodes[].order | int | 是 | 执行顺序 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.id | string | 工作流 ID |
| data.name | string | 工作流名称 |
| message | string | 操作消息 |
---
@@ -461,98 +411,96 @@
### 14. 获取余额信息
**GET** `/api/user/billing/balance`
**功能**: 获取用户余额信息
**响应示例**:
```json
{
"success": true,
"data": {
"balance": 2450.50,
"monthlySpent": 189.75,
"currency": "CNY"
}
}
```
**请求方式**: `GET /api/user/billing/balance`
**请求头**: `Authorization: Bearer <token>`
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.balance | float | 当前余额 |
| data.monthlySpent | float | 本月消费 |
| data.currency | string | 货币类型 |
---
### 15. 充值余额
**POST** `/api/user/billing/recharge`
**功能**: 充值余额
**请求体**:
```json
{
"amount": 500.00,
"paymentMethod": "alipay"
}
```
**请求方式**: `POST /api/user/billing/recharge`
**paymentMethod可选值**: `alipay`, `wechat`, `card`
**请求头**: `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"orderId": "ORD20251225120000abc123",
"amount": 500.00,
"paymentUrl": "https://pay.taiji-ai.com/checkout?order_id=ORD20251225120000abc123",
"status": "pending"
}
}
```
**请求体参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| amount | float | 是 | 充值金额 |
| paymentMethod | string | 是 | 支付方式: `alipay`, `wechat`, `card` |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.orderId | string | 订单 ID |
| data.amount | float | 充值金额 |
| data.paymentUrl | string | 支付链接 |
| data.status | string | 订单状态 |
---
### 16. 获取计费历史
**GET** `/api/user/billing/history`
**功能**: 获取计费历史记录
**请求方式**: `GET /api/user/billing/history`
**请求头**: `Authorization: Bearer <token>`
**查询参数**:
- `startTime` (string, 必需): 开始时间 (ISO 8601)
- `endTime` (string, 必需): 结束时间 (ISO 8601)
- `customerName` (string, 可选): 客户名称筛选
- `minCalls` (int, 可选): 最小调用次数筛选
- `maxCalls` (int, 可选): 最大调用次数筛选
- `export` (string, 可选): 导出格式 (`excel`, `csv`, `pdf`)
- `page` (int, 可选): 页码 (默认: 1)
- `pageSize` (int, 可选): 每页数量 (默认: 20)
**响应示例(查询)**:
```json
{
"success": true,
"data": {
"total": 150,
"records": [
{
"id": "billing-uuid-1",
"timestamp": "2025-12-25T10:30:15Z",
"agentName": "weather-agent",
"duration": 45,
"eu": 5,
"cost": 0.05
}
]
}
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| startTime | string | 是 | 开始时间 (ISO 8601) |
| endTime | string | 是 | 结束时间 (ISO 8601) |
| customerName | string | 否 | 客户名称筛选 |
| minCalls | int | 否 | 最小调用次数筛选 |
| maxCalls | int | 否 | 最大调用次数筛选 |
| export | string | 否 | 导出格式: `excel`, `csv`, `pdf` |
| page | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 每页数量,默认 20 |
**响应示例(导出)**:
```json
{
"success": true,
"data": {
"fileUrl": "https://exports.taiji-ai.com/user-uuid/excel/billing_20251225123000.xlsx",
"format": "excel",
"expiresAt": "2025-12-26T12:30:00Z"
}
}
```
**响应字段(查询)**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.total | int | 总记录数 |
| data.records | array | 记录列表 |
| data.records[].id | string | 记录 ID |
| data.records[].timestamp | string | 时间戳 |
| data.records[].agentName | string | Agent 名称 |
| data.records[].duration | int | 持续时间 |
| data.records[].eu | int | EU 消耗 |
| data.records[].cost | float | 成本 |
**响应字段(导出)**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.fileUrl | string | 文件下载链接 |
| data.format | string | 文件格式 |
| data.expiresAt | string | 链接过期时间 |
---
> 返回 [API接口文档](./API接口文档.md)
File diff suppressed because it is too large Load Diff
@@ -19,190 +19,153 @@
## 1. 用户登录
**POST** `/api/auth/login`
**功能**: 用户登录获取 Token
支持七种角色登录:
**请求方式**: `POST /api/auth/login`
| 角色参数 | 说明 | 登录实体 |
|---------|------|---------|
| `user` | 租户用户 | users表中role=user的用户 |
| `channel` | 渠道管理员 | channels表中的渠道账号 |
| `billing_admin` | 计费管理员 | users表中role=billing_admin的用户 |
| `operations_admin` | 运营管理员 | users表中role=operations_admin的用户 |
| `admin` | 管理员 | users表中role=admin的用户 |
| `super_admin` | 超级管理员 | users表中role=super_admin的用户 |
| `provider` | 供应商管理员 | users表中role=provider_admin的用户 |
**请求体参数**:
**请求体**:
```json
{
"email": "superadmin@taiji-ai.com",
"password": "Admin@123456",
"role": "super_admin"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| email | string | 是 | 用户邮箱 |
| password | string | 是 | 用户密码 |
| role | string | 是 | 登录角色 |
**curl示例**:
```bash
# 超级管理员登录
curl -s -X POST "http://localhost:8002/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"superadmin@taiji-ai.com","password":"Admin@123456","role":"super_admin"}'
**role 可选值**:
# 渠道管理员登录
curl -s -X POST "http://localhost:8002/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"channel-alpha@test.com","password":"Channel@123456","role":"channel"}'
| 角色参数 | 说明 |
|---------|------|
| `user` | 租户用户 |
| `channel` | 渠道管理员 |
| `billing_admin` | 计费管理员 |
| `operations_admin` | 运营管理员 |
| `admin` | 管理员 |
| `super_admin` | 超级管理员 |
| `provider` | 供应商管理员 |
# 租户用户登录
curl -s -X POST "http://localhost:8002/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"zhangsan@company.com","password":"User@123456","role":"user"}'
```
**响应字段**:
**响应示例**:
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b",
"name": "超级管理员",
"email": "superadmin@taiji-ai.com",
"role": "super_admin",
"channelId": null
}
}
}
```
**渠道登录响应示例**:
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "2c1a76d5-9f22-48bc-a5db-8b18b04c5c0b",
"name": "测试渠道Alpha",
"email": "channel-alpha@test.com",
"role": "channel_admin",
"channelId": "2c1a76d5-9f22-48bc-a5db-8b18b04c5c0b"
}
}
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.token | string | 访问 Token |
| data.refreshToken | string | 刷新 Token |
| data.user | object | 用户信息 |
| data.user.id | string | 用户 ID |
| data.user.name | string | 用户名称 |
| data.user.email | string | 用户邮箱 |
| data.user.role | string | 用户角色 |
| data.user.channelId | string | 渠道 ID(渠道用户有值) |
---
## 2. 用户登出
**POST** `/api/auth/logout`
**功能**: 用户登出
**请求方式**: `POST /api/auth/logout`
**请求头**:
- `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"message": "登出成功"
}
```
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| message | string | 操作消息 |
---
## 3. 刷新Token
**POST** `/api/auth/refresh`
**功能**: 刷新访问 Token
**请求方式**: `POST /api/auth/refresh`
**请求头**:
- `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.token | string | 新的访问 Token |
| data.refreshToken | string | 新的刷新 Token |
---
## 4. 修改密码
**PUT** `/api/auth/password`
**功能**: 修改当前用户密码
**请求方式**: `PUT /api/auth/password`
**请求头**:
- `Authorization: Bearer <token>`
**请求体**:
```json
{
"old_password": "admin123",
"new_password": "newpassword456"
}
```
**请求体参数**:
**响应示例**:
```json
{
"success": true,
"message": "密码修改成功"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| old_password | string | 是 | 旧密码 |
| new_password | string | 是 | 新密码 |
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| message | string | 操作消息 |
---
## 5. 获取API密钥信息
**GET** `/api/auth/keys/info`
**功能**: 获取当前用户的 API 密钥信息
获取当前用户的API密钥信息(部分隐藏)
**请求方式**: `GET /api/auth/keys/info`
**请求头**:
- `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"endpoint": "https://api.taiji-ai.com/v1",
"apiKey": "sk-xxxx...xxxx",
"createdAt": "2025-12-25T05:00:00Z",
"lastUsed": "2025-12-25T10:30:00Z"
}
}
```
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.endpoint | string | API 端点 |
| data.apiKey | string | API 密钥(部分隐藏) |
| data.createdAt | string | 创建时间 |
| data.lastUsed | string | 最后使用时间 |
---
## 6. 重新生成API密钥
**POST** `/api/auth/keys/regenerate`
**功能**: 重新生成 API 密钥
重新生成API密钥,旧密钥将立即失效
**请求方式**: `POST /api/auth/keys/regenerate`
**请求头**:
- `Authorization: Bearer <token>`
**响应示例**:
```json
{
"success": true,
"data": {
"apiKey": "sk-aBcD1234EfGh5678IjKl9012MnOp3456",
"message": "旧密钥已失效"
}
}
```
**参数**: 无
**响应字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| success | boolean | 是否成功 |
| data.apiKey | string | 新的 API 密钥 |
| data.message | string | 操作消息 |
---
File diff suppressed because it is too large Load Diff
@@ -1,890 +0,0 @@
# 前端角色选择和权限控制指南
## 文档版本
- **版本**: 1.0
- **更新日期**: 2025-12-25
- **适用范围**: Taiji AI-PAD 前端应用
---
## 目录
1. [角色体系概述](#角色体系概述)
2. [登录界面实现](#登录界面实现)
3. [权限控制实现](#权限控制实现)
4. [路由守卫](#路由守卫)
5. [UI组件权限控制](#ui组件权限控制)
6. [API调用权限](#api调用权限)
7. [示例代码](#示例代码)
---
## 角色体系概述
### 系统角色列表
| 角色代码 | 角色名称 | 说明 | 登录入口 |
|---------|---------|------|---------|
| `user` | 租户用户 | 普通用户,使用平台服务 | 用户登录 |
| `channel_admin` | 渠道管理员 | 管理渠道下的租户和资源 | 渠道登录 |
| `billing_admin` | 计费管理员 | 负责计费、充值等财务操作 | 管理员登录 |
| `operations_admin` | 运营管理员 | 负责租户和资源的日常运营管理 | 管理员登录 |
| `admin` | 管理员 | 平台管理员,拥有综合管理权限 | 管理员登录 |
| `super_admin` | 超级管理员 | 拥有全部权限 | 管理员登录 |
| `provider_admin` | 供应商管理员 | 管理供应商的模型和配置 | 供应商登录 |
### 权限列表
```typescript
export const PERMISSIONS = {
VIEW_OVERVIEW: 'view:overview', // 查看概览
MANAGE_TENANTS: 'manage:tenants', // 管理租户
MANAGE_RESOURCES: 'manage:resources', // 管理资源
VIEW_BILLING: 'view:billing', // 查看计费
MANAGE_BILLING: 'manage:billing', // 管理计费(含充值)
MANAGE_SETTINGS: 'manage:settings', // 管理设置
APPROVE_APPLICATIONS: 'approve:applications', // 审批申请
MANAGE_CHANNELS: 'manage:channels', // 管理渠道
MANAGE_PROVIDERS: 'manage:providers', // 管理供应商
VIEW_MONITORING: 'view:monitoring', // 查看监控
} as const;
```
### 角色权限映射
```typescript
export const ROLE_PERMISSIONS: Record<string, string[]> = {
user: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.VIEW_BILLING,
],
channel_admin: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.MANAGE_TENANTS,
PERMISSIONS.MANAGE_RESOURCES,
PERMISSIONS.VIEW_BILLING,
PERMISSIONS.MANAGE_BILLING,
],
billing_admin: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.VIEW_BILLING,
PERMISSIONS.MANAGE_BILLING,
],
operations_admin: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.MANAGE_TENANTS,
PERMISSIONS.MANAGE_RESOURCES,
PERMISSIONS.VIEW_BILLING,
],
admin: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.MANAGE_TENANTS,
PERMISSIONS.MANAGE_RESOURCES,
PERMISSIONS.VIEW_BILLING,
PERMISSIONS.MANAGE_BILLING,
PERMISSIONS.MANAGE_SETTINGS,
PERMISSIONS.VIEW_MONITORING,
],
super_admin: Object.values(PERMISSIONS), // 全部权限
provider_admin: [
PERMISSIONS.VIEW_OVERVIEW,
PERMISSIONS.MANAGE_PROVIDERS,
],
};
```
---
## 登录界面实现
### 1. 登录页面设计
登录页面应该提供**角色选择**功能,让用户选择以何种身份登录:
```tsx
// LoginPage.tsx
import React, { useState } from 'react';
import { Form, Input, Button, Select, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { login } from '@/services/auth';
const { Option } = Select;
const LoginPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const roleOptions = [
{ value: 'user', label: '租户用户', icon: '👤' },
{ value: 'channel', label: '渠道管理员', icon: '🏢' },
{ value: 'billing_admin', label: '计费管理员', icon: '💰' },
{ value: 'operations_admin', label: '运营管理员', icon: '⚙️' },
{ value: 'admin', label: '管理员', icon: '👨‍💼' },
{ value: 'super_admin', label: '超级管理员', icon: '👑' },
{ value: 'provider', label: '供应商管理员', icon: '🔧' },
];
const onFinish = async (values: any) => {
setLoading(true);
try {
const response = await login({
email: values.email,
password: values.password,
role: values.role,
});
if (response.success) {
// 保存token和用户信息
localStorage.setItem('token', response.data.token);
localStorage.setItem('userInfo', JSON.stringify(response.data.user));
message.success('登录成功!');
// 根据角色跳转到不同页面
redirectByRole(response.data.user.role);
} else {
message.error(response.message || '登录失败');
}
} catch (error) {
message.error('登录失败,请检查网络连接');
} finally {
setLoading(false);
}
};
const redirectByRole = (role: string) => {
const roleRoutes: Record<string, string> = {
user: '/user/dashboard',
channel_admin: '/channel/dashboard',
billing_admin: '/admin/billing',
operations_admin: '/admin/operations',
admin: '/admin/dashboard',
super_admin: '/admin/dashboard',
provider_admin: '/provider/dashboard',
};
window.location.href = roleRoutes[role] || '/';
};
return (
<div className="login-container">
<div className="login-box">
<h1>Taiji AI-PAD</h1>
<h2>多角色统一登录</h2>
<Form
name="login"
onFinish={onFinish}
initialValues={{ role: 'user' }}
>
<Form.Item
name="role"
rules={[{ required: true, message: '请选择登录角色' }]}
>
<Select placeholder="选择登录角色" size="large">
{roleOptions.map(option => (
<Option key={option.value} value={option.value}>
<span>{option.icon} {option.label}</span>
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="email"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' }
]}
>
<Input
prefix={<UserOutlined />}
placeholder="邮箱"
size="large"
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockOutlined />}
placeholder="密码"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={loading}
block
size="large"
>
登录
</Button>
</Form.Item>
</Form>
</div>
</div>
);
};
export default LoginPage;
```
### 2. 认证服务
```typescript
// services/auth.ts
import axios from 'axios';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000/api';
export interface LoginRequest {
email: string;
password: string;
role: string;
}
export interface LoginResponse {
success: boolean;
data: {
token: string;
refresh_token: string;
user: {
id: string;
name: string;
email: string;
role: string;
permissions: string[];
};
};
message?: string;
}
export const login = async (data: LoginRequest): Promise<LoginResponse> => {
const response = await axios.post(`${API_BASE_URL}/auth/login`, data);
return response.data;
};
export const logout = async (): Promise<void> => {
const token = localStorage.getItem('token');
await axios.post(
`${API_BASE_URL}/auth/logout`,
{},
{
headers: { Authorization: `Bearer ${token}` }
}
);
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
};
export const refreshToken = async (): Promise<string> => {
const refreshToken = localStorage.getItem('refresh_token');
const response = await axios.post(`${API_BASE_URL}/auth/refresh`, {
refresh_token: refreshToken
});
const newToken = response.data.data.token;
localStorage.setItem('token', newToken);
return newToken;
};
```
---
## 权限控制实现
### 1. 权限工具函数
```typescript
// utils/permissions.ts
import { ROLE_PERMISSIONS, PERMISSIONS } from '@/constants/permissions';
/**
* 获取当前用户信息
*/
export const getCurrentUser = () => {
const userInfo = localStorage.getItem('userInfo');
return userInfo ? JSON.parse(userInfo) : null;
};
/**
* 获取当前用户角色
*/
export const getCurrentRole = (): string | null => {
const user = getCurrentUser();
return user?.role || null;
};
/**
* 获取当前用户权限列表
*/
export const getCurrentPermissions = (): string[] => {
const user = getCurrentUser();
return user?.permissions || [];
};
/**
* 检查是否有某个权限
*/
export const hasPermission = (permission: string): boolean => {
const permissions = getCurrentPermissions();
return permissions.includes(permission);
};
/**
* 检查是否有任意一个权限
*/
export const hasAnyPermission = (permissions: string[]): boolean => {
const userPermissions = getCurrentPermissions();
return permissions.some(p => userPermissions.includes(p));
};
/**
* 检查是否有所有权限
*/
export const hasAllPermissions = (permissions: string[]): boolean => {
const userPermissions = getCurrentPermissions();
return permissions.every(p => userPermissions.includes(p));
};
/**
* 检查是否是指定角色
*/
export const hasRole = (role: string | string[]): boolean => {
const currentRole = getCurrentRole();
if (!currentRole) return false;
if (Array.isArray(role)) {
return role.includes(currentRole);
}
return currentRole === role;
};
/**
* 检查是否是管理员(包括所有管理员角色)
*/
export const isAdmin = (): boolean => {
return hasRole([
'billing_admin',
'operations_admin',
'admin',
'super_admin'
]);
};
/**
* 检查是否是超级管理员
*/
export const isSuperAdmin = (): boolean => {
return hasRole('super_admin');
};
```
---
## 路由守卫
### 1. 路由配置
```typescript
// router/index.tsx
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import RoleRoute from './RoleRoute';
// 页面组件
import LoginPage from '@/pages/Login';
import UserDashboard from '@/pages/User/Dashboard';
import ChannelDashboard from '@/pages/Channel/Dashboard';
import AdminDashboard from '@/pages/Admin/Dashboard';
import BillingManagement from '@/pages/Admin/Billing';
import OperationsManagement from '@/pages/Admin/Operations';
import ProviderDashboard from '@/pages/Provider/Dashboard';
const AppRouter: React.FC = () => {
return (
<BrowserRouter>
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<LoginPage />} />
{/* 用户路由 */}
<Route
path="/user/*"
element={
<RoleRoute allowedRoles={['user']}>
<UserDashboard />
</RoleRoute>
}
/>
{/* 渠道管理员路由 */}
<Route
path="/channel/*"
element={
<RoleRoute allowedRoles={['channel_admin']}>
<ChannelDashboard />
</RoleRoute>
}
/>
{/* 管理员路由 */}
<Route
path="/admin/*"
element={
<RoleRoute allowedRoles={['billing_admin', 'operations_admin', 'admin', 'super_admin']}>
<AdminDashboard />
</RoleRoute>
}
/>
{/* 计费管理路由 */}
<Route
path="/admin/billing"
element={
<RoleRoute allowedRoles={['billing_admin', 'admin', 'super_admin']}>
<BillingManagement />
</RoleRoute>
}
/>
{/* 运营管理路由 */}
<Route
path="/admin/operations"
element={
<RoleRoute allowedRoles={['operations_admin', 'admin', 'super_admin']}>
<OperationsManagement />
</RoleRoute>
}
/>
{/* 供应商路由 */}
<Route
path="/provider/*"
element={
<RoleRoute allowedRoles={['provider_admin']}>
<ProviderDashboard />
</RoleRoute>
}
/>
{/* 默认重定向 */}
<Route path="/" element={<Navigate to="/login" replace />} />
</Routes>
</BrowserRouter>
);
};
export default AppRouter;
```
### 2. 角色路由守卫组件
```typescript
// router/RoleRoute.tsx
import React from 'react';
import { Navigate } from 'react-router-dom';
import { getCurrentUser, hasRole } from '@/utils/permissions';
interface RoleRouteProps {
allowedRoles: string[];
children: React.ReactNode;
}
const RoleRoute: React.FC<RoleRouteProps> = ({ allowedRoles, children }) => {
const user = getCurrentUser();
// 未登录,跳转到登录页
if (!user) {
return <Navigate to="/login" replace />;
}
// 检查角色权限
if (!hasRole(allowedRoles)) {
// 无权限,跳转到403页面或首页
return <Navigate to="/403" replace />;
}
return <>{children}</>;
};
export default RoleRoute;
```
---
## UI组件权限控制
### 1. 权限包装组件
```typescript
// components/PermissionWrapper.tsx
import React from 'react';
import { hasPermission, hasAnyPermission, hasAllPermissions } from '@/utils/permissions';
interface PermissionWrapperProps {
permission?: string;
anyPermissions?: string[];
allPermissions?: string[];
fallback?: React.ReactNode;
children: React.ReactNode;
}
/**
* 权限包装组件
* 根据权限控制子组件的显示
*/
const PermissionWrapper: React.FC<PermissionWrapperProps> = ({
permission,
anyPermissions,
allPermissions,
fallback = null,
children,
}) => {
let hasAccess = false;
if (permission) {
hasAccess = hasPermission(permission);
} else if (anyPermissions) {
hasAccess = hasAnyPermission(anyPermissions);
} else if (allPermissions) {
hasAccess = hasAllPermissions(allPermissions);
}
return hasAccess ? <>{children}</> : <>{fallback}</>;
};
export default PermissionWrapper;
```
### 2. 使用示例
```tsx
// 示例:根据权限显示按钮
import PermissionWrapper from '@/components/PermissionWrapper';
import { PERMISSIONS } from '@/constants/permissions';
const Dashboard: React.FC = () => {
return (
<div>
<h1>仪表板</h1>
{/* 只有拥有管理租户权限的用户才能看到此按钮 */}
<PermissionWrapper permission={PERMISSIONS.MANAGE_TENANTS}>
<Button type="primary">创建租户</Button>
</PermissionWrapper>
{/* 拥有计费管理或设置管理权限的用户可以看到 */}
<PermissionWrapper
anyPermissions={[PERMISSIONS.MANAGE_BILLING, PERMISSIONS.MANAGE_SETTINGS]}
>
<Button>财务设置</Button>
</PermissionWrapper>
{/* 必须同时拥有两个权限才能看到 */}
<PermissionWrapper
allPermissions={[PERMISSIONS.VIEW_BILLING, PERMISSIONS.MANAGE_BILLING]}
>
<Button danger>执行充值</Button>
</PermissionWrapper>
</div>
);
};
```
---
## API调用权限
### 1. Axios拦截器
```typescript
// utils/request.ts
import axios from 'axios';
import { message } from 'antd';
import { refreshToken } from '@/services/auth';
const request = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:8000/api',
timeout: 30000,
});
// 请求拦截器
request.interceptors.request.use(
(config) => {
// 添加token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
(response) => {
return response.data;
},
async (error) => {
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
// Token过期,尝试刷新
try {
const newToken = await refreshToken();
// 重试原请求
error.config.headers.Authorization = `Bearer ${newToken}`;
return request(error.config);
} catch (refreshError) {
// 刷新失败,跳转到登录页
message.error('登录已过期,请重新登录');
localStorage.clear();
window.location.href = '/login';
}
break;
case 403:
message.error('权限不足,无法执行此操作');
break;
case 404:
message.error('请求的资源不存在');
break;
case 500:
message.error('服务器错误,请稍后重试');
break;
default:
message.error(data?.message || '请求失败');
}
} else {
message.error('网络错误,请检查网络连接');
}
return Promise.reject(error);
}
);
export default request;
```
---
## 示例代码
### 完整的管理员仪表板示例
```tsx
// pages/Admin/Dashboard.tsx
import React, { useEffect, useState } from 'react';
import { Card, Row, Col, Statistic, Button, Table } from 'antd';
import {
UserOutlined,
DollarOutlined,
CloudServerOutlined,
SettingOutlined,
} from '@ant-design/icons';
import PermissionWrapper from '@/components/PermissionWrapper';
import { PERMISSIONS } from '@/constants/permissions';
import { hasRole, getCurrentUser } from '@/utils/permissions';
import request from '@/utils/request';
const AdminDashboard: React.FC = () => {
const [stats, setStats] = useState<any>({});
const [loading, setLoading] = useState(false);
const user = getCurrentUser();
useEffect(() => {
fetchDashboardStats();
}, []);
const fetchDashboardStats = async () => {
setLoading(true);
try {
const response = await request.get('/admin/dashboard/stats');
setStats(response.data);
} catch (error) {
console.error('获取统计数据失败', error);
} finally {
setLoading(false);
}
};
return (
<div className="admin-dashboard">
<h1>管理员仪表板</h1>
<p>欢迎回来,{user?.name}!您的角色:{getRoleName(user?.role)}</p>
<Row gutter={16}>
{/* 租户统计 - 运营管理员、管理员、超级管理员可见 */}
<PermissionWrapper permission={PERMISSIONS.MANAGE_TENANTS}>
<Col span={6}>
<Card>
<Statistic
title="租户总数"
value={stats.total_tenants || 0}
prefix={<UserOutlined />}
/>
</Card>
</Col>
</PermissionWrapper>
{/* 计费统计 - 计费管理员、管理员、超级管理员可见 */}
<PermissionWrapper permission={PERMISSIONS.VIEW_BILLING}>
<Col span={6}>
<Card>
<Statistic
title="今日收入"
value={stats.today_revenue || 0}
prefix={<DollarOutlined />}
precision={2}
/>
</Card>
</Col>
</PermissionWrapper>
{/* 资源统计 - 运营管理员、管理员、超级管理员可见 */}
<PermissionWrapper permission={PERMISSIONS.MANAGE_RESOURCES}>
<Col span={6}>
<Card>
<Statistic
title="活跃Agent"
value={stats.active_agents || 0}
prefix={<CloudServerOutlined />}
/>
</Card>
</Col>
</PermissionWrapper>
{/* 系统设置 - 管理员、超级管理员可见 */}
<PermissionWrapper permission={PERMISSIONS.MANAGE_SETTINGS}>
<Col span={6}>
<Card>
<Statistic
title="系统状态"
value="正常"
prefix={<SettingOutlined />}
/>
</Card>
</Col>
</PermissionWrapper>
</Row>
{/* 操作按钮区 */}
<div style={{ marginTop: 24 }}>
<PermissionWrapper permission={PERMISSIONS.MANAGE_BILLING}>
<Button type="primary" style={{ marginRight: 8 }}>
充值管理
</Button>
</PermissionWrapper>
<PermissionWrapper permission={PERMISSIONS.MANAGE_TENANTS}>
<Button style={{ marginRight: 8 }}>
租户管理
</Button>
</PermissionWrapper>
<PermissionWrapper permission={PERMISSIONS.MANAGE_RESOURCES}>
<Button style={{ marginRight: 8 }}>
资源分配
</Button>
</PermissionWrapper>
<PermissionWrapper permission={PERMISSIONS.VIEW_MONITORING}>
<Button>
系统监控
</Button>
</PermissionWrapper>
</div>
</div>
);
};
const getRoleName = (role: string): string => {
const roleNames: Record<string, string> = {
super_admin: '超级管理员',
admin: '管理员',
billing_admin: '计费管理员',
operations_admin: '运营管理员',
channel_admin: '渠道管理员',
provider_admin: '供应商管理员',
user: '租户用户',
};
return roleNames[role] || role;
};
export default AdminDashboard;
```
---
## 最佳实践
### 1. 权限检查的层次
- **路由层**: 使用 `RoleRoute` 组件保护整个页面
- **UI层**: 使用 `PermissionWrapper` 组件控制按钮、菜单等UI元素的显示
- **API层**: 后端验证权限,前端只是UI控制
### 2. 安全建议
- ✅ 始终在后端验证权限,前端权限控制只是为了更好的用户体验
- ✅ 不要在前端代码中硬编码敏感信息
- ✅ Token应该设置合理的过期时间
- ✅ 使用HTTPS传输敏感数据
- ✅ 实现Token自动刷新机制
### 3. 用户体验
- ✅ 根据用户角色显示合适的菜单和功能
- ✅ 对于无权限的操作,隐藏按钮而不是禁用
- ✅ 提供清晰的权限不足提示
- ✅ 登录后根据角色自动跳转到合适的页面
---
## 测试建议
### 1. 功能测试
- 测试每个角色能否正确登录
- 测试每个角色能否访问其权限范围内的页面
- 测试每个角色是否被正确阻止访问无权限的页面
- 测试权限控制的UI组件是否正确显示/隐藏
### 2. 安全测试
- 尝试直接访问无权限的URL
- 尝试修改localStorage中的用户信息
- 尝试使用过期的Token
- 尝试使用其他用户的Token
---
## 常见问题
### Q1: 如何处理角色层级关系?
A: 在权限检查时,可以使用数组传入多个允许的角色,例如:
```typescript
<RoleRoute allowedRoles={['admin', 'super_admin']}>
```
### Q2: 如何实现动态权限?
A: 从后端获取用户的权限列表,存储在localStorage中,前端根据权限列表动态控制UI。
### Q3: Token刷新失败怎么办?
A: 清除本地存储,跳转到登录页,要求用户重新登录。
---
**文档版本**: 1.0
**最后更新**: 2025-12-25
**维护人**: Taiji AI-PAD Team
@@ -1,279 +0,0 @@
# MCP Server 函数工具调用测试报告
**测试时间**: 2025年12月22日
**测试版本**: v1.2.1
**测试人员**: taiji-AI-PAD 项目组
**最后更新**: 2025年12月22日
---
## 📋 测试概述
本次测试针对 MCP Server 函数工具调用功能进行全面测试,包括:
- 函数注册表功能
- 沙箱执行器功能
- 函数工具调用 API
- 错误处理机制
---
## ✅ 测试结果
### 1. 函数注册表测试
**测试项**: 函数注册表初始化和查询
**测试结果**: ✅ **通过**
```
✅ 函数注册表初始化成功
📊 已注册函数数量: 16
```
**已注册的函数**:
- 数学函数: `math_add`, `math_subtract`, `math_multiply`, `math_divide`, `math_power`
- 字符串函数: `string_upper`, `string_lower`, `string_length`, `string_replace`
- 日期时间: `datetime_now`
- JSON: `json_parse`, `json_stringify`
- 哈希: `hash_md5`, `hash_sha256`
- Base64: `base64_encode`, `base64_decode`
**测试用例**:
- ✅ 函数注册表初始化
- ✅ 函数查询 (`math_add`)
- ✅ 函数执行 (`math_add(10, 20) = 30.0`)
---
### 2. 沙箱执行器测试
**测试项**: 沙箱执行器的安全执行和参数验证
**测试结果**: ✅ **通过**
**测试用例**:
#### 2.1 正常执行
```
✅ math_add(15, 25) = 40.0
```
#### 2.2 字符串函数执行
```
✅ string_upper('hello world') = HELLO WORLD
```
#### 2.3 参数验证
```
✅ 参数验证正确捕获错误: RuntimeError
函数 math_add 执行失败: could not convert string to float: 'invalid'
```
**结论**: 沙箱执行器能够:
- ✅ 正常执行函数
- ✅ 正确处理参数类型转换
- ✅ 正确捕获和报告错误
---
### 3. 函数工具调用 API 测试
**测试项**: 通过 HTTP API 调用函数工具
**测试状态**: ⚠️ **需要服务运行**
**问题**: MCP Server 服务未运行,无法进行 API 测试
**建议**:
1. 启动 MCP Server 服务
2. 创建测试 Agent
3. 通过 `/agents/{agent_id}/execute` 端点测试函数调用
---
### 4. 错误处理测试
**测试项**: 错误场景处理
**测试结果**: ✅ **通过**
**测试场景**:
#### 4.1 未注册函数
- **预期**: 返回错误,拒绝执行
- **实际**: ✅ 正确拒绝未注册的函数
#### 4.2 参数类型错误
- **预期**: 返回参数验证错误
- **实际**: ✅ 正确捕获参数类型错误
#### 4.3 参数值错误
- **预期**: 返回参数值错误
- **实际**: ✅ 正确捕获参数值错误(如字符串无法转换为数字)
---
## 📊 测试统计
| 测试类别 | 测试用例数 | 通过 | 失败 | 跳过 |
|---------|----------|------|------|------|
| 函数注册表 | 3 | 3 | 0 | 0 |
| 沙箱执行器 | 3 | 3 | 0 | 0 |
| API 调用 | 0 | 0 | 0 | 0 |
| 错误处理 | 3 | 3 | 0 | 0 |
| **总计** | **9** | **9** | **0** | **0** |
**通过率**: 100% (已测试部分)
---
## 🔍 详细测试用例
### 测试用例 1: 函数注册表初始化
**步骤**:
1. 导入 `function_registry` 模块
2. 获取函数注册表实例
3. 检查已注册函数数量
**预期结果**: 成功初始化,注册 16 个函数
**实际结果**: ✅ 通过
---
### 测试用例 2: 函数查询
**步骤**:
1. 查询 `math_add` 函数
2. 检查函数信息(描述、参数)
**预期结果**: 返回函数信息
**实际结果**: ✅ 通过
```
✅ math_add 函数存在
描述: 两个数字相加
参数: ['a', 'b']
```
---
### 测试用例 3: 函数执行
**步骤**:
1. 获取 `math_add` 函数
2. 执行 `math_add(10, 20)`
**预期结果**: 返回 30
**实际结果**: ✅ 通过
```
✅ math_add(10, 20) = 30.0
```
---
### 测试用例 4: 沙箱执行器正常执行
**步骤**:
1. 创建沙箱执行器
2. 执行 `math_add(15, 25)`
**预期结果**: 返回 40
**实际结果**: ✅ 通过
```
✅ math_add(15, 25) = 40.0
```
---
### 测试用例 5: 字符串函数执行
**步骤**:
1. 执行 `string_upper('hello world')`
**预期结果**: 返回 'HELLO WORLD'
**实际结果**: ✅ 通过
```
✅ string_upper('hello world') = HELLO WORLD
```
---
### 测试用例 6: 参数验证
**步骤**:
1. 使用无效参数执行函数
2. 检查错误处理
**预期结果**: 正确捕获错误
**实际结果**: ✅ 通过
```
✅ 参数验证正确捕获错误: RuntimeError
```
---
## ⚠️ 待测试项
### 1. API 端点测试
需要 MCP Server 服务运行后测试:
- `POST /agents` - 创建 Agent
- `POST /agents/{agent_id}/execute` - 执行函数工具
- 错误场景测试
### 2. 性能测试
- 并发执行测试
- 超时测试
- 资源限制测试
### 3. 安全测试
- 未注册函数调用测试
- 参数注入测试
- 资源耗尽测试
---
## 📝 测试结论
### ✅ 已通过测试
1. **函数注册表**: 功能正常,16 个函数全部注册成功
2. **沙箱执行器**: 执行正常,参数验证有效
3. **错误处理**: 能够正确捕获和处理错误
### ⚠️ 待完成测试
1. **API 端点测试**: 需要服务运行
2. **集成测试**: 端到端测试
3. **性能测试**: 并发和压力测试
### 🎯 总体评价
**核心功能**: ✅ **正常**
**安全机制**: ✅ **有效**
**错误处理**: ✅ **完善**
**完成度**: 90% (核心功能测试通过,API 测试待服务运行)
---
## 🔧 建议
1. **启动服务**: 确保 MCP Server 服务正常运行
2. **API 测试**: 完成 API 端点测试
3. **性能测试**: 进行并发和压力测试
4. **文档更新**: 根据测试结果更新使用文档
---
**测试完成时间**: 2025年12月22日
**下次测试**: 服务运行后进行 API 端点测试
-308
View File
@@ -1,308 +0,0 @@
# taiji-AI-PAD 数据接入服务测试报告
**测试时间**: 2025年12月22日
**测试版本**: v1.2.1
**测试环境**: Docker Compose
**最后更新**: 2025年12月22日
## 测试概览
本次测试覆盖了数据接入服务和 MCP Server 的核心功能,包括:
- ✅ OpenAPI 解析
- ✅ APILLAMA 处理
- ✅ 工具生成
- ✅ Prometheus Metrics
- ✅ RapidAPI 集成
- ✅ 健康检查
- ✅ MCP Server 函数工具调用(新增)
## 详细测试结果
### 1. 健康检查 ✅
**测试端点**: `GET /health`
**结果**:
```json
{
"status": "healthy",
"services": {
"data_ingestion": "healthy",
"redis": "healthy",
"nats": "healthy",
"rapidapi": "healthy",
"apillama": "healthy"
}
}
```
**状态**: ✅ 所有服务健康
---
### 2. OpenAPI 解析 ✅
**测试端点**: `POST /openapi/parse?url=https://petstore3.swagger.io/api/v3/openapi.json`
**结果**:
- ✅ 成功解析 OpenAPI 3.0 规范
- ✅ 识别了 13 个端点
- ✅ 识别了 6 个 schema
- ✅ 自动生成了 19 个工具(从解析的端点)
**性能**:
- 解析时间: < 2秒
- 缓存: 已启用(Redis + 文件缓存)
**状态**: ✅ 通过
---
### 3. APILLAMA 处理 ✅
**测试端点**: `POST /apillama/process`
**测试数据**: Weather API 文档
**结果**:
- ✅ 成功处理 API 文档
- ✅ 生成了 JSON Schema 格式的 schema
- ✅ 提取了参数定义
- ✅ 生成了示例数据
- ✅ 计算了置信度和完整性分数
**输出格式支持**:
- ✅ Pydantic
- ✅ JSON Schema
- ✅ OpenAPI
**状态**: ✅ 通过
---
### 4. 工具生成 ✅
**测试端点**: `POST /tools/generate`
**结果**:
- ✅ 成功生成工具定义
- ✅ 工具已保存到 Redis
- ✅ 工具已添加到注册表
- ✅ 已发布到 NATS(如果连接)
**统计**:
- 总工具数: 20
- 分类统计:
- `v3`: 19 个工具
- `general`: 1 个工具
**状态**: ✅ 通过
---
### 5. 工具管理 ✅
**测试端点**:
- `GET /tools` - 获取工具列表
- `GET /tools/{tool_name}` - 获取特定工具
**结果**:
- ✅ 成功获取工具列表
- ✅ 支持分页(limit, offset)
- ✅ 支持分类过滤
- ✅ 工具定义完整(包含 schema、参数、描述等)
**状态**: ✅ 通过
---
### 6. 统计信息 ✅
**测试端点**: `GET /stats`
**结果**:
```json
{
"total_apis": 0,
"processed_apis": 0,
"generated_tools": 20,
"failed_processes": 0,
"cache_size": 44,
"categories": {
"v3": 19,
"general": 1
}
}
```
**状态**: ✅ 通过
---
### 7. Prometheus Metrics ✅
**测试端点**: `GET /metrics`
**收集的指标**:
#### HTTP 请求指标
- ✅ `data_ingestion_http_requests_total` - 请求总数(按方法、端点、状态)
- ✅ `data_ingestion_http_request_duration_seconds` - 请求耗时直方图
#### API 处理指标
- ✅ `data_ingestion_openapi_parse_total` - OpenAPI 解析次数
- ✅ `data_ingestion_apillama_processing_total` - APILLAMA 处理次数
- ✅ `data_ingestion_tools_generated_total` - 工具生成次数
- ✅ `data_ingestion_rapidapi_sync_total` - RapidAPI 同步次数
#### 系统指标
- ✅ `data_ingestion_redis_connections` - Redis 连接状态
- ✅ `data_ingestion_nats_connections` - NATS 连接状态
- ✅ `data_ingestion_tools_registry_size` - 工具注册表大小
#### 缓存指标
- ✅ `data_ingestion_cache_hits_total` - 缓存命中
- ✅ `data_ingestion_cache_misses_total` - 缓存未命中
**Prometheus 抓取**: ✅ 正常(Prometheus 已成功抓取指标)
**状态**: ✅ 通过
---
### 8. RapidAPI 集成 ✅
**测试端点**: `POST /rapidapi/sync?limit=5`
**结果**:
- ✅ 同步任务已启动
- ✅ 后台处理正常
- ⚠️ 需要有效的 RapidAPI API Key 才能完成实际同步
**状态**: ✅ 功能正常(需要配置 API Key)
---
### 9. MCP Server 函数工具调用 ✅ (新增)
**测试范围**: MCP Server 函数工具调用功能
**测试结果**:
#### 9.1 函数注册表测试 ✅
- ✅ 成功注册 16 个内置安全函数
- ✅ 函数查询功能正常
- ✅ 函数执行功能正常
**已注册的函数类别**:
- 数学函数: `math_add`, `math_subtract`, `math_multiply`, `math_divide`, `math_power`
- 字符串函数: `string_upper`, `string_lower`, `string_length`, `string_replace`
- 日期时间: `datetime_now`
- JSON: `json_parse`, `json_stringify`
- 哈希: `hash_md5`, `hash_sha256`
- Base64: `base64_encode`, `base64_decode`
#### 9.2 沙箱执行器测试 ✅
- ✅ 正常执行: `math_add(15, 25) = 40.0`
- ✅ 字符串函数: `string_upper('hello world') = HELLO WORLD`
- ✅ 参数验证: 正确捕获错误
#### 9.3 错误处理测试 ✅
- ✅ 未注册函数: 正确拒绝
- ✅ 参数错误: 正确验证和报告
**测试通过率**: 100%
**详细测试报告**: 请参考 [MCP函数工具调用测试报告.md](./MCP函数工具调用测试报告.md)
**状态**: ✅ 通过
---
## 性能指标
### 响应时间
- 健康检查: < 50ms
- OpenAPI 解析: < 2s
- APILLAMA 处理: < 1s
- 工具生成: < 500ms
- Metrics 端点: < 10ms
### 资源使用
- Redis 连接: ✅ 正常
- NATS 连接: ✅ 正常
- 内存使用: 正常范围
- CPU 使用: 正常范围
- MCP Server: ✅ 正常运行
- 函数注册表: ✅ 16个函数已注册
---
## 发现的问题
### 1. APILLAMA context 字段类型 ⚠️
- **问题**: 初始测试中 context 字段类型不匹配
- **原因**: Schema 定义 context 为 Dict,但测试传入字符串
- **状态**: ✅ 已修复(测试时使用正确的字典格式)
### 2. RapidAPI API Key ⚠️
- **问题**: 需要有效的 RapidAPI API Key 才能完成实际同步
- **状态**: ⚠️ 需要配置(功能代码已实现)
---
## 测试结论
### ✅ 通过的功能
1. ✅ OpenAPI 解析 - 完全正常
2. ✅ APILLAMA 处理 - 完全正常
3. ✅ 工具生成 - 完全正常
4. ✅ 工具管理 - 完全正常
5. ✅ Prometheus Metrics - 完全正常
6. ✅ 健康检查 - 完全正常
7. ✅ 统计信息 - 完全正常
8. ✅ RapidAPI 集成 - 代码正常(需要 API Key)
### 📊 测试统计
- **总测试数**: 10
- **通过**: 10
- **失败**: 0
- **需要配置**: 1 (RapidAPI API Key)
### 🎯 总体评价
**功能完整性**: ✅ 100%
**代码质量**: ✅ 优秀
**性能**: ✅ 良好
**稳定性**: ✅ 稳定
所有核心功能均已实现并通过测试,服务可以正常使用。
---
## 下一步建议
1. **配置 RapidAPI API Key** - 完成 RapidAPI 实际同步测试
2. **Grafana 仪表板** - 配置 Prometheus 数据源并创建监控仪表板
3. **压力测试** - 进行负载测试验证性能
4. **集成测试** - 与其他服务进行端到端测试
5. **文档完善** - 添加 API 使用示例和最佳实践
---
**测试人员**: AI Assistant
**审核状态**: ✅ 通过
**当前版本**: v1.2.1
**最后更新**: 2025年12月22日
## 最新更新 (v1.2.1)
### MCP Server 函数工具调用测试 ✅
- ✅ 函数注册表测试通过 (16个内置函数)
- ✅ 沙箱执行器测试通过
- ✅ 错误处理测试通过
- ✅ 测试通过率: 100%
详细测试报告请参考: [MCP函数工具调用测试报告.md](./MCP函数工具调用测试报告.md)
+740
View File
@@ -0,0 +1,740 @@
# AKS Agent 执行方案设计
## 1. 问题分析
### 当前状态
```
用户请求 → MCP Server → 本地 MCP 协议处理 → 工具执行
↓
(未使用 AKS Pod 的 access_url)
```
**问题**:虽然 AKS 部署后返回了 `access_url` 和 `endpoints`,但当前代码并没有使用这些 URL 来调用 AKS 中运行的 Pod。
### 目标状态
```
用户请求 → MCP Server → 判断 Agent 类型 → 转发到 AKS Pod → 返回结果
↓
本地执行(无 K8s 部署)
```
---
## 2. 架构设计
### 2.1 整体架构图
```mermaid
flowchart TB
subgraph Client[客户端]
User[用户]
end
subgraph MCPServer[MCP Server]
API[API Gateway]
Router[Agent Router]
LocalHandler[本地 MCP Handler]
K8sProxy[K8s Agent Proxy]
end
subgraph AKS[Azure Kubernetes Service]
Pod1[Agent Pod 1]
Pod2[Agent Pod 2]
Pod3[Agent Pod N]
end
subgraph AgentManager[Agent Manager Service]
AM[Agent Manager API]
end
User --> API
API --> Router
Router -->|无 K8s 部署| LocalHandler
Router -->|有 K8s 部署| K8sProxy
K8sProxy --> Pod1
K8sProxy --> Pod2
K8sProxy --> Pod3
AM -.->|管理| Pod1
AM -.->|管理| Pod2
AM -.->|管理| Pod3
```
### 2.2 执行流程图
```mermaid
sequenceDiagram
participant User as 用户
participant API as MCP Server API
participant Router as Agent Router
participant DB as 数据库
participant Proxy as K8s Proxy
participant Pod as AKS Agent Pod
participant Local as 本地 Handler
User->>API: POST /agents/{id}/execute
API->>DB: 获取 Agent 信息
DB-->>API: Agent 数据
API->>Router: 路由决策
alt Agent 有 access_url
Router->>Proxy: 转发请求
Proxy->>Pod: HTTP POST /execute
Pod-->>Proxy: 执行结果
Proxy-->>API: 返回结果
else Agent 无 K8s 部署
Router->>Local: 本地执行
Local-->>API: 执行结果
end
API->>DB: 记录执行和计费
API-->>User: 返回结果
```
---
## 3. 详细设计
### 3.1 新增组件:K8s Agent Proxy
**文件位置**: `services/mcp-server/app/k8s_agent_proxy.py`
```python
"""
K8s Agent Proxy - 负责转发请求到 AKS 部署的 Agent Pod
"""
import httpx
import structlog
from typing import Dict, Any, Optional
from dataclasses import dataclass
from datetime import datetime
logger = structlog.get_logger(__name__)
@dataclass
class ProxyConfig:
"""代理配置"""
timeout: float = 30.0
max_retries: int = 3
retry_delay: float = 1.0
health_check_interval: int = 30
@dataclass
class ProxyResult:
"""代理执行结果"""
success: bool
result: Optional[Dict[str, Any]] = None
error: Optional[str] = None
execution_time_ms: float = 0.0
pod_name: Optional[str] = None
status_code: Optional[int] = None
class K8sAgentProxy:
"""K8s Agent 代理类"""
def __init__(self, config: Optional[ProxyConfig] = None):
self.config = config or ProxyConfig()
self._client: Optional[httpx.AsyncClient] = None
async def _get_client(self) -> httpx.AsyncClient:
"""获取或创建 HTTP 客户端"""
if self._client is None or self._client.is_closed:
self._client = httpx.AsyncClient(
timeout=self.config.timeout,
headers={"Content-Type": "application/json"}
)
return self._client
async def execute(
self,
access_url: str,
request_data: Dict[str, Any],
pod_name: Optional[str] = None,
headers: Optional[Dict[str, str]] = None
) -> ProxyResult:
"""
转发执行请求到 AKS Agent Pod
Args:
access_url: Agent Pod 的访问 URL
request_data: MCP 请求数据
pod_name: Pod 名称(用于日志)
headers: 额外的请求头
Returns:
ProxyResult: 执行结果
"""
start_time = datetime.utcnow()
try:
client = await self._get_client()
# 构建完整的执行 URL
execute_url = f"{access_url.rstrip('/')}/execute"
logger.info(
"转发请求到 AKS Agent",
url=execute_url,
pod_name=pod_name,
method=request_data.get("method")
)
# 合并请求头
request_headers = {"Content-Type": "application/json"}
if headers:
request_headers.update(headers)
# 发送请求(带重试)
response = await self._request_with_retry(
client, execute_url, request_data, request_headers
)
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
if response.status_code == 200:
result_data = response.json()
return ProxyResult(
success=True,
result=result_data,
execution_time_ms=execution_time,
pod_name=pod_name,
status_code=response.status_code
)
else:
error_detail = response.text
try:
error_detail = response.json()
except Exception:
pass
return ProxyResult(
success=False,
error=f"Pod 返回错误: {response.status_code} - {error_detail}",
execution_time_ms=execution_time,
pod_name=pod_name,
status_code=response.status_code
)
except httpx.TimeoutException as e:
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
logger.error("请求 AKS Agent 超时", pod_name=pod_name, error=str(e))
return ProxyResult(
success=False,
error=f"请求超时: {str(e)}",
execution_time_ms=execution_time,
pod_name=pod_name
)
except httpx.ConnectError as e:
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
logger.error("无法连接到 AKS Agent", pod_name=pod_name, error=str(e))
return ProxyResult(
success=False,
error=f"连接失败: {str(e)}",
execution_time_ms=execution_time,
pod_name=pod_name
)
except Exception as e:
execution_time = (datetime.utcnow() - start_time).total_seconds() * 1000
logger.error("转发请求失败", pod_name=pod_name, error=str(e))
return ProxyResult(
success=False,
error=f"执行失败: {str(e)}",
execution_time_ms=execution_time,
pod_name=pod_name
)
async def _request_with_retry(
self,
client: httpx.AsyncClient,
url: str,
data: Dict[str, Any],
headers: Dict[str, str]
) -> httpx.Response:
"""带重试的请求"""
import asyncio
last_exception = None
for attempt in range(self.config.max_retries):
try:
response = await client.post(url, json=data, headers=headers)
return response
except (httpx.TimeoutException, httpx.ConnectError) as e:
last_exception = e
if attempt < self.config.max_retries - 1:
await asyncio.sleep(self.config.retry_delay * (attempt + 1))
logger.warning(
f"重试请求 {attempt + 1}/{self.config.max_retries}",
url=url,
error=str(e)
)
raise last_exception
async def health_check(self, access_url: str) -> bool:
"""检查 Agent Pod 健康状态"""
try:
client = await self._get_client()
health_url = f"{access_url.rstrip('/')}/health"
response = await client.get(health_url, timeout=5.0)
return response.status_code == 200
except Exception:
return False
async def close(self):
"""关闭客户端"""
if self._client and not self._client.is_closed:
await self._client.aclose()
self._client = None
# 全局代理实例
_k8s_proxy: Optional[K8sAgentProxy] = None
def get_k8s_agent_proxy() -> K8sAgentProxy:
"""获取全局 K8s Agent 代理实例"""
global _k8s_proxy
if _k8s_proxy is None:
_k8s_proxy = K8sAgentProxy()
return _k8s_proxy
async def close_k8s_agent_proxy():
"""关闭全局代理"""
global _k8s_proxy
if _k8s_proxy:
await _k8s_proxy.close()
_k8s_proxy = None
```
### 3.2 修改 Agent 执行端点
**文件位置**: `services/mcp-server/app/routes/agents.py`
修改 `execute_agent` 函数:
```python
@router.post("/{agent_id}/execute", response_model=ExecutionResult)
async def execute_agent(
agent_id: str,
request: MCPRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
session_id: Optional[str] = None,
) -> ExecutionResult:
"""
执行 Agent 请求。
如果 Agent 部署在 AKS 中,请求将被转发到对应的 Pod。
否则,请求将在本地执行。
"""
state = get_state()
try:
agent_uuid = uuid.UUID(agent_id)
user_id = uuid.UUID(current_user["user_id"])
except ValueError as exc:
raise HTTPException(status_code=400, detail="Invalid agent ID or user ID") from exc
agent = await db.get(Agent, agent_uuid)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
# 权限检查
if agent.owner_id != user_id and current_user.get("role") != "super_admin":
raise HTTPException(status_code=403, detail="Access denied")
# 资源管控检查
await enforce_resource_control(
user_id=str(user_id),
resource_type="agent",
resource_id=agent_id,
estimated_cost=Decimal("0.01"),
db=db
)
start_time = time.time()
execution_id = str(uuid.uuid4())
try:
# ========== 路由决策:K8s Pod 或本地执行 ==========
if agent.access_url and agent.k8s_status == "Running":
# 转发到 AKS Agent Pod
result = await _execute_on_k8s_pod(
agent=agent,
request=request,
execution_id=execution_id,
user_id=str(user_id)
)
else:
# 本地执行
result = await _execute_locally(
agent=agent,
request=request,
execution_id=execution_id,
user_id=str(user_id),
db=db
)
# ================================================
duration = time.time() - start_time
# 记录执行和计费
await _record_execution_and_billing(
db=db,
agent=agent,
request=request,
result=result,
execution_id=execution_id,
start_time=start_time,
duration=duration,
session_id=session_id
)
return result
except Exception as exc:
duration = time.time() - start_time
logger.error("执行Agent任务失败", error=str(exc))
raise HTTPException(status_code=500, detail=str(exc)) from exc
async def _execute_on_k8s_pod(
agent: Agent,
request: MCPRequest,
execution_id: str,
user_id: str
) -> ExecutionResult:
"""转发请求到 AKS Agent Pod"""
from ..k8s_agent_proxy import get_k8s_agent_proxy
proxy = get_k8s_agent_proxy()
# 构建请求数据
request_data = {
"jsonrpc": request.jsonrpc,
"id": str(request.id),
"method": request.method,
"params": request.params or {},
"metadata": {
"execution_id": execution_id,
"user_id": user_id,
"agent_id": str(agent.id)
}
}
# 转发请求
proxy_result = await proxy.execute(
access_url=agent.access_url,
request_data=request_data,
pod_name=agent.pod_name
)
if proxy_result.success:
return ExecutionResult(
execution_id=execution_id,
success=True,
result=proxy_result.result,
execution_time=proxy_result.execution_time_ms,
started_at=datetime.utcnow(),
completed_at=datetime.utcnow()
)
else:
return ExecutionResult(
execution_id=execution_id,
success=False,
error=proxy_result.error,
execution_time=proxy_result.execution_time_ms,
started_at=datetime.utcnow(),
completed_at=datetime.utcnow()
)
async def _execute_locally(
agent: Agent,
request: MCPRequest,
execution_id: str,
user_id: str,
db: AsyncSession
) -> ExecutionResult:
"""本地执行 MCP 请求"""
state = get_state()
handler = state.mcp_handler
if not handler:
raise HTTPException(status_code=500, detail="MCP handler not initialized")
return await handler.execute_request(
str(agent.id),
request,
user_id=user_id,
db_session=db
)
```
### 3.3 AKS Agent Pod 端点规范
每个部署在 AKS 中的 Agent Pod 需要实现以下端点:
| 端点 | 方法 | 描述 |
|-----|------|------|
| `/health` | GET | 健康检查 |
| `/execute` | POST | 执行 MCP 请求 |
| `/status` | GET | 获取 Agent 状态 |
| `/metrics` | GET | 获取资源使用指标 |
#### 3.3.1 `/execute` 端点请求格式
```json
{
"jsonrpc": "2.0",
"id": "request-uuid",
"method": "tools/call",
"params": {
"name": "tool_name",
"arguments": {}
},
"metadata": {
"execution_id": "exec-uuid",
"user_id": "user-uuid",
"agent_id": "agent-uuid"
}
}
```
#### 3.3.2 `/execute` 端点响应格式
```json
{
"success": true,
"result": {
"content": [
{
"type": "text",
"text": "执行结果"
}
]
},
"execution_time_ms": 150.5,
"resource_usage": {
"cpu_ms": 50,
"memory_mb": 128
}
}
```
---
## 4. 数据库变更
### 4.1 Agent 表新增字段(已存在)
当前 `Agent` 模型已包含必要字段:
| 字段 | 类型 | 描述 |
|-----|------|------|
| `access_url` | String(500) | Pod 访问 URL |
| `pod_name` | String(100) | Pod 名称 |
| `pod_ip` | String(45) | Pod IP |
| `k8s_status` | String(20) | Pod 状态 |
| `service_port` | Integer | Service 端口 |
| `endpoints` | JSON | 端点字典 |
### 4.2 新增执行记录字段
在 `Execution` 表中添加:
```python
# 执行位置
execution_location = Column(String(20), default="local") # local, k8s
pod_name = Column(String(100)) # 执行的 Pod 名称
```
---
## 5. 配置变更
### 5.1 环境变量
```bash
# K8s Agent Proxy 配置
K8S_PROXY_TIMEOUT=30.0
K8S_PROXY_MAX_RETRIES=3
K8S_PROXY_RETRY_DELAY=1.0
K8S_PROXY_HEALTH_CHECK_INTERVAL=30
```
### 5.2 应用配置
在 `config.py` 中添加:
```python
class K8sProxyConfig:
timeout: float = float(os.getenv("K8S_PROXY_TIMEOUT", "30.0"))
max_retries: int = int(os.getenv("K8S_PROXY_MAX_RETRIES", "3"))
retry_delay: float = float(os.getenv("K8S_PROXY_RETRY_DELAY", "1.0"))
health_check_interval: int = int(os.getenv("K8S_PROXY_HEALTH_CHECK_INTERVAL", "30"))
```
---
## 6. 实施计划
### 6.1 任务清单
- [ ] **Phase 1: 基础设施**
- [ ] 创建 `k8s_agent_proxy.py` 模块
- [ ] 添加配置项
- [ ] 编写单元测试
- [ ] **Phase 2: 路由逻辑**
- [ ] 修改 `execute_agent` 端点
- [ ] 实现路由决策逻辑
- [ ] 添加本地执行回退
- [ ] **Phase 3: 监控和日志**
- [ ] 添加执行位置记录
- [ ] 添加 Prometheus 指标
- [ ] 完善日志记录
- [ ] **Phase 4: 健康检查**
- [ ] 实现 Pod 健康检查
- [ ] 添加自动故障转移
- [ ] 实现连接池管理
- [ ] **Phase 5: 测试和文档**
- [ ] 集成测试
- [ ] 性能测试
- [ ] 更新 API 文档
### 6.2 文件变更清单
| 文件 | 操作 | 描述 |
|-----|------|------|
| `services/mcp-server/app/k8s_agent_proxy.py` | 新增 | K8s Agent 代理模块 |
| `services/mcp-server/app/routes/agents.py` | 修改 | 添加路由逻辑 |
| `services/mcp-server/config.py` | 修改 | 添加代理配置 |
| `services/mcp-server/app/lifecycle.py` | 修改 | 添加代理生命周期管理 |
| `services/mcp-server/models.py` | 修改 | 添加执行位置字段 |
---
## 7. 错误处理
### 7.1 错误场景和处理策略
| 场景 | 处理策略 |
|-----|---------|
| Pod 不可达 | 重试 3 次后返回错误 |
| Pod 返回 5xx | 记录错误,返回给用户 |
| 请求超时 | 返回超时错误,建议重试 |
| Pod 状态非 Running | 回退到本地执行或返回错误 |
| access_url 为空 | 使用本地执行 |
### 7.2 故障转移策略
```python
async def execute_with_fallback(agent, request, ...):
"""带故障转移的执行"""
# 1. 尝试 K8s Pod 执行
if agent.access_url and agent.k8s_status == "Running":
result = await _execute_on_k8s_pod(...)
if result.success:
return result
# 2. K8s 执行失败,检查是否可以本地执行
if agent.tools and not agent.template:
logger.warning("K8s 执行失败,回退到本地执行")
return await _execute_locally(...)
# 3. 本地执行
return await _execute_locally(...)
```
---
## 8. 监控指标
### 8.1 新增 Prometheus 指标
```python
# K8s Agent 执行指标
k8s_agent_requests_total = Counter(
"k8s_agent_requests_total",
"Total K8s agent requests",
["pod_name", "status"]
)
k8s_agent_request_duration = Histogram(
"k8s_agent_request_duration_seconds",
"K8s agent request duration",
["pod_name"]
)
k8s_agent_health_status = Gauge(
"k8s_agent_health_status",
"K8s agent health status",
["pod_name"]
)
```
### 8.2 日志格式
```json
{
"timestamp": "2024-01-01T00:00:00Z",
"level": "INFO",
"message": "转发请求到 AKS Agent",
"execution_id": "exec-uuid",
"agent_id": "agent-uuid",
"pod_name": "my-agent-abc123",
"access_url": "http://my-agent.ai-agents.svc.cluster.local:8080",
"method": "tools/call",
"execution_location": "k8s"
}
```
---
## 9. 安全考虑
### 9.1 网络安全
- Pod 间通信使用 K8s 内部网络
- 不暴露 Pod 到公网
- 使用 NetworkPolicy 限制访问
### 9.2 认证授权
- 请求中携带 `user_id` 和 `execution_id`
- Pod 可验证请求来源
- 支持 mTLS(可选)
### 9.3 数据安全
- 敏感数据不在日志中记录
- 请求/响应数据加密传输
- 执行结果脱敏存储
---
## 10. 总结
本方案实现了 MCP Server 与 AKS Agent Pod 的集成,主要特点:
1. **智能路由**:根据 Agent 配置自动选择执行位置
2. **故障转移**:K8s 执行失败时可回退到本地
3. **可观测性**:完整的日志、指标和追踪
4. **安全性**:网络隔离和认证机制
5. **可扩展性**:支持多 Pod 负载均衡(未来)
通过此方案,用户可以透明地使用部署在 AKS 中的 Agent,无需关心底层执行细节。
+166
View File
@@ -0,0 +1,166 @@
# 数据库迁移计划:postgres → taiji
**创建时间**: 2025-12-31
**目标**: 将 postgres 库的表结构和数据完全覆盖到 taiji 库
---
## 📋 任务概述
将 Azure PostgreSQL 服务器上的 `postgres` 数据库(新结构)完全复制到 `taiji` 数据库(旧结构),包括:
- 表结构
- 索引
- 约束
- 数据
- 序列
---
## 🔄 迁移流程图
```mermaid
flowchart TD
A[开始迁移] --> B[连接 postgres 源数据库]
B --> C[连接 taiji 目标数据库]
C --> D[备份 taiji 数据库 - 可选]
D --> E[删除 taiji 中的所有表]
E --> F[从 postgres 获取表结构]
F --> G[在 taiji 中创建表]
G --> H[创建索引和约束]
H --> I[复制数据]
I --> J[同步序列值]
J --> K[验证迁移结果]
K --> L[完成]
```
---
## ✅ 任务清单
### 1. 准备工作
- [ ] 确认数据库连接信息正确
- [ ] 确认 postgres 库中有最新的表结构
- [ ] 备份 taiji 库现有数据(可选但推荐)
### 2. 创建迁移脚本
- [ ] 修改现有 `copy_database.py` 脚本,交换源和目标数据库
- [ ] 或创建新脚本 `sync_postgres_to_taiji.py`
### 3. 脚本功能实现
- [ ] 连接源数据库(postgres)
- [ ] 连接目标数据库(taiji)
- [ ] 获取 postgres 库所有表列表
- [ ] 删除 taiji 库中的所有现有表(CASCADE)
- [ ] 复制表结构(DDL)
- [ ] 复制索引定义
- [ ] 复制数据
- [ ] 同步序列值
### 4. 验证和测试
- [ ] 验证表数量一致
- [ ] 验证数据行数一致
- [ ] 验证索引创建成功
- [ ] 测试应用连接 taiji 库正常工作
---
## 📝 技术细节
### 数据库连接信息
```python
DB_HOST = "taijipda.postgres.database.azure.com"
DB_USER = "taiji"
DB_PASSWORD = "By@123456."
DB_PORT = 5432
# 源数据库(新结构)
SOURCE_DB = "postgres"
# 目标数据库(需要更新)
TARGET_DB = "taiji"
```
### 主要表列表(基于 models.py)
| 表名 | 说明 |
|------|------|
| users | 用户表(租户使用者) |
| agents | Agent表(平台Agent和自定义Agent) |
| tools | 工具表 |
| sessions | 会话表 |
| executions | 执行记录表 |
| api_keys | API密钥表 |
| billing | 计费详情表 |
| balances | 用户余额表 |
| billing_records | 计费记录表 |
| channels | 渠道合作伙伴表 |
| model_providers | 模型供应商表 |
| resource_allocations | 资源分配表 |
| applications | 申请审批表 |
| workflows | 工作流表 |
| audit_logs | 审计日志表 |
| channel_agent_quotas | 渠道Agent配额表 |
| provider_models | 模型提供商表 |
| token_blacklist | Token黑名单表 |
| resource_usage | 资源使用记录表 |
| quota_alerts | 配额预警记录表 |
| model_pricing | 模型定价配置表 |
| provider_health_checks | 供应商健康检查记录表 |
| agent_traces | Agent执行轨迹表 |
| billing_events | 计费事件表 |
| channel_provider_access | 渠道供应商授权表 |
| provider_applications | 供应商使用申请表 |
| gateway_apis | 网关API定义表 |
| data_templates | 数据模板表 |
| recharge_records | 充值记录表 |
---
## ⚠️ 注意事项
1. **数据丢失风险**: 此操作会删除 taiji 库中的所有现有数据,请确保已备份
2. **外键约束**: 删除表时使用 CASCADE 处理外键依赖
3. **序列同步**: 确保序列值正确同步,避免主键冲突
4. **连接中断**: 迁移过程中确保网络稳定
5. **应用停机**: 建议在迁移期间停止连接 taiji 库的应用服务
---
## 🚀 执行步骤
1. **运行迁移脚本**:
```bash
cd /home/taiji/tools/taiji-AI-PAD
python scripts/sync_postgres_to_taiji.py
```
2. **验证迁移结果**:
```bash
# 连接 taiji 库检查表
psql "host=taijipda.postgres.database.azure.com port=5432 dbname=taiji user=taiji password=By@123456. sslmode=require"
# 查看所有表
\dt
# 检查数据行数
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM agents;
```
3. **更新应用配置**(如需要):
确保应用的 DATABASE_URL 指向 taiji 库
---
## 📊 预期结果
迁移完成后:
- taiji 库将拥有与 postgres 库完全相同的表结构
- 所有数据将从 postgres 库复制到 taiji 库
- 索引和约束将正确创建
- 序列值将同步
---
**下一步**: 切换到 Code 模式创建迁移脚本
+558
View File
@@ -0,0 +1,558 @@
#!/usr/bin/env python3
"""
将 postgres 数据库的表结构和数据完全覆盖到 taiji 数据库
使用方法:
python scripts/sync_postgres_to_taiji.py
注意:
- 需要安装 psycopg2-binary: pip install psycopg2-binary
- 此操作会删除 taiji 库中的所有现有数据!
- 建议在执行前备份 taiji 库
"""
import psycopg2
from psycopg2.extras import Json, register_default_json, register_default_jsonb
import json
import sys
from datetime import datetime
# 数据库连接配置
DB_HOST = "taijipda.postgres.database.azure.com"
DB_USER = "taiji"
DB_PASSWORD = "By@123456."
DB_PORT = 5432
# 源数据库(新结构)和目标数据库(需要更新)
SOURCE_DB = "postgres" # 新的表结构
TARGET_DB = "taiji" # 需要更新的旧数据库
def get_connection(database):
"""获取数据库连接"""
return psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
user=DB_USER,
password=DB_PASSWORD,
database=database,
sslmode="require"
)
def get_all_tables(conn):
"""获取所有用户表"""
cursor = conn.cursor()
cursor.execute("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
ORDER BY table_name
""")
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
return tables
def get_table_ddl(conn, table_name):
"""获取表的 DDL 语句"""
cursor = conn.cursor()
# 获取列定义
cursor.execute("""
SELECT
column_name,
data_type,
character_maximum_length,
numeric_precision,
numeric_scale,
is_nullable,
column_default,
udt_name
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = %s
ORDER BY ordinal_position
""", (table_name,))
columns = cursor.fetchall()
if not columns:
cursor.close()
return None
# 构建列定义
column_defs = []
for col in columns:
col_name, data_type, char_max_len, num_precision, num_scale, is_nullable, col_default, udt_name = col
# 处理数据类型
if data_type == 'character varying':
if char_max_len:
type_str = f"VARCHAR({char_max_len})"
else:
type_str = "VARCHAR"
elif data_type == 'character':
type_str = f"CHAR({char_max_len})" if char_max_len else "CHAR"
elif data_type == 'numeric':
if num_precision and num_scale:
type_str = f"NUMERIC({num_precision},{num_scale})"
elif num_precision:
type_str = f"NUMERIC({num_precision})"
else:
type_str = "NUMERIC"
elif data_type == 'ARRAY':
type_str = f"{udt_name.lstrip('_')}[]"
elif data_type == 'USER-DEFINED':
type_str = udt_name
else:
type_str = data_type.upper()
# 构建列定义
col_def = f' "{col_name}" {type_str}'
if is_nullable == 'NO':
col_def += " NOT NULL"
if col_default:
col_def += f" DEFAULT {col_default}"
column_defs.append(col_def)
# 获取主键约束
cursor.execute("""
SELECT kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = 'public'
AND tc.table_name = %s
ORDER BY kcu.ordinal_position
""", (table_name,))
pk_columns = [row[0] for row in cursor.fetchall()]
if pk_columns:
pk_cols_str = '", "'.join(pk_columns)
pk_def = f' PRIMARY KEY ("{pk_cols_str}")'
column_defs.append(pk_def)
ddl = f'CREATE TABLE IF NOT EXISTS "{table_name}" (\n'
ddl += ",\n".join(column_defs)
ddl += "\n);"
cursor.close()
return ddl
def get_indexes(conn, table_name):
"""获取表的索引"""
cursor = conn.cursor()
cursor.execute("""
SELECT indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = %s
AND indexname NOT LIKE '%%_pkey'
""", (table_name,))
indexes = [row[0] for row in cursor.fetchall()]
cursor.close()
return indexes
def get_unique_constraints(conn, table_name):
"""获取表的唯一约束"""
cursor = conn.cursor()
cursor.execute("""
SELECT
tc.constraint_name,
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) as columns
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'UNIQUE'
AND tc.table_schema = 'public'
AND tc.table_name = %s
GROUP BY tc.constraint_name
""", (table_name,))
constraints = cursor.fetchall()
cursor.close()
return constraints
def get_foreign_keys(conn, table_name):
"""获取表的外键约束"""
cursor = conn.cursor()
cursor.execute("""
SELECT
tc.constraint_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
AND tc.table_name = %s
""", (table_name,))
fks = cursor.fetchall()
cursor.close()
return fks
def get_sequences(conn):
"""获取所有序列"""
cursor = conn.cursor()
cursor.execute("""
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
""")
sequences = [row[0] for row in cursor.fetchall()]
cursor.close()
return sequences
def get_sequence_value(conn, sequence_name):
"""获取序列当前值"""
cursor = conn.cursor()
try:
cursor.execute(f'SELECT last_value FROM "{sequence_name}"')
value = cursor.fetchone()[0]
except:
value = 1
cursor.close()
return value
def get_json_columns(conn, table_name):
"""获取表中的 JSON/JSONB 列"""
cursor = conn.cursor()
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = %s
AND data_type IN ('json', 'jsonb')
""", (table_name,))
json_cols = [row[0] for row in cursor.fetchall()]
cursor.close()
return json_cols
def copy_table_data(source_conn, target_conn, table_name):
"""复制表数据"""
source_cursor = source_conn.cursor()
target_cursor = target_conn.cursor()
# 获取列名
source_cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = %s
ORDER BY ordinal_position
""", (table_name,))
columns = [row[0] for row in source_cursor.fetchall()]
if not columns:
source_cursor.close()
target_cursor.close()
return 0
# 获取 JSON 列
json_columns = get_json_columns(source_conn, table_name)
json_col_indices = [columns.index(col) for col in json_columns if col in columns]
# 获取数据
columns_str = ', '.join([f'"{c}"' for c in columns])
source_cursor.execute(f'SELECT {columns_str} FROM "{table_name}"')
rows = source_cursor.fetchall()
if not rows:
source_cursor.close()
target_cursor.close()
return 0
# 插入数据
placeholders = ', '.join(['%s'] * len(columns))
insert_sql = f'INSERT INTO "{table_name}" ({columns_str}) VALUES ({placeholders}) ON CONFLICT DO NOTHING'
inserted = 0
for row in rows:
try:
# 转换 JSON 列的数据
row_list = list(row)
for idx in json_col_indices:
if row_list[idx] is not None:
# 如果是 dict 或 list,转换为 Json 对象
if isinstance(row_list[idx], (dict, list)):
row_list[idx] = Json(row_list[idx])
target_cursor.execute(insert_sql, tuple(row_list))
inserted += 1
except Exception as e:
print(f" 警告: 插入数据失败 - {e}")
target_conn.commit()
source_cursor.close()
target_cursor.close()
return inserted
def drop_all_tables(conn):
"""删除目标数据库中的所有表"""
cursor = conn.cursor()
# 获取所有表
cursor.execute("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
""")
tables = [row[0] for row in cursor.fetchall()]
if tables:
# 禁用外键检查并删除所有表
for table in tables:
try:
cursor.execute(f'DROP TABLE IF EXISTS "{table}" CASCADE')
print(f" ✓ 已删除表: {table}")
except Exception as e:
print(f" ✗ 删除表 {table} 失败: {e}")
conn.commit()
cursor.close()
return len(tables)
def get_table_row_count(conn, table_name):
"""获取表的行数"""
cursor = conn.cursor()
try:
cursor.execute(f'SELECT COUNT(*) FROM "{table_name}"')
count = cursor.fetchone()[0]
except:
count = 0
cursor.close()
return count
def verify_migration(source_conn, target_conn, tables):
"""验证迁移结果"""
print("\n[验证] 检查迁移结果...")
all_ok = True
for table in tables:
source_count = get_table_row_count(source_conn, table)
target_count = get_table_row_count(target_conn, table)
if source_count == target_count:
print(f" ✓ {table}: {target_count} 行 (匹配)")
else:
print(f" ✗ {table}: 源={source_count}, 目标={target_count} (不匹配)")
all_ok = False
return all_ok
def main():
print("=" * 70)
print("PostgreSQL 数据库同步工具")
print(f"源数据库: {SOURCE_DB} (新结构)")
print(f"目标数据库: {TARGET_DB} (将被覆盖)")
print(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 70)
# 确认操作
print("\n⚠️ 警告: 此操作将删除 taiji 数据库中的所有现有数据!")
confirm = input("确认继续? (输入 'yes' 继续): ")
if confirm.lower() != 'yes':
print("操作已取消")
sys.exit(0)
# 连接源数据库
print("\n[1] 连接源数据库 (postgres)...")
try:
source_conn = get_connection(SOURCE_DB)
print(f" ✓ 成功连接到 {SOURCE_DB}")
except Exception as e:
print(f" ✗ 连接失败: {e}")
sys.exit(1)
# 连接目标数据库
print("\n[2] 连接目标数据库 (taiji)...")
try:
target_conn = get_connection(TARGET_DB)
print(f" ✓ 成功连接到 {TARGET_DB}")
except Exception as e:
print(f" ✗ 连接失败: {e}")
source_conn.close()
sys.exit(1)
# 获取源数据库表列表
print("\n[3] 获取源数据库表列表...")
tables = get_all_tables(source_conn)
print(f" 找到 {len(tables)} 个表:")
for t in tables:
count = get_table_row_count(source_conn, t)
print(f" - {t} ({count} 行)")
# 删除目标数据库中的旧表
print("\n[4] 清理目标数据库旧表...")
dropped_count = drop_all_tables(target_conn)
print(f" 共删除 {dropped_count} 个旧表")
# 复制表结构
print("\n[5] 复制表结构...")
target_cursor = target_conn.cursor()
for table in tables:
print(f" 处理表: {table}")
# 获取并执行 DDL
ddl = get_table_ddl(source_conn, table)
if ddl:
try:
target_cursor.execute(ddl)
target_conn.commit()
print(f" ✓ 表结构已创建")
except Exception as e:
target_conn.rollback()
if "already exists" in str(e):
print(f" ○ 表已存在,跳过创建")
else:
print(f" ✗ 创建失败: {e}")
target_cursor.close()
# 复制数据(先复制,再创建外键约束)
print("\n[6] 复制表数据...")
for table in tables:
print(f" 复制表: {table}")
try:
count = copy_table_data(source_conn, target_conn, table)
print(f" ✓ 已复制 {count} 行数据")
except Exception as e:
print(f" ✗ 复制失败: {e}")
# 创建索引
print("\n[7] 创建索引...")
target_cursor = target_conn.cursor()
for table in tables:
indexes = get_indexes(source_conn, table)
for idx in indexes:
try:
target_cursor.execute(idx)
target_conn.commit()
print(f" ✓ 索引已创建: {table}")
except Exception as e:
target_conn.rollback()
if "already exists" in str(e):
pass # 静默跳过已存在的索引
else:
print(f" ✗ 索引创建失败 ({table}): {e}")
target_cursor.close()
# 创建唯一约束
print("\n[8] 创建唯一约束...")
target_cursor = target_conn.cursor()
for table in tables:
constraints = get_unique_constraints(source_conn, table)
for constraint_name, columns in constraints:
try:
sql = f'ALTER TABLE "{table}" ADD CONSTRAINT "{constraint_name}" UNIQUE ({columns})'
target_cursor.execute(sql)
target_conn.commit()
print(f" ✓ 唯一约束已创建: {constraint_name}")
except Exception as e:
target_conn.rollback()
if "already exists" in str(e):
pass
else:
print(f" ✗ 唯一约束创建失败 ({constraint_name}): {e}")
target_cursor.close()
# 创建外键约束
print("\n[9] 创建外键约束...")
target_cursor = target_conn.cursor()
for table in tables:
fks = get_foreign_keys(source_conn, table)
for constraint_name, column_name, foreign_table, foreign_column in fks:
try:
sql = f'''
ALTER TABLE "{table}"
ADD CONSTRAINT "{constraint_name}"
FOREIGN KEY ("{column_name}")
REFERENCES "{foreign_table}" ("{foreign_column}")
'''
target_cursor.execute(sql)
target_conn.commit()
print(f" ✓ 外键已创建: {constraint_name}")
except Exception as e:
target_conn.rollback()
if "already exists" in str(e):
pass
else:
print(f" ✗ 外键创建失败 ({constraint_name}): {e}")
target_cursor.close()
# 更新序列
print("\n[10] 同步序列值...")
sequences = get_sequences(source_conn)
target_cursor = target_conn.cursor()
for seq in sequences:
try:
value = get_sequence_value(source_conn, seq)
target_cursor.execute(f"SELECT setval('{seq}', {value}, true)")
target_conn.commit()
print(f" ✓ 序列 {seq} 设置为 {value}")
except Exception as e:
target_conn.rollback()
print(f" ✗ 序列 {seq} 同步失败: {e}")
target_cursor.close()
# 验证迁移结果
print("\n[11] 验证迁移结果...")
# 重新连接以获取最新数据
target_conn.close()
target_conn = get_connection(TARGET_DB)
verify_ok = verify_migration(source_conn, target_conn, tables)
# 关闭连接
source_conn.close()
target_conn.close()
print("\n" + "=" * 70)
if verify_ok:
print("✓ 数据库同步完成!所有数据已成功迁移。")
else:
print("⚠ 数据库同步完成,但部分数据可能不一致,请检查。")
print("=" * 70)
if __name__ == "__main__":
main()
+21 -11
View File
@@ -41,16 +41,26 @@ def create_app() -> FastAPI:
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
"""Enforce API Key/JWT on /api routes except login/health/metrics."""
async with AsyncSessionLocal() as session:
principal = await authenticate_request(request, session)
if principal:
request.state.principal = principal
elif request.url.path.startswith("/api"):
# Allow unauthenticated access for checklist placeholder APIs while keeping
# any provided principal for future auth-enabled endpoints.
request.state.principal = {}
response = await call_next(request)
return response
# 跳过不需要认证的路径
skip_paths = ["/health", "/metrics", "/docs", "/redoc", "/openapi.json"]
if any(request.url.path.startswith(p) for p in skip_paths):
return await call_next(request)
try:
async with AsyncSessionLocal() as session:
principal = await authenticate_request(request, session)
if principal:
request.state.principal = principal
elif request.url.path.startswith("/api"):
# Allow unauthenticated access for checklist placeholder APIs while keeping
# any provided principal for future auth-enabled endpoints.
request.state.principal = {}
response = await call_next(request)
return response
except Exception as e:
import logging
logging.getLogger(__name__).error(f"Auth middleware error: {e}")
request.state.principal = {}
return await call_next(request)
return app
+34 -2
View File
@@ -222,7 +222,9 @@ async def create_agent(
agent.k8s_namespace = result.namespace
agent.k8s_status = result.status
agent.service_port = result.service_port
agent.pod_created_at = datetime.fromisoformat(result.created_at.replace("Z", "+00:00"))
# 解析时间并移除时区信息(数据库使用 TIMESTAMP WITHOUT TIME ZONE)
pod_created = datetime.fromisoformat(result.created_at.replace("Z", "+00:00"))
agent.pod_created_at = pod_created.replace(tzinfo=None)
if result.access_info:
agent.endpoints = result.access_info.get("endpoints", {})
@@ -258,6 +260,37 @@ async def create_agent(
await redis_client.setex(
f"agent:{agent.id}", 3600, json.dumps(card.model_dump(mode="json"), ensure_ascii=False)
)
# 注册Agent的工具权限到Redis
if agent.tools:
agent_tools_key = f"agent:{agent.id}:tools"
for tool_name in agent.tools:
await redis_client.sadd(agent_tools_key, tool_name)
await redis_client.expire(agent_tools_key, 86400) # 24小时过期
# 同时注册工具信息到Redis(如果是内置函数)
from function_registry import get_function_registry
func_registry = get_function_registry()
for tool_name in agent.tools:
func_info = func_registry.get(tool_name)
if func_info:
tool_data = {
"name": tool_name,
"description": func_info.get("description", ""),
"category": "function",
"schema": {
"type": "object",
"properties": {
p["name"]: {"type": p.get("type", "string"), "description": p.get("description", "")}
for p in func_info.get("parameters", [])
},
"required": [p["name"] for p in func_info.get("parameters", []) if p.get("required", True)]
}
}
await redis_client.setex(
f"tool:{tool_name}", 86400, json.dumps(tool_data, ensure_ascii=False)
)
logger.info(f"已为Agent {agent.id} 注册 {len(agent.tools)} 个工具")
# 发布事件到 NATS
nats_client = state.nats_client
@@ -730,7 +763,6 @@ async def execute_agent(
# ==================================
await db.commit()
await db.refresh(execution)
return result
except Exception as exc:
+4 -3
View File
@@ -52,9 +52,10 @@ if database_url_obj.get_backend_name().startswith("sqlite"):
engine_kwargs["connect_args"] = {"check_same_thread": False}
else:
engine_kwargs.update({
"pool_size": 20,
"max_overflow": 0,
"pool_recycle": 3600,
"pool_size": 5,
"max_overflow": 10,
"pool_recycle": 1800,
"pool_timeout": 30,
})
# Azure Database for PostgreSQL 或任何包含 sslmode 的连接都需要 TLS