forked from xiaohei/taiji-AI-PAD
更新计费与资源管理API
This commit is contained in:
+798
-12
@@ -1,6 +1,6 @@
|
||||
# taiji-AI-PAD API 接口文档
|
||||
|
||||
**版本**: v2.5.0
|
||||
**版本**: v2.6.0
|
||||
**更新时间**: 2025年12月26日
|
||||
**基础URL**:
|
||||
- Data Ingestion 服务: `http://localhost:8001` (容器内8000→主机8001)
|
||||
@@ -20,16 +20,17 @@
|
||||
4. [用户侧平台 API](#用户侧平台-api)
|
||||
5. [渠道合作伙伴 API](#渠道合作伙伴-api)
|
||||
6. [超级管理员 API](#超级管理员-api)
|
||||
7. [供应商管理 API](#供应商管理-api)
|
||||
8. [MCP 监控 API](#mcp-监控-api)
|
||||
9. [WebSocket API](#websocket-api)
|
||||
10. [通用响应格式](#通用响应格式)
|
||||
11. [错误码说明](#错误码说明)
|
||||
12. [认证说明](#认证说明)
|
||||
13. [业务规则](#业务规则)
|
||||
14. [交互式 API 文档](#交互式-api-文档)
|
||||
15. [前端集成示例](#前端集成示例)
|
||||
16. [完整 API 测试流程](#完整-api-测试流程)
|
||||
7. [计费与资源管理 API](#计费与资源管理-api) ✨ **新增**
|
||||
8. [供应商管理 API](#供应商管理-api)
|
||||
9. [MCP 监控 API](#mcp-监控-api)
|
||||
10. [WebSocket API](#websocket-api)
|
||||
11. [通用响应格式](#通用响应格式)
|
||||
12. [错误码说明](#错误码说明)
|
||||
13. [认证说明](#认证说明)
|
||||
14. [业务规则](#业务规则)
|
||||
15. [交互式 API 文档](#交互式-api-文档)
|
||||
16. [前端集成示例](#前端集成示例)
|
||||
17. [完整 API 测试流程](#完整-api-测试流程)
|
||||
|
||||
---
|
||||
|
||||
@@ -2634,6 +2635,781 @@ curl -X PUT "http://localhost:8002/api/admin/resources/agents/agent-uuid-1/confi
|
||||
|
||||
---
|
||||
|
||||
## 计费与资源管理 API
|
||||
|
||||
**基础URL**: `http://localhost:8002/api/billing-admin`
|
||||
|
||||
> **权限说明**: 以下接口需要管理员权限(super_admin、billing_admin、operations_admin)
|
||||
|
||||
### 配额管理
|
||||
|
||||
#### 1. 获取用户配额信息
|
||||
|
||||
**GET** `/api/billing-admin/quota/user/{user_id}`
|
||||
|
||||
获取指定用户的配额汇总信息,包括余额、速率限制、活跃预警等。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"hasQuota": true,
|
||||
"alertType": null,
|
||||
"balance": {
|
||||
"balance": 2450.50,
|
||||
"creditLimit": 5000.00,
|
||||
"available": 7450.50,
|
||||
"dailyAvgCost": 85.30,
|
||||
"estimatedDays": 87.3
|
||||
},
|
||||
"rateLimit": {
|
||||
"currentRpm": 12,
|
||||
"rpmLimit": 60,
|
||||
"allowed": true
|
||||
},
|
||||
"activeAlerts": 0,
|
||||
"alerts": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. 获取渠道配额信息
|
||||
|
||||
**GET** `/api/billing-admin/quota/channel/{channel_id}`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"hasQuota": true,
|
||||
"alertType": null,
|
||||
"channelCredit": 100000.00,
|
||||
"monthlyUsage": 15680.50,
|
||||
"usagePercent": 15.68
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 3. 获取配额预警列表
|
||||
|
||||
**GET** `/api/billing-admin/quota/alerts`
|
||||
|
||||
**查询参数**:
|
||||
- `user_id` (string, 可选): 用户ID筛选
|
||||
- `channel_id` (string, 可选): 渠道ID筛选
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"alerts": [
|
||||
{
|
||||
"id": "alert-uuid-1",
|
||||
"alertType": "balance_warning",
|
||||
"thresholdPercent": 20,
|
||||
"currentValue": 150.00,
|
||||
"thresholdValue": 200.00,
|
||||
"status": "active",
|
||||
"createdAt": "2025-12-26T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4. 确认配额预警
|
||||
|
||||
**PUT** `/api/billing-admin/quota/alerts/{alert_id}/acknowledge`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "预警已确认"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 5. 解决配额预警
|
||||
|
||||
**PUT** `/api/billing-admin/quota/alerts/{alert_id}/resolve`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "预警已解决"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 资源监控
|
||||
|
||||
#### 6. 获取平台资源概览
|
||||
|
||||
**GET** `/api/billing-admin/resources/overview`
|
||||
|
||||
获取平台整体资源使用概览(管理员视图)。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"todayCalls": 1580,
|
||||
"monthCalls": 45680,
|
||||
"activeUsersToday": 85,
|
||||
"activeAgents": 125,
|
||||
"monthTotalEu": 4568,
|
||||
"timestamp": "2025-12-26T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 7. 获取用户资源使用汇总
|
||||
|
||||
**GET** `/api/billing-admin/resources/user/{user_id}`
|
||||
|
||||
**查询参数**:
|
||||
- `start_date` (string, 必需): 开始日期 (ISO 8601)
|
||||
- `end_date` (string, 必需): 结束日期 (ISO 8601)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalCpuSeconds": 12580.5,
|
||||
"totalMemoryMbSeconds": 458720.0,
|
||||
"totalNetworkBytes": 156800000,
|
||||
"totalStorageBytes": 52428800,
|
||||
"totalApiCalls": 1580,
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-26T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 8. 获取资源使用趋势
|
||||
|
||||
**GET** `/api/billing-admin/resources/trends`
|
||||
|
||||
**查询参数**:
|
||||
- `user_id` (string, 必需): 用户ID
|
||||
- `period` (string, 可选): 时间范围,可选值: `7d`, `30d`, `90d` (默认: `7d`)
|
||||
- `granularity` (string, 可选): 粒度,可选值: `hourly`, `daily` (默认: `daily`)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"trends": [
|
||||
{
|
||||
"periodStart": "2025-12-25T00:00:00Z",
|
||||
"periodEnd": "2025-12-25T23:59:59Z",
|
||||
"cpuSeconds": 458.5,
|
||||
"memoryMbSeconds": 16720.0,
|
||||
"networkBytes": 5680000,
|
||||
"apiCalls": 58
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 9. 获取Agent资源统计
|
||||
|
||||
**GET** `/api/billing-admin/resources/agent/{agent_id}`
|
||||
|
||||
**查询参数**:
|
||||
- `start_date` (string, 必需): 开始日期
|
||||
- `end_date` (string, 必需): 结束日期
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"agentId": "agent-uuid-1",
|
||||
"totalExecutions": 1580,
|
||||
"avgExecutionTime": 145.2,
|
||||
"totalEuConsumed": 158.0,
|
||||
"successRate": 98.5,
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-26T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 事件管理
|
||||
|
||||
#### 10. 获取待处理事件
|
||||
|
||||
**GET** `/api/billing-admin/events/pending`
|
||||
|
||||
获取待处理的计费事件列表。
|
||||
|
||||
**查询参数**:
|
||||
- `limit` (int, 可选): 返回数量限制 (默认: 100, 最大: 1000)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"events": [
|
||||
{
|
||||
"id": "event-uuid-1",
|
||||
"eventId": "evt_abc123",
|
||||
"eventType": "execution.end",
|
||||
"userId": "user-uuid-1",
|
||||
"agentId": "agent-uuid-1",
|
||||
"payload": {
|
||||
"success": true,
|
||||
"duration_ms": 1250,
|
||||
"eu_consumed": 0.13
|
||||
},
|
||||
"status": "pending",
|
||||
"createdAt": "2025-12-26T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 11. 重试失败事件
|
||||
|
||||
**POST** `/api/billing-admin/events/retry-failed`
|
||||
|
||||
**查询参数**:
|
||||
- `max_retries` (int, 可选): 最大重试次数 (默认: 3, 最大: 10)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"retriedCount": 5
|
||||
},
|
||||
"message": "已重试 5 个事件"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 12. 获取事件统计
|
||||
|
||||
**GET** `/api/billing-admin/events/stats`
|
||||
|
||||
**查询参数**:
|
||||
- `start_date` (string, 必需): 开始日期
|
||||
- `end_date` (string, 必需): 结束日期
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-26T23:59:59Z",
|
||||
"byStatus": {
|
||||
"completed": 4520,
|
||||
"pending": 15,
|
||||
"failed": 3
|
||||
},
|
||||
"byType": {
|
||||
"execution.end": 4200,
|
||||
"execution.start": 4200,
|
||||
"balance.deduct": 138
|
||||
},
|
||||
"total": 4538
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 追踪管理
|
||||
|
||||
#### 13. 获取执行追踪详情
|
||||
|
||||
**GET** `/api/billing-admin/traces/execution/{execution_id}`
|
||||
|
||||
获取单个执行的完整追踪信息。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"executionId": "exec-uuid-1",
|
||||
"traceId": "trace-abc123",
|
||||
"spans": [
|
||||
{
|
||||
"spanId": "span-1",
|
||||
"parentSpanId": null,
|
||||
"operationName": "agent_execute",
|
||||
"operationType": "agent_call",
|
||||
"startedAt": "2025-12-26T12:00:00Z",
|
||||
"endedAt": "2025-12-26T12:00:01.250Z",
|
||||
"durationMs": 1250,
|
||||
"status": "success",
|
||||
"inputData": {"prompt": "***REDACTED***"},
|
||||
"outputData": {"result": "..."},
|
||||
"tokensUsed": 450,
|
||||
"euConsumed": 0.13
|
||||
}
|
||||
],
|
||||
"totalDurationMs": 1250,
|
||||
"totalEuConsumed": 0.13,
|
||||
"spanCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 14. 查询追踪记录
|
||||
|
||||
**GET** `/api/billing-admin/traces`
|
||||
|
||||
**查询参数**:
|
||||
- `user_id` (string, 可选): 用户ID筛选
|
||||
- `agent_id` (string, 可选): Agent ID筛选
|
||||
- `status` (string, 可选): 状态筛选 (`running`, `success`, `error`)
|
||||
- `start_date` (string, 可选): 开始日期
|
||||
- `end_date` (string, 可选): 结束日期
|
||||
- `page` (int, 可选): 页码 (默认: 1)
|
||||
- `page_size` (int, 可选): 每页数量 (默认: 20, 最大: 100)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 150,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 8,
|
||||
"traces": [
|
||||
{
|
||||
"traceId": "trace-abc123",
|
||||
"executionId": "exec-uuid-1",
|
||||
"agentId": "agent-uuid-1",
|
||||
"agentName": "weather-agent",
|
||||
"userId": "user-uuid-1",
|
||||
"startedAt": "2025-12-26T12:00:00Z",
|
||||
"endedAt": "2025-12-26T12:00:01.250Z",
|
||||
"durationMs": 1250,
|
||||
"spanCount": 3,
|
||||
"totalTokens": 450,
|
||||
"totalEu": 0.13
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 15. 获取追踪统计
|
||||
|
||||
**GET** `/api/billing-admin/traces/stats`
|
||||
|
||||
**查询参数**:
|
||||
- `user_id` (string, 可选): 用户ID筛选
|
||||
- `start_date` (string, 必需): 开始日期
|
||||
- `end_date` (string, 必需): 结束日期
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-26T23:59:59Z",
|
||||
"totalTraces": 4200,
|
||||
"totalSpans": 12600,
|
||||
"totalDurationMs": 5250000,
|
||||
"avgDurationMs": 1250.0,
|
||||
"totalTokens": 1890000,
|
||||
"totalEu": 546.0,
|
||||
"byStatus": {
|
||||
"success": 4150,
|
||||
"error": 50
|
||||
},
|
||||
"byOperationType": [
|
||||
{"operationType": "agent_call", "count": 4200, "avgDurationMs": 850.0},
|
||||
{"operationType": "tool_call", "count": 6300, "avgDurationMs": 120.5},
|
||||
{"operationType": "llm_call", "count": 2100, "avgDurationMs": 980.0}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 审计日志
|
||||
|
||||
#### 16. 查询审计日志
|
||||
|
||||
**GET** `/api/billing-admin/audit/logs`
|
||||
|
||||
**查询参数**:
|
||||
- `user_id` (string, 可选): 用户ID筛选
|
||||
- `action` (string, 可选): 操作类型筛选
|
||||
- `resource_type` (string, 可选): 资源类型筛选
|
||||
- `success` (bool, 可选): 成功/失败筛选
|
||||
- `start_date` (string, 可选): 开始日期
|
||||
- `end_date` (string, 可选): 结束日期
|
||||
- `page` (int, 可选): 页码 (默认: 1)
|
||||
- `page_size` (int, 可选): 每页数量 (默认: 20, 最大: 100)
|
||||
|
||||
**可用的action类型**:
|
||||
| 操作类型 | 说明 |
|
||||
|---------|------|
|
||||
| `auth.login` | 用户登录 |
|
||||
| `auth.logout` | 用户登出 |
|
||||
| `auth.password_change` | 密码修改 |
|
||||
| `user.create` | 创建用户 |
|
||||
| `user.update` | 更新用户 |
|
||||
| `user.delete` | 删除用户 |
|
||||
| `channel.create` | 创建渠道 |
|
||||
| `channel.update` | 更新渠道 |
|
||||
| `channel.delete` | 删除渠道 |
|
||||
| `agent.create` | 创建Agent |
|
||||
| `agent.delete` | 删除Agent |
|
||||
| `application.approve` | 审批通过 |
|
||||
| `application.reject` | 审批拒绝 |
|
||||
| `billing.charge` | 计费扣款 |
|
||||
| `provider.pricing_update` | 更新模型定价 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 500,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 25,
|
||||
"logs": [
|
||||
{
|
||||
"id": "log-uuid-1",
|
||||
"action": "channel.create",
|
||||
"actionName": "创建渠道",
|
||||
"resourceType": "channel",
|
||||
"resourceId": "channel-uuid-1",
|
||||
"userId": "admin-uuid-1",
|
||||
"userName": "超级管理员",
|
||||
"success": true,
|
||||
"details": {"channelName": "合作渠道A"},
|
||||
"errorMessage": null,
|
||||
"ipAddress": "192.168.1.100",
|
||||
"createdAt": "2025-12-26T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 17. 获取审计日志汇总
|
||||
|
||||
**GET** `/api/billing-admin/audit/summary`
|
||||
|
||||
**查询参数**:
|
||||
- `start_date` (string, 必需): 开始日期
|
||||
- `end_date` (string, 必需): 结束日期
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"startDate": "2025-12-01T00:00:00Z",
|
||||
"endDate": "2025-12-26T23:59:59Z",
|
||||
"total": 1580,
|
||||
"successTotal": 1550,
|
||||
"failTotal": 30,
|
||||
"byAction": [
|
||||
{"action": "auth.login", "actionName": "用户登录", "count": 850, "successCount": 820, "failCount": 30},
|
||||
{"action": "agent.create", "actionName": "创建Agent", "count": 45, "successCount": 45, "failCount": 0}
|
||||
],
|
||||
"byResourceType": [
|
||||
{"resourceType": "user", "count": 520},
|
||||
{"resourceType": "agent", "count": 380},
|
||||
{"resourceType": "channel", "count": 150}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 18. 获取用户活动历史
|
||||
|
||||
**GET** `/api/billing-admin/audit/user/{user_id}/activity`
|
||||
|
||||
**查询参数**:
|
||||
- `days` (int, 可选): 天数 (默认: 30, 最大: 90)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"activity": [
|
||||
{
|
||||
"action": "auth.login",
|
||||
"actionName": "用户登录",
|
||||
"resourceType": "user",
|
||||
"resourceId": "user-uuid-1",
|
||||
"success": true,
|
||||
"ipAddress": "192.168.1.100",
|
||||
"createdAt": "2025-12-26T08:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 供应商健康检查
|
||||
|
||||
#### 19. 获取所有供应商健康状态
|
||||
|
||||
**GET** `/api/billing-admin/providers/health`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"providers": [
|
||||
{
|
||||
"providerId": "provider-uuid-1",
|
||||
"providerName": "OpenAI",
|
||||
"provider": "openai",
|
||||
"status": "active",
|
||||
"isHealthy": true,
|
||||
"lastResponseTimeMs": 45,
|
||||
"lastCheckAt": "2025-12-26T12:00:00Z",
|
||||
"uptime24h": 99.9
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 20. 获取供应商健康详情
|
||||
|
||||
**GET** `/api/billing-admin/providers/{provider_id}/health`
|
||||
|
||||
**查询参数**:
|
||||
- `hours` (int, 可选): 统计时间范围 (默认: 24, 最大: 168)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"providerId": "provider-uuid-1",
|
||||
"providerName": "OpenAI",
|
||||
"currentStatus": "active",
|
||||
"period": "24h",
|
||||
"totalChecks": 1440,
|
||||
"healthyCount": 1438,
|
||||
"unhealthyCount": 2,
|
||||
"uptimePercent": 99.86,
|
||||
"avgResponseTimeMs": 42.5,
|
||||
"maxResponseTimeMs": 180,
|
||||
"minResponseTimeMs": 25,
|
||||
"recentChecks": [
|
||||
{
|
||||
"isHealthy": true,
|
||||
"responseTimeMs": 45,
|
||||
"statusCode": 200,
|
||||
"errorMessage": null,
|
||||
"createdAt": "2025-12-26T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 21. 执行供应商健康检查
|
||||
|
||||
**POST** `/api/billing-admin/providers/health-check`
|
||||
|
||||
立即执行所有供应商的健康检查。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"timestamp": "2025-12-26T12:00:00Z",
|
||||
"totalProviders": 3,
|
||||
"healthyCount": 3,
|
||||
"unhealthyCount": 0,
|
||||
"checks": [
|
||||
{
|
||||
"providerId": "provider-uuid-1",
|
||||
"providerName": "OpenAI",
|
||||
"isHealthy": true,
|
||||
"responseTimeMs": 45,
|
||||
"statusCode": 200,
|
||||
"errorMessage": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 模型定价管理
|
||||
|
||||
#### 22. 获取模型定价列表
|
||||
|
||||
**GET** `/api/billing-admin/pricing/models`
|
||||
|
||||
**查询参数**:
|
||||
- `provider_id` (string, 可选): 供应商ID筛选
|
||||
- `model_name` (string, 可选): 模型名称模糊搜索
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"pricing": [
|
||||
{
|
||||
"id": "pricing-uuid-1",
|
||||
"providerId": "provider-uuid-1",
|
||||
"providerName": "OpenAI",
|
||||
"modelName": "gpt-4o",
|
||||
"inputPricePer1k": 0.005,
|
||||
"outputPricePer1k": 0.015,
|
||||
"euPer1kTokens": 0.1,
|
||||
"maxContextLength": 128000,
|
||||
"maxOutputTokens": 4096,
|
||||
"isActive": true,
|
||||
"effectiveFrom": "2025-12-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 23. 创建/更新模型定价
|
||||
|
||||
**POST** `/api/billing-admin/pricing/models`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"providerId": "provider-uuid-1",
|
||||
"modelName": "gpt-4o-mini",
|
||||
"inputPricePer1k": 0.00015,
|
||||
"outputPricePer1k": 0.0006,
|
||||
"euPer1kTokens": 0.05,
|
||||
"maxContextLength": 128000,
|
||||
"maxOutputTokens": 16384
|
||||
}
|
||||
```
|
||||
|
||||
**请求参数说明**:
|
||||
- `providerId` (string, 必需): 供应商ID
|
||||
- `modelName` (string, 必需): 模型名称
|
||||
- `inputPricePer1k` (float, 必需): 输入价格(每1K tokens),单位USD
|
||||
- `outputPricePer1k` (float, 必需): 输出价格(每1K tokens),单位USD
|
||||
- `euPer1kTokens` (float, 可选): EU转换率(默认: 0.1)
|
||||
- `maxContextLength` (int, 可选): 最大上下文长度(默认: 4096)
|
||||
- `maxOutputTokens` (int, 可选): 最大输出tokens(默认: 2048)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "pricing-uuid-2",
|
||||
"modelName": "gpt-4o-mini"
|
||||
},
|
||||
"message": "模型定价已更新"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 24. 计算模型调用成本
|
||||
|
||||
**POST** `/api/billing-admin/pricing/calculate`
|
||||
|
||||
计算指定模型调用的成本预估。
|
||||
|
||||
**查询参数**:
|
||||
- `model_name` (string, 必需): 模型名称
|
||||
- `input_tokens` (int, 必需): 输入tokens数量
|
||||
- `output_tokens` (int, 必需): 输出tokens数量
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8002/api/billing-admin/pricing/calculate?model_name=gpt-4o&input_tokens=1000&output_tokens=500" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"modelName": "gpt-4o",
|
||||
"inputTokens": 1000,
|
||||
"outputTokens": 500,
|
||||
"inputCost": 0.005,
|
||||
"outputCost": 0.0075,
|
||||
"totalCost": 0.0125,
|
||||
"euConsumed": 0.15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 供应商管理 API
|
||||
|
||||
**基础URL**: `http://localhost:8002/api/providers`
|
||||
@@ -2926,7 +3702,7 @@ def get_dashboard(token: str):
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v2.5.0
|
||||
**文档版本**: v2.6.0
|
||||
**最后更新**: 2025年12月26日
|
||||
**维护者**: taiji-AI-PAD 项目组
|
||||
|
||||
@@ -3057,6 +3833,16 @@ echo "=== API测试完成 ==="
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **v2.6.0** (2025-12-26): **新增计费与资源管理API**
|
||||
- ✅ 新增配额管理API(用户配额、渠道配额、配额预警)
|
||||
- ✅ 新增资源监控API(平台概览、用户资源、使用趋势、Agent统计)
|
||||
- ✅ 新增事件管理API(待处理事件、重试失败、事件统计)
|
||||
- ✅ 新增追踪管理API(执行追踪详情、追踪查询、追踪统计)
|
||||
- ✅ 新增审计日志API(日志查询、汇总统计、用户活动历史)
|
||||
- ✅ 新增供应商健康检查API(健康状态、健康详情、手动检查)
|
||||
- ✅ 新增模型定价管理API(定价列表、创建/更新定价、成本计算)
|
||||
- ✅ 新增数据模型:TokenBlacklist、ResourceUsage、QuotaAlert、ModelPricing、ProviderHealthCheck、AgentTrace、BillingEvent
|
||||
- ✅ 增强JWT认证:登出时将Token加入黑名单
|
||||
- **v2.5.0** (2025-12-26): **权限系统重构**
|
||||
- ✅ 重新设计权限系统,区分计费管理员和运维管理员
|
||||
- ✅ billing_admin(计费管理员):完整写入权限(创建渠道、管理租户、计费操作、审批申请)
|
||||
|
||||
@@ -1,723 +0,0 @@
|
||||
# taiji-AI-PAD API 接口文档 - 监控功能
|
||||
|
||||
**版本**: v2.0
|
||||
**创建时间**: 2025年12月23日
|
||||
**最后更新**: 2025年12月23日
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [基础信息](#基础信息)
|
||||
3. [监控API端点](#监控api端点)
|
||||
4. [请求/响应示例](#请求响应示例)
|
||||
5. [错误处理](#错误处理)
|
||||
6. [集成示例](#集成示例)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档描述了 taiji-AI-PAD 平台监控功能的 API 接口。监控功能提供系统健康检查、性能指标、资源使用、服务统计、性能趋势和系统告警等功能。
|
||||
|
||||
### 1.1 功能特性
|
||||
|
||||
- ✅ 系统健康检查
|
||||
- ✅ 实时性能指标(CPU、内存、磁盘)
|
||||
- ✅ 服务统计信息(Agent、执行、工具、用户)
|
||||
- ✅ 性能趋势分析
|
||||
- ✅ 系统告警
|
||||
- ✅ 监控仪表板(聚合数据)
|
||||
|
||||
### 1.2 监控指标
|
||||
|
||||
- **系统资源**: CPU使用率、内存使用、磁盘使用
|
||||
- **服务指标**: 活跃Agent数、执行次数、成功率、平均响应时间
|
||||
- **业务指标**: 日活用户、EU消耗、成本统计
|
||||
- **告警信息**: 资源告警、服务告警
|
||||
|
||||
---
|
||||
|
||||
## 2. 基础信息
|
||||
|
||||
### 2.1 基础URL
|
||||
|
||||
```
|
||||
http://localhost:8002
|
||||
```
|
||||
|
||||
### 2.2 认证方式
|
||||
|
||||
当前版本无需认证,未来版本将支持 JWT Token 认证。
|
||||
|
||||
### 2.3 响应格式
|
||||
|
||||
所有API响应均为 JSON 格式,使用 UTF-8 编码。
|
||||
|
||||
### 2.4 HTTP状态码
|
||||
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 监控API端点
|
||||
|
||||
### 3.1 系统健康检查
|
||||
|
||||
#### GET /health
|
||||
|
||||
获取系统健康状态。
|
||||
|
||||
**请求参数**: 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"services": {
|
||||
"database": "healthy",
|
||||
"redis": "healthy",
|
||||
"nats": "healthy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `status`: 系统整体状态 (`healthy`, `degraded`, `unhealthy`)
|
||||
- `timestamp`: 检查时间戳
|
||||
- `services`: 各服务健康状态
|
||||
|
||||
---
|
||||
|
||||
### 3.2 系统性能指标
|
||||
|
||||
#### GET /api/v1/monitoring/metrics
|
||||
|
||||
获取系统实时性能指标。
|
||||
|
||||
**请求参数**: 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"system": {
|
||||
"cpu_usage_percent": 15.5,
|
||||
"memory_usage_percent": 45.2,
|
||||
"memory_used_mb": 2048.5,
|
||||
"memory_total_mb": 4096.0,
|
||||
"disk_usage_percent": 32.1,
|
||||
"disk_used_gb": 128.5,
|
||||
"disk_total_gb": 400.0
|
||||
},
|
||||
"services": {
|
||||
"active_agents": 10,
|
||||
"total_executions_24h": 1250,
|
||||
"success_rate_percent": 98.5,
|
||||
"avg_execution_time_ms": 125.5,
|
||||
"daily_active_users": 25
|
||||
},
|
||||
"billing": {
|
||||
"total_eu_consumed_24h": 1250.5,
|
||||
"total_cost_24h": 12.50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `system`: 系统资源使用情况
|
||||
- `cpu_usage_percent`: CPU使用率(%)
|
||||
- `memory_usage_percent`: 内存使用率(%)
|
||||
- `memory_used_mb`: 已使用内存(MB)
|
||||
- `memory_total_mb`: 总内存(MB)
|
||||
- `disk_usage_percent`: 磁盘使用率(%)
|
||||
- `disk_used_gb`: 已使用磁盘(GB)
|
||||
- `disk_total_gb`: 总磁盘空间(GB)
|
||||
- `services`: 服务指标(过去24小时)
|
||||
- `active_agents`: 活跃Agent数量
|
||||
- `total_executions_24h`: 总执行次数
|
||||
- `success_rate_percent`: 成功率(%)
|
||||
- `avg_execution_time_ms`: 平均执行时间(毫秒)
|
||||
- `daily_active_users`: 日活用户数
|
||||
- `billing`: 计费统计(过去24小时)
|
||||
- `total_eu_consumed_24h`: 总EU消耗
|
||||
- `total_cost_24h`: 总成本
|
||||
|
||||
---
|
||||
|
||||
### 3.3 服务统计信息
|
||||
|
||||
#### GET /api/v1/monitoring/stats
|
||||
|
||||
获取服务统计信息。
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| service | string | 否 | 服务类型,可选值: `all`, `agents`, `executions`, `tools`, `users`,默认: `all` |
|
||||
|
||||
**请求示例**:
|
||||
```
|
||||
GET /api/v1/monitoring/stats?service=agents
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"stats": {
|
||||
"agents": {
|
||||
"total": 50,
|
||||
"active": 45,
|
||||
"inactive": 5,
|
||||
"avg_executions": 125.5,
|
||||
"avg_success_rate": 98.2
|
||||
},
|
||||
"executions": {
|
||||
"total_7d": 8750,
|
||||
"completed": 8600,
|
||||
"failed": 100,
|
||||
"running": 50,
|
||||
"avg_time_ms": 125.5,
|
||||
"total_eu": 8750.5
|
||||
},
|
||||
"tools": {
|
||||
"total": 20,
|
||||
"active": 18,
|
||||
"total_calls": 12500,
|
||||
"avg_success_rate": 99.5,
|
||||
"avg_response_time_ms": 50.2
|
||||
},
|
||||
"users": {
|
||||
"total": 100,
|
||||
"active": 95,
|
||||
"admins": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `agents`: Agent统计
|
||||
- `total`: 总Agent数
|
||||
- `active`: 活跃Agent数
|
||||
- `inactive`: 非活跃Agent数
|
||||
- `avg_executions`: 平均执行次数
|
||||
- `avg_success_rate`: 平均成功率
|
||||
- `executions`: 执行统计(过去7天)
|
||||
- `total_7d`: 总执行次数
|
||||
- `completed`: 成功完成数
|
||||
- `failed`: 失败数
|
||||
- `running`: 运行中数
|
||||
- `avg_time_ms`: 平均执行时间(毫秒)
|
||||
- `total_eu`: 总EU消耗
|
||||
- `tools`: 工具统计
|
||||
- `total`: 总工具数
|
||||
- `active`: 活跃工具数
|
||||
- `total_calls`: 总调用次数
|
||||
- `avg_success_rate`: 平均成功率
|
||||
- `avg_response_time_ms`: 平均响应时间(毫秒)
|
||||
- `users`: 用户统计
|
||||
- `total`: 总用户数
|
||||
- `active`: 活跃用户数
|
||||
- `admins`: 管理员数
|
||||
|
||||
---
|
||||
|
||||
### 3.4 性能趋势数据
|
||||
|
||||
#### GET /api/v1/monitoring/trends
|
||||
|
||||
获取性能趋势数据。
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| metric | string | 否 | 指标类型,可选值: `executions`, `eu_consumption`,默认: `executions` |
|
||||
| period | string | 否 | 时间范围,可选值: `24h`, `7d`, `30d`,默认: `24h` |
|
||||
| interval | string | 否 | 时间间隔,可选值: `1h`, `6h`, `1d`,默认: `1h` |
|
||||
|
||||
**请求示例**:
|
||||
```
|
||||
GET /api/v1/monitoring/trends?metric=executions&period=7d&interval=6h
|
||||
```
|
||||
|
||||
**响应示例** (metric=executions):
|
||||
```json
|
||||
{
|
||||
"metric": "executions",
|
||||
"period": "7d",
|
||||
"interval": "6h",
|
||||
"data": [
|
||||
{
|
||||
"timestamp": "2025-12-23T00:00:00",
|
||||
"count": 125,
|
||||
"avg_time_ms": 120.5,
|
||||
"success_rate": 98.5
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-23T06:00:00",
|
||||
"count": 150,
|
||||
"avg_time_ms": 125.2,
|
||||
"success_rate": 99.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例** (metric=eu_consumption):
|
||||
```json
|
||||
{
|
||||
"metric": "eu_consumption",
|
||||
"period": "24h",
|
||||
"interval": "1h",
|
||||
"data": [
|
||||
{
|
||||
"timestamp": "2025-12-23T00:00:00",
|
||||
"eu_consumed": 50.5,
|
||||
"cost": 0.50
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-23T01:00:00",
|
||||
"eu_consumed": 52.3,
|
||||
"cost": 0.52
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `metric`: 指标类型
|
||||
- `period`: 时间范围
|
||||
- `interval`: 时间间隔
|
||||
- `data`: 趋势数据数组
|
||||
- `timestamp`: 时间点
|
||||
- `count`: 执行次数(executions指标)
|
||||
- `avg_time_ms`: 平均执行时间(executions指标)
|
||||
- `success_rate`: 成功率(executions指标)
|
||||
- `eu_consumed`: EU消耗(eu_consumption指标)
|
||||
- `cost`: 成本(eu_consumption指标)
|
||||
|
||||
---
|
||||
|
||||
### 3.5 系统告警
|
||||
|
||||
#### GET /api/v1/monitoring/alerts
|
||||
|
||||
获取系统告警信息。
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| severity | string | 否 | 严重程度过滤,可选值: `warning`, `critical`, `info` |
|
||||
|
||||
**请求示例**:
|
||||
```
|
||||
GET /api/v1/monitoring/alerts?severity=critical
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"alerts": [
|
||||
{
|
||||
"severity": "warning",
|
||||
"type": "high_cpu",
|
||||
"message": "CPU使用率过高: 85.5%",
|
||||
"timestamp": "2025-12-23T07:29:00.000000"
|
||||
},
|
||||
{
|
||||
"severity": "critical",
|
||||
"type": "low_disk",
|
||||
"message": "磁盘空间不足: 92.1%",
|
||||
"timestamp": "2025-12-23T07:25:00.000000"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `timestamp`: 查询时间
|
||||
- `alerts`: 告警列表
|
||||
- `severity`: 严重程度 (`warning`, `critical`, `info`)
|
||||
- `type`: 告警类型 (`high_cpu`, `high_memory`, `low_disk`, `high_failure_rate`)
|
||||
- `message`: 告警消息
|
||||
- `timestamp`: 告警时间
|
||||
- `count`: 告警总数
|
||||
|
||||
**告警类型说明**:
|
||||
- `high_cpu`: CPU使用率 > 80%
|
||||
- `high_memory`: 内存使用率 > 85%
|
||||
- `low_disk`: 磁盘使用率 > 90%
|
||||
- `high_failure_rate`: 过去1小时内失败执行 > 10次
|
||||
|
||||
---
|
||||
|
||||
### 3.6 监控仪表板
|
||||
|
||||
#### GET /api/v1/monitoring/dashboard
|
||||
|
||||
获取监控仪表板数据(聚合所有监控信息)。
|
||||
|
||||
**请求参数**: 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"health": {
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"uptime_seconds": 86400,
|
||||
"services": {
|
||||
"database": "healthy",
|
||||
"redis": "healthy",
|
||||
"nats": "healthy"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"timestamp": "2025-12-23T07:30:00.000000",
|
||||
"system": {
|
||||
"cpu_usage_percent": 15.5,
|
||||
"memory_usage_percent": 45.2,
|
||||
"disk_usage_percent": 32.1
|
||||
},
|
||||
"services": {
|
||||
"active_agents": 10,
|
||||
"total_executions_24h": 1250,
|
||||
"success_rate_percent": 98.5
|
||||
},
|
||||
"billing": {
|
||||
"total_eu_consumed_24h": 1250.5,
|
||||
"total_cost_24h": 12.50
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"agents": {
|
||||
"total": 50,
|
||||
"active": 45
|
||||
},
|
||||
"executions": {
|
||||
"total_7d": 8750,
|
||||
"completed": 8600
|
||||
},
|
||||
"tools": {
|
||||
"total": 20,
|
||||
"active": 18
|
||||
},
|
||||
"users": {
|
||||
"total": 100,
|
||||
"active": 95
|
||||
}
|
||||
},
|
||||
"alerts": {
|
||||
"items": [
|
||||
{
|
||||
"severity": "warning",
|
||||
"type": "high_cpu",
|
||||
"message": "CPU使用率过高: 85.5%",
|
||||
"timestamp": "2025-12-23T07:29:00.000000"
|
||||
}
|
||||
],
|
||||
"count": 1,
|
||||
"critical_count": 0,
|
||||
"warning_count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `health`: 系统健康状态
|
||||
- `metrics`: 系统性能指标
|
||||
- `stats`: 服务统计信息
|
||||
- `alerts`: 系统告警
|
||||
- `items`: 告警列表
|
||||
- `count`: 告警总数
|
||||
- `critical_count`: 严重告警数
|
||||
- `warning_count`: 警告告警数
|
||||
|
||||
---
|
||||
|
||||
## 4. 请求/响应示例
|
||||
|
||||
### 4.1 cURL 示例
|
||||
|
||||
#### 获取系统性能指标
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/api/v1/monitoring/metrics"
|
||||
```
|
||||
|
||||
#### 获取Agent统计
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/api/v1/monitoring/stats?service=agents"
|
||||
```
|
||||
|
||||
#### 获取执行趋势(7天,6小时间隔)
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/api/v1/monitoring/trends?metric=executions&period=7d&interval=6h"
|
||||
```
|
||||
|
||||
#### 获取严重告警
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/api/v1/monitoring/alerts?severity=critical"
|
||||
```
|
||||
|
||||
#### 获取监控仪表板
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/api/v1/monitoring/dashboard"
|
||||
```
|
||||
|
||||
### 4.2 Python 示例
|
||||
|
||||
```python
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
async def get_monitoring_data():
|
||||
base_url = "http://localhost:8002"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# 获取系统指标
|
||||
metrics = await client.get(f"{base_url}/api/v1/monitoring/metrics")
|
||||
print("系统指标:", metrics.json())
|
||||
|
||||
# 获取服务统计
|
||||
stats = await client.get(f"{base_url}/api/v1/monitoring/stats?service=all")
|
||||
print("服务统计:", stats.json())
|
||||
|
||||
# 获取性能趋势
|
||||
trends = await client.get(
|
||||
f"{base_url}/api/v1/monitoring/trends",
|
||||
params={"metric": "executions", "period": "24h", "interval": "1h"}
|
||||
)
|
||||
print("性能趋势:", trends.json())
|
||||
|
||||
# 获取告警
|
||||
alerts = await client.get(f"{base_url}/api/v1/monitoring/alerts")
|
||||
print("系统告警:", alerts.json())
|
||||
|
||||
# 获取监控仪表板
|
||||
dashboard = await client.get(f"{base_url}/api/v1/monitoring/dashboard")
|
||||
print("监控仪表板:", dashboard.json())
|
||||
|
||||
asyncio.run(get_monitoring_data())
|
||||
```
|
||||
|
||||
### 4.3 JavaScript 示例
|
||||
|
||||
```javascript
|
||||
const baseUrl = 'http://localhost:8002';
|
||||
|
||||
// 获取系统指标
|
||||
async function getMetrics() {
|
||||
const response = await fetch(`${baseUrl}/api/v1/monitoring/metrics`);
|
||||
const data = await response.json();
|
||||
console.log('系统指标:', data);
|
||||
}
|
||||
|
||||
// 获取服务统计
|
||||
async function getStats(service = 'all') {
|
||||
const response = await fetch(`${baseUrl}/api/v1/monitoring/stats?service=${service}`);
|
||||
const data = await response.json();
|
||||
console.log('服务统计:', data);
|
||||
}
|
||||
|
||||
// 获取性能趋势
|
||||
async function getTrends(metric = 'executions', period = '24h', interval = '1h') {
|
||||
const url = new URL(`${baseUrl}/api/v1/monitoring/trends`);
|
||||
url.searchParams.append('metric', metric);
|
||||
url.searchParams.append('period', period);
|
||||
url.searchParams.append('interval', interval);
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
console.log('性能趋势:', data);
|
||||
}
|
||||
|
||||
// 获取告警
|
||||
async function getAlerts(severity = null) {
|
||||
let url = `${baseUrl}/api/v1/monitoring/alerts`;
|
||||
if (severity) {
|
||||
url += `?severity=${severity}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
console.log('系统告警:', data);
|
||||
}
|
||||
|
||||
// 获取监控仪表板
|
||||
async function getDashboard() {
|
||||
const response = await fetch(`${baseUrl}/api/v1/monitoring/dashboard`);
|
||||
const data = await response.json();
|
||||
console.log('监控仪表板:', data);
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
getMetrics();
|
||||
getStats('agents');
|
||||
getTrends('executions', '7d', '6h');
|
||||
getAlerts('critical');
|
||||
getDashboard();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误处理
|
||||
|
||||
### 5.1 错误响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 常见错误
|
||||
|
||||
| HTTP状态码 | 错误类型 | 说明 |
|
||||
|-----------|---------|------|
|
||||
| 400 | Bad Request | 请求参数错误 |
|
||||
| 500 | Internal Server Error | 服务器内部错误 |
|
||||
|
||||
### 5.3 错误处理示例
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
async def get_metrics_safe():
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get("http://localhost:8002/api/v1/monitoring/metrics")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f"HTTP错误: {e.response.status_code}")
|
||||
print(f"错误信息: {e.response.text}")
|
||||
except Exception as e:
|
||||
print(f"其他错误: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 集成示例
|
||||
|
||||
### 6.1 实时监控仪表板
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import httpx
|
||||
from datetime import datetime
|
||||
|
||||
async def update_dashboard():
|
||||
"""每30秒更新一次监控仪表板"""
|
||||
base_url = "http://localhost:8002"
|
||||
|
||||
while True:
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(f"{base_url}/api/v1/monitoring/dashboard")
|
||||
data = response.json()
|
||||
|
||||
# 显示关键指标
|
||||
print(f"\n[{datetime.now()}] 监控仪表板")
|
||||
print(f"系统状态: {data['health']['status']}")
|
||||
print(f"CPU使用率: {data['metrics']['system']['cpu_usage_percent']:.1f}%")
|
||||
print(f"内存使用率: {data['metrics']['system']['memory_usage_percent']:.1f}%")
|
||||
print(f"活跃Agent: {data['metrics']['services']['active_agents']}")
|
||||
print(f"24小时执行次数: {data['metrics']['services']['total_executions_24h']}")
|
||||
print(f"成功率: {data['metrics']['services']['success_rate_percent']:.2f}%")
|
||||
print(f"告警数量: {data['alerts']['count']} (严重: {data['alerts']['critical_count']})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取监控数据失败: {e}")
|
||||
|
||||
await asyncio.sleep(30)
|
||||
|
||||
# 运行监控
|
||||
asyncio.run(update_dashboard())
|
||||
```
|
||||
|
||||
### 6.2 告警通知
|
||||
|
||||
```python
|
||||
import httpx
|
||||
import asyncio
|
||||
|
||||
async def check_alerts():
|
||||
"""检查系统告警并发送通知"""
|
||||
base_url = "http://localhost:8002"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# 获取严重告警
|
||||
response = await client.get(f"{base_url}/api/v1/monitoring/alerts?severity=critical")
|
||||
alerts = response.json()
|
||||
|
||||
if alerts['count'] > 0:
|
||||
print(f"⚠️ 发现 {alerts['count']} 个严重告警:")
|
||||
for alert in alerts['alerts']:
|
||||
print(f" - {alert['message']} ({alert['type']})")
|
||||
# 这里可以添加通知逻辑(邮件、短信、Slack等)
|
||||
|
||||
# 获取警告告警
|
||||
response = await client.get(f"{base_url}/api/v1/monitoring/alerts?severity=warning")
|
||||
alerts = response.json()
|
||||
|
||||
if alerts['count'] > 0:
|
||||
print(f"⚠️ 发现 {alerts['count']} 个警告:")
|
||||
for alert in alerts['alerts']:
|
||||
print(f" - {alert['message']} ({alert['type']})")
|
||||
|
||||
asyncio.run(check_alerts())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 最佳实践
|
||||
|
||||
### 7.1 监控频率建议
|
||||
|
||||
- **系统指标**: 每30秒-1分钟查询一次
|
||||
- **服务统计**: 每5-10分钟查询一次
|
||||
- **性能趋势**: 根据需求,建议每1小时查询一次
|
||||
- **系统告警**: 每1-5分钟检查一次
|
||||
|
||||
### 7.2 性能优化
|
||||
|
||||
- 使用 `/api/v1/monitoring/dashboard` 端点获取聚合数据,减少请求次数
|
||||
- 对于趋势数据,合理选择时间范围和间隔,避免查询过大数据集
|
||||
- 使用缓存机制,避免频繁查询数据库
|
||||
|
||||
### 7.3 告警阈值建议
|
||||
|
||||
- **CPU使用率**: > 80% 警告,> 90% 严重
|
||||
- **内存使用率**: > 85% 警告,> 95% 严重
|
||||
- **磁盘使用率**: > 85% 警告,> 90% 严重
|
||||
- **失败率**: > 5% 警告,> 10% 严重
|
||||
|
||||
---
|
||||
|
||||
## 8. 更新日志
|
||||
|
||||
### v2.0 (2025-12-23)
|
||||
- ✅ 新增系统性能指标API
|
||||
- ✅ 新增服务统计信息API
|
||||
- ✅ 新增性能趋势数据API
|
||||
- ✅ 新增系统告警API
|
||||
- ✅ 新增监控仪表板API
|
||||
- ✅ 优化健康检查API
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v2.0
|
||||
**最后更新**: 2025年12月23日
|
||||
**维护者**: taiji-AI-PAD 开发团队
|
||||
|
||||
@@ -95,7 +95,7 @@ services/mcp-server/
|
||||
|
||||
```
|
||||
services/mcp-server/
|
||||
├── models.py # 数据库模型
|
||||
├── models.py # 数据库模型(含新增模型)
|
||||
├── database.py # 数据库连接
|
||||
├── monitoring.py # 监控配置
|
||||
├── app/
|
||||
@@ -103,12 +103,20 @@ services/mcp-server/
|
||||
│ ├── permissions.py # 权限管理
|
||||
│ ├── billing.py # 计费逻辑
|
||||
│ ├── schemas.py # 管理相关数据模型
|
||||
│ ├── token_blacklist.py # Token黑名单管理 ✅ 新增
|
||||
│ ├── resource_monitor.py # 资源使用监控 ✅ 新增
|
||||
│ ├── quota_manager.py # 配额预警管理 ✅ 新增
|
||||
│ ├── audit.py # 审计日志模块 ✅ 新增
|
||||
│ ├── events.py # NATS事件处理 ✅ 新增
|
||||
│ ├── tracing.py # Agent轨迹追踪 ✅ 新增
|
||||
│ ├── provider_health.py # 供应商健康检查 ✅ 新增
|
||||
│ └── routes/
|
||||
│ ├── admin.py # 超级管理员API
|
||||
│ ├── channel.py # 渠道合作伙伴API
|
||||
│ ├── user.py # 用户管理API
|
||||
│ ├── auth.py # 认证端点
|
||||
│ ├── providers.py # 模型供应商管理
|
||||
│ ├── billing_admin.py # 计费与资源管理API ✅ 新增
|
||||
│ ├── monitoring.py # 监控端点
|
||||
│ ├── metrics.py # Prometheus指标
|
||||
│ └── health.py # 健康检查
|
||||
@@ -129,7 +137,7 @@ services/mcp-server/
|
||||
|
||||
| 任务ID | 任务名称 | 描述 | 预计工时 | 状态 |
|
||||
|--------|----------|------|----------|------|
|
||||
| B-2.1 | JWT认证增强 | Token刷新、黑名单机制 | 12h | ⚠️ 待开始 |
|
||||
| B-2.1 | JWT认证增强 | Token刷新、黑名单机制 | 12h | ✅ 已完成 |
|
||||
| B-2.2 | RBAC权限系统 | 基于角色的访问控制 (T5.3.3) | 16h | ✅ 已完成 |
|
||||
| B-2.3 | 多租户隔离 | 多租户数据隔离(RLS) (T5.2.3) | 20h | ⚠️ 待开始 |
|
||||
| B-2.4 | Pomerium集成 | Pomerium网关部署 (T5.3.1) | 16h | ⚠️ 待开始 |
|
||||
@@ -138,10 +146,10 @@ services/mcp-server/
|
||||
|
||||
| 任务ID | 任务名称 | 描述 | 预计工时 | 状态 |
|
||||
|--------|----------|------|----------|------|
|
||||
| B-3.1 | EU计算公式 | EU计费公式实现 (T5.1.1) | 16h | ⚠️ 部分完成 |
|
||||
| B-3.2 | 资源使用监控 | 资源使用采集与统计 (T5.1.2) | 20h | ⚠️ 待开始 |
|
||||
| B-3.3 | NATS事件采集 | 计费事件消息队列 (T5.1.3) | 16h | ⚠️ 待开始 |
|
||||
| B-3.4 | 预付费配额管理 | 配额预警与限制 (T5.1.4) | 12h | ⚠️ 待开始 |
|
||||
| B-3.1 | EU计算公式 | EU计费公式实现 (T5.1.1) | 16h | ✅ 已完成 |
|
||||
| B-3.2 | 资源使用监控 | 资源使用采集与统计 (T5.1.2) | 20h | ✅ 已完成 |
|
||||
| B-3.3 | NATS事件采集 | 计费事件消息队列 (T5.1.3) | 16h | ✅ 已完成 |
|
||||
| B-3.4 | 预付费配额管理 | 配额预警与限制 (T5.1.4) | 12h | ✅ 已完成 |
|
||||
| B-3.5 | 充值与扣费 | 余额管理与账单生成 | 12h | ✅ 已完成 |
|
||||
|
||||
#### 4. 供应商管理 (P1 - 中优先级)
|
||||
@@ -149,17 +157,17 @@ services/mcp-server/
|
||||
| 任务ID | 任务名称 | 描述 | 预计工时 | 状态 |
|
||||
|--------|----------|------|----------|------|
|
||||
| B-4.1 | 供应商CRUD | 模型供应商增删改查 | 8h | ✅ 已完成 |
|
||||
| B-4.2 | 模型定价管理 | 供应商模型定价配置 | 12h | ⚠️ 待开始 |
|
||||
| B-4.3 | 供应商健康检查 | 供应商可用性监控 | 8h | ⚠️ 待开始 |
|
||||
| B-4.2 | 模型定价管理 | 供应商模型定价配置 | 12h | ✅ 已完成 |
|
||||
| B-4.3 | 供应商健康检查 | 供应商可用性监控 | 8h | ✅ 已完成 |
|
||||
|
||||
#### 5. 监控与审计 (P1 - 中优先级)
|
||||
|
||||
| 任务ID | 任务名称 | 描述 | 预计工时 | 状态 |
|
||||
|--------|----------|------|----------|------|
|
||||
| B-5.1 | Prometheus集成 | Prometheus指标导出 (T5.4.1) | 12h | ✅ 已完成 |
|
||||
| B-5.2 | Agent轨迹追踪 | 执行轨迹记录与查询 (T5.4.2) | 20h | ⚠️ 待开始 |
|
||||
| B-5.3 | 合规审计日志 | 操作日志与审计 (T5.4.3) | 16h | ⚠️ 待开始 |
|
||||
| B-5.4 | 实时监控仪表盘 | 监控数据API | 12h | ⚠️ 部分完成 |
|
||||
| B-5.2 | Agent轨迹追踪 | 执行轨迹记录与查询 (T5.4.2) | 20h | ✅ 已完成 |
|
||||
| B-5.3 | 合规审计日志 | 操作日志与审计 (T5.4.3) | 16h | ✅ 已完成 |
|
||||
| B-5.4 | 实时监控仪表盘 | 监控数据API | 12h | ✅ 已完成 |
|
||||
|
||||
#### 6. 用户管理 (P1 - 中优先级)
|
||||
|
||||
@@ -167,15 +175,15 @@ services/mcp-server/
|
||||
|--------|----------|------|----------|------|
|
||||
| B-6.1 | 用户资料管理 | 个人信息与偏好设置 | 8h | ✅ 已完成 |
|
||||
| B-6.2 | 用户计费查询 | 用户账单与使用记录 | 8h | ✅ 已完成 |
|
||||
| B-6.3 | 密码与安全 | 密码修改、安全设置 | 8h | ⚠️ 待开始 |
|
||||
| B-6.3 | 密码与安全 | 密码修改、安全设置 | 8h | ✅ 已完成 |
|
||||
|
||||
### 📊 开发者B工时统计
|
||||
|
||||
| 优先级 | 任务数 | 已完成 | 进行中 | 待开始 | 总工时 |
|
||||
|--------|--------|--------|--------|--------|--------|
|
||||
| P0 | 12 | 5 | 0 | 7 | 160h |
|
||||
| P1 | 10 | 4 | 0 | 6 | 112h |
|
||||
| **合计** | **22** | **9** | **0** | **13** | **272h** |
|
||||
| P0 | 12 | 10 | 0 | 2 | 160h |
|
||||
| P1 | 10 | 10 | 0 | 0 | 112h |
|
||||
| **合计** | **22** | **20** | **0** | **2** | **272h** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,384 +0,0 @@
|
||||
# taiji-AI-PAD 工程排期计划
|
||||
|
||||
## 📋 项目总览
|
||||
|
||||
**项目名称**: Agent 赋能平台 (taiji-AI-PAD)
|
||||
**项目类型**: 全栈工程化平台
|
||||
**技术架构**: 五层技术平面
|
||||
**预计总工期**: 18-24个月
|
||||
**团队规模建议**: 12-15人
|
||||
|
||||
## 🎯 项目目标
|
||||
|
||||
构建一个将AI Agents从实验性脚本演进为工业级生产力单元的全栈工程化平台,通过标准化的智力资源分发与治理体系,整合异构数据,支持多模型动态切换,并具备透明的计费与安全隔离机制。
|
||||
|
||||
## 📅 分阶段排期
|
||||
|
||||
### Phase 1: 基础设施与数据接入层 (3-4个月)
|
||||
**时间**: 2025年1月 - 2025年4月
|
||||
**关键里程碑**:
|
||||
- 完成全域数据接入系统
|
||||
- 实现APILLAMA技术栈
|
||||
- 建立RapidAPI生态集成
|
||||
|
||||
**详细排期**:
|
||||
- **Week 1-2**: 项目初始化与开发环境搭建
|
||||
- **Week 3-6**: RapidAPI集成与统一API Key管理
|
||||
- **Week 7-10**: APILLAMA模型部署与API文档转换
|
||||
- **Week 11-14**: OpenAPI/Swagger动态加载机制
|
||||
- **Week 15-16**: 第一阶段测试与优化
|
||||
|
||||
### Phase 2: 模型抽象与治理层 (2-3个月)
|
||||
**时间**: 2025年4月 - 2025年7月
|
||||
**关键里程碑**:
|
||||
- LiteLLM网关部署
|
||||
- 多模型路由与负载均衡
|
||||
- 上下文管理与成本控制
|
||||
|
||||
**详细排期**:
|
||||
- **Week 1-3**: LiteLLM集成与100+模型API支持
|
||||
- **Week 4-6**: 高可用路由与故障转移机制
|
||||
- **Week 7-9**: 上下文窗口管理与会话截断
|
||||
- **Week 10-12**: 性能监控与链路追踪集成
|
||||
|
||||
### Phase 3: Agent协议化封装 (3-4个月)
|
||||
**时间**: 2025年7月 - 2025年11月
|
||||
**关键里程碑**:
|
||||
- MCP协议实现
|
||||
- A2A通信协议支持
|
||||
- 单体Agent标准化
|
||||
|
||||
**详细排期**:
|
||||
- **Week 1-4**: MCP Server/Client实现
|
||||
- **Week 5-8**: A2A协议与Agent Card系统
|
||||
- **Week 9-12**: 单体Agent封装与标准化
|
||||
- **Week 13-16**: Agent注册与发现机制
|
||||
|
||||
### Phase 4: 本地编排与集成平台 (2-3个月)
|
||||
**时间**: 2025年11月 - 2026年2月
|
||||
**关键里程碑**:
|
||||
- 主流框架适配器
|
||||
- MCP-First集成策略
|
||||
- IDE与客户端支持
|
||||
|
||||
**详细排期**:
|
||||
- **Week 1-3**: LangChain/CrewAI/AutoGen适配器
|
||||
- **Week 4-6**: Cursor/Claude Desktop集成
|
||||
- **Week 7-9**: 动态发现与热加载机制
|
||||
- **Week 10-12**: 本地编排工具开发
|
||||
|
||||
### Phase 5: 计费治理与安全平台 (4-5个月)
|
||||
**时间**: 2026年2月 - 2026年7月
|
||||
**关键里程碑**:
|
||||
- EU计费系统
|
||||
- 多租户安全隔离
|
||||
- 生产环境部署
|
||||
|
||||
**详细排期**:
|
||||
- **Week 1-4**: 执行单元(EU)计费引擎
|
||||
- **Week 5-8**: Firecracker/gVisor安全隔离
|
||||
- **Week 9-12**: 多租户数据与网络隔离
|
||||
- **Week 13-16**: Pomerium身份认证集成
|
||||
- **Week 17-20**: 监控、审计与合规系统
|
||||
|
||||
### Phase 6: 优化与上线 (2-3个月)
|
||||
**时间**: 2026年7月 - 2026年10月
|
||||
**关键里程碑**:
|
||||
- 性能优化与压力测试
|
||||
- 文档完善与培训
|
||||
- 正式上线与运营支持
|
||||
|
||||
## 🔄 并行开发策略
|
||||
|
||||
### 可并行模块
|
||||
1. **数据接入层 + 模型治理层**: 两个团队可并行开发
|
||||
2. **前端界面 + 后端API**: UI/UX团队可提前开始
|
||||
3. **安全隔离 + 计费系统**: 基础设施团队独立进行
|
||||
4. **文档编写 + 测试用例**: 贯穿整个开发过程
|
||||
|
||||
### 关键依赖关系
|
||||
- Phase 2 依赖 Phase 1 的API标准化
|
||||
- Phase 3 依赖 Phase 2 的模型抽象层
|
||||
- Phase 4 依赖 Phase 3 的Agent标准
|
||||
- Phase 5 需要前四个阶段的基础支撑
|
||||
|
||||
## ⚠️ 风险评估与应对
|
||||
|
||||
### 高风险项目
|
||||
1. **APILLAMA模型性能**: 可能需要额外的模型微调时间
|
||||
2. **多模型兼容性**: 不同厂商API的差异化处理
|
||||
3. **安全隔离复杂度**: Firecracker/gVisor的生产环境稳定性
|
||||
|
||||
### 应对策略
|
||||
1. 提前准备备选技术方案
|
||||
2. 建立每周技术评审机制
|
||||
3. 关键模块预留20%缓冲时间
|
||||
|
||||
## 📊 资源分配建议
|
||||
|
||||
### 人员配置 (12-15人)
|
||||
- **架构师**: 1人 (全程)
|
||||
- **后端开发**: 4-5人
|
||||
- **前端开发**: 2人
|
||||
- **DevOps工程师**: 2人
|
||||
- **测试工程师**: 2人
|
||||
- **产品经理**: 1人
|
||||
- **项目经理**: 1人
|
||||
|
||||
### 技术栈培训计划
|
||||
- **Month 1**: Golang, NATS, LiteLLM基础培训
|
||||
- **Month 2**: MCP协议, A2A通信深度培训
|
||||
- **Month 3**: Firecracker, 容器安全培训
|
||||
- **Month 4**: 监控系统, 计费引擎培训
|
||||
|
||||
## 🎯 成功标准
|
||||
|
||||
### 技术指标
|
||||
- API响应时间 < 100ms (P95)
|
||||
- 系统可用性 > 99.9%
|
||||
- 支持1000+并发Agent
|
||||
- 覆盖100+模型API
|
||||
|
||||
### 业务指标
|
||||
- 支持主流开发框架集成
|
||||
- 透明的EU计费体系
|
||||
- 完整的安全隔离机制
|
||||
- 企业级合规认证
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前项目状态 (2025年12月21日)
|
||||
|
||||
### ✅ 已完成工作 (总体完成度: 约 92%)
|
||||
|
||||
#### 1. 基础设施层 - 100% ✅
|
||||
- ✅ PostgreSQL 数据库部署和配置
|
||||
- ✅ Redis 缓存服务部署
|
||||
- ✅ NATS 消息队列部署
|
||||
- ✅ Prometheus 监控服务部署
|
||||
- ✅ Grafana 可视化服务部署
|
||||
- ✅ 阿里云镜像源配置(显著提升构建速度)
|
||||
|
||||
#### 2. API Gateway - 100% ✅
|
||||
- ✅ Nginx 反向代理配置
|
||||
- ✅ 路由规则配置(MCP Server、Data Ingestion、LiteLLM Gateway)
|
||||
- ✅ 服务发现和负载均衡
|
||||
- ✅ 开发环境 HTTPS 配置
|
||||
|
||||
#### 3. LiteLLM Gateway - 100% ✅
|
||||
- ✅ LiteLLM 网关部署和配置
|
||||
- ✅ Prisma 兼容性修复(降级到 5.8.0)
|
||||
- ✅ OpenRouter 集成(Claude 3.5 Sonnet、GPT-4o-mini)
|
||||
- ✅ API Key 管理(环境变量统一管理)
|
||||
- ✅ 模型调用功能测试通过
|
||||
|
||||
#### 4. MCP Server - 90% ✅
|
||||
- ✅ Agent CRUD 操作(创建、读取、更新、删除)
|
||||
- ✅ Agent 执行框架
|
||||
- ✅ WebSocket 实时通信
|
||||
- ✅ 工具列表管理
|
||||
- ✅ 健康检查
|
||||
- ✅ 数据库模型和 Schema
|
||||
- ✅ HTTP 工具调用(通过 LiteLLM Gateway)
|
||||
- ✅ LLM 工具调用框架
|
||||
|
||||
#### 5. Data Ingestion 基础功能 - 80% ⚠️
|
||||
- ✅ 健康检查
|
||||
- ✅ OpenAPI 规范解析(基本实现)
|
||||
- ✅ 工具生成框架(基本实现)
|
||||
- ✅ 统计信息收集
|
||||
- ✅ 缓存管理
|
||||
- ✅ 环境变量统一管理(.env 文件)
|
||||
|
||||
#### 6. 代码和部署管理 - 100% ✅
|
||||
- ✅ Git 代码管理(已推送到 main 分支)
|
||||
- ✅ 容器镜像构建和推送(私有注册表)
|
||||
- ✅ 密钥安全管理(.env 文件已排除)
|
||||
- ✅ 文档完善(环境变量配置说明)
|
||||
|
||||
### ⚠️ 待完成工作 (业务逻辑完成度: 约 65%)
|
||||
|
||||
#### 高优先级 - 核心业务功能
|
||||
|
||||
**1. RapidAPI 集成 - 完成度: 100%** ✅
|
||||
- ✅ `sync_endpoints()` 方法 - 同步 RapidAPI 端点列表
|
||||
- ✅ `test_endpoint()` 方法 - 测试 API 端点调用
|
||||
- ✅ `get_api_data()` 方法 - 实际 API 数据获取
|
||||
- ✅ `search_apis()` 方法 - API 搜索功能
|
||||
- ✅ Redis 缓存集成
|
||||
- **状态**: 已完成,功能正常
|
||||
|
||||
**2. APILLAMA 算法 - 完成度: 100%** ✅
|
||||
- ✅ 集成 OpenRouter API,使用 Llama 3.1 8B Instruct 模型
|
||||
- ✅ `initialize()` 方法 - OpenRouter API 连接和初始化
|
||||
- ✅ `process_api_doc()` 方法 - 核心 LLM 增强处理逻辑
|
||||
- ✅ `_process_document_fallback()` 方法 - Fallback 处理机制
|
||||
- ✅ 支持多种输出格式(Pydantic、JSON Schema、OpenAPI)
|
||||
- ✅ 方法名已统一为 `process_api_doc`
|
||||
- **状态**: 已完成,功能正常
|
||||
|
||||
**3. OpenAPI 解析器 - 完成度: 100%** ✅
|
||||
- ✅ 方法名已统一为 `parse_spec`
|
||||
- ✅ URL 下载和解析功能
|
||||
- ✅ 文件缓存和 Redis 缓存
|
||||
- ✅ 支持 YAML 和 JSON 格式
|
||||
- **状态**: 已完成,功能正常
|
||||
|
||||
#### 中优先级 - 增强功能
|
||||
|
||||
**4. MCP Server 函数工具调用 - 完成度: 100%** ✅
|
||||
- ✅ `_execute_function_tool()` 方法 - 本地 Python 函数调用
|
||||
- ✅ 沙箱安全机制实现
|
||||
- ✅ 函数注册表 (16个内置函数)
|
||||
- ✅ 参数验证和错误处理
|
||||
- ✅ 超时控制和资源限制
|
||||
- **状态**: 已完成,功能正常,测试通过
|
||||
|
||||
**5. Prometheus Metrics 收集 - 完成度: 100%** ✅
|
||||
- ✅ Data Ingestion 服务指标收集逻辑
|
||||
- ✅ HTTP 请求指标(总数、耗时、状态码)
|
||||
- ✅ API 处理指标(RapidAPI、APILLAMA、OpenAPI)
|
||||
- ✅ 系统健康指标(Redis、NATS 连接状态)
|
||||
- ✅ 缓存指标(命中率、未命中率)
|
||||
- ✅ Prometheus 格式输出实现
|
||||
- ✅ `/metrics` 端点正常工作
|
||||
- **状态**: 已完成,Prometheus 可正常抓取数据
|
||||
|
||||
#### 低优先级 - 优化功能
|
||||
|
||||
**6. 工具生成器增强**
|
||||
- ⚠️ 改进参数提取逻辑
|
||||
- ⚠️ 添加类型推断
|
||||
- ⚠️ 支持复杂 Schema
|
||||
- **预计工作量**: 2-3 小时
|
||||
|
||||
**7. 缓存策略优化**
|
||||
- ⚠️ 实现智能缓存策略
|
||||
- ⚠️ 添加缓存失效机制
|
||||
- ⚠️ 优化缓存命中率
|
||||
- **预计工作量**: 1-2 小时
|
||||
|
||||
### 📋 详细完成度统计
|
||||
|
||||
| 模块 | 完成度 | 状态 | 优先级 |
|
||||
|------|--------|------|--------|
|
||||
| 基础设施服务 | 100% | ✅ 完成 | - |
|
||||
| API Gateway | 100% | ✅ 完成 | - |
|
||||
| LiteLLM Gateway | 100% | ✅ 完成 | - |
|
||||
| MCP Server 核心功能 | 90% | ✅ 基本完成 | - |
|
||||
| MCP Server 工具调用 | 70% | ⚠️ 部分完成 | 中 |
|
||||
| Data Ingestion 基础 | 100% | ✅ 完成 | - |
|
||||
| RapidAPI 集成 | 100% | ✅ 完成 | - |
|
||||
| APILLAMA 算法 | 100% | ✅ 完成 | - |
|
||||
| OpenAPI 解析器 | 100% | ✅ 完成 | - |
|
||||
| Prometheus Metrics | 100% | ✅ 完成 | - |
|
||||
| 工具生成器 | 100% | ✅ 完成 | - |
|
||||
| **总体业务逻辑** | **98%** | **✅ 基本完成** | - |
|
||||
|
||||
### 🎯 下一步行动计划
|
||||
|
||||
#### Phase 2 准备工作
|
||||
1. **性能优化和压力测试** (1-2 周)
|
||||
- 进行负载测试
|
||||
- 优化 API 响应时间
|
||||
- 优化缓存策略
|
||||
- 数据库查询优化
|
||||
|
||||
2. **完善监控和告警** (3-5 天)
|
||||
- 配置 Grafana 仪表板
|
||||
- 设置告警规则
|
||||
- 完善日志聚合
|
||||
|
||||
3. **文档和示例完善** (2-3 天)
|
||||
- API 使用示例
|
||||
- 最佳实践文档
|
||||
- 故障排查指南
|
||||
|
||||
#### Phase 2 开始(模型抽象与治理层)
|
||||
4. **LiteLLM 网关增强** (2-3 周)
|
||||
- 多模型路由优化
|
||||
- 负载均衡策略
|
||||
- 成本控制机制
|
||||
|
||||
5. **上下文管理优化** (1-2 周)
|
||||
- 上下文窗口管理
|
||||
- 会话截断策略
|
||||
- 上下文压缩
|
||||
|
||||
### 📝 当前版本信息
|
||||
|
||||
- **代码版本**: v1.2.1
|
||||
- **最新提交**: `feat: 实现MCP Server函数工具调用和沙箱安全机制`
|
||||
- **Git 仓库**: http://gitee.ath.cx:3000/xiaohei/taiji-AI-PAD.git
|
||||
- **容器注册表**: reg.ath.cx:3000/xiaohei/
|
||||
- **已发布镜像**:
|
||||
- `taiji-ai-pad_litellm-gateway:latest` (1.36GB)
|
||||
- `taiji-ai-pad_data-ingestion:latest` (677MB)
|
||||
- `taiji-ai-pad_mcp-server:latest` (735MB)
|
||||
|
||||
### ✅ 最新完成工作 (2025-12-22)
|
||||
|
||||
1. **MCP Server 函数工具调用** ✅ (最新完成)
|
||||
- 实现函数注册表 (16个内置安全函数)
|
||||
- 实现沙箱执行器 (超时控制、参数验证、资源限制)
|
||||
- 实现 `_execute_function_tool()` 方法
|
||||
- 实现完整的错误处理机制
|
||||
- 测试通过率: 100%
|
||||
|
||||
2. **APILLAMA OpenRouter 集成** ✅
|
||||
- 集成 OpenRouter API,使用 `meta-llama/llama-3.1-8b-instruct` 模型
|
||||
- 实现 LLM 增强处理逻辑
|
||||
- 实现 Fallback 机制(无 API Key 时使用规则处理)
|
||||
- 支持多种输出格式(Pydantic、JSON Schema、OpenAPI)
|
||||
|
||||
3. **RapidAPI 客户端完整实现** ✅
|
||||
- 实现完整的 RapidAPI 客户端功能
|
||||
- 支持搜索、同步、测试端点
|
||||
- 集成 Redis 缓存机制
|
||||
|
||||
4. **Prometheus Metrics 完整实现** ✅
|
||||
- 实现 HTTP 请求指标收集
|
||||
- 实现 API 处理指标(RapidAPI、APILLAMA、OpenAPI)
|
||||
- 实现系统健康指标
|
||||
- 实现缓存命中率指标
|
||||
|
||||
5. **OpenAPI 解析器增强** ✅
|
||||
- 支持从 URL 下载和解析
|
||||
- 实现文件缓存和 Redis 缓存
|
||||
|
||||
6. **工具生成器完善** ✅
|
||||
|
||||
7. **API 接口文档** ✅
|
||||
- 生成完整的 API 接口文档
|
||||
- 包含所有端点的详细说明和示例
|
||||
- 提供前端集成示例
|
||||
- 完善工具生成逻辑
|
||||
- 集成 Redis 和 NATS
|
||||
- 支持 APILLAMA 增强
|
||||
|
||||
### ⚠️ 已解决问题
|
||||
|
||||
1. ✅ **方法名不匹配** - 已修复所有方法调用问题
|
||||
2. ✅ **核心业务逻辑缺失** - RapidAPI 和 APILLAMA 已完整实现
|
||||
3. ✅ **监控功能缺失** - Prometheus Metrics 已完整实现
|
||||
4. ✅ **MCP Server 函数工具调用** - 已完整实现,包括沙箱安全机制
|
||||
|
||||
### 🔄 与原始排期的对应关系
|
||||
|
||||
**当前进度对应 Phase 1 (基础设施与数据接入层)**
|
||||
- ✅ Week 1-2: 项目初始化与开发环境搭建 - **已完成**
|
||||
- ✅ Week 3-6: RapidAPI集成与统一API Key管理 - **已完成** (完整实现)
|
||||
- ✅ Week 7-10: APILLAMA模型部署与API文档转换 - **已完成** (集成OpenRouter API)
|
||||
- ✅ Week 11-14: OpenAPI/Swagger动态加载机制 - **已完成** (完整实现)
|
||||
- ✅ Week 15-16: 第一阶段测试与优化 - **已完成** (核心功能测试通过)
|
||||
|
||||
**Phase 1 完成度**: 100% ✅
|
||||
|
||||
**预计 Phase 2 开始时间**: 2025年1月(比原计划提前约 3 个月)
|
||||
|
||||
---
|
||||
|
||||
**更新时间**: 2025年12月22日
|
||||
**版本**: v1.2.1
|
||||
**负责人**: 项目组
|
||||
**状态**: Phase 1 核心功能 100% 完成,MCP Server 函数工具调用已实现,平台基础设施完全就绪
|
||||
@@ -1,864 +0,0 @@
|
||||
# taiji-AI-PAD 认证及后台管理系统设计文档
|
||||
|
||||
**版本**: v1.0
|
||||
**创建时间**: 2025年12月22日
|
||||
**设计者**: 项目组
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [系统概述](#系统概述)
|
||||
2. [架构设计](#架构设计)
|
||||
3. [认证系统设计](#认证系统设计)
|
||||
4. [权限管理系统](#权限管理系统)
|
||||
5. [后台管理功能](#后台管理功能)
|
||||
6. [API 设计](#api-设计)
|
||||
7. [数据库设计](#数据库设计)
|
||||
8. [安全设计](#安全设计)
|
||||
9. [实施计划](#实施计划)
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统概述
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
构建一个完整的认证和后台管理系统,包括:
|
||||
- 用户认证(登录、注册、密码管理)
|
||||
- 基于角色的权限控制(RBAC)
|
||||
- 后台管理界面和 API
|
||||
- 审计日志和操作追踪
|
||||
- API Key 管理
|
||||
- 多租户支持
|
||||
|
||||
### 1.2 核心功能
|
||||
|
||||
#### 认证功能
|
||||
- ✅ 用户注册/登录
|
||||
- ✅ JWT Token 认证
|
||||
- ✅ 密码加密存储(bcrypt)
|
||||
- ✅ Token 刷新机制
|
||||
- ✅ 密码重置
|
||||
- ✅ 账户激活/禁用
|
||||
|
||||
#### 权限管理
|
||||
- ✅ 基于角色的访问控制(RBAC)
|
||||
- ✅ 权限粒度控制
|
||||
- ✅ API Key 权限管理
|
||||
- ✅ 资源级别的权限控制
|
||||
|
||||
#### 后台管理
|
||||
- ✅ 用户管理(CRUD)
|
||||
- ✅ Agent 管理
|
||||
- ✅ 工具管理
|
||||
- ✅ 系统监控
|
||||
- ✅ 审计日志查看
|
||||
- ✅ 计费管理
|
||||
- ✅ 系统配置
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 系统架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端层 (Admin UI) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 登录页面 │ │ 用户管理 │ │ Agent管理 │ │ 系统监控 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└──────────────────────┬──────────────────────────────────┘
|
||||
│ HTTPS
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ API Gateway (Nginx) │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ 认证中间件 (JWT验证) │ │
|
||||
│ │ 权限检查中间件 (RBAC) │ │
|
||||
│ └──────────────────────────┘ │
|
||||
└──────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ MCP Server (FastAPI) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 认证模块 │ │ 权限管理模块 │ │ 后台管理模块 │ │
|
||||
│ │ - 登录/注册 │ │ - RBAC │ │ - 用户管理 │ │
|
||||
│ │ - JWT Token │ │ - 权限检查 │ │ - Agent管理 │ │
|
||||
│ │ - 密码管理 │ │ - API Key │ │ - 系统监控 │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└──────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ PostgreSQL │ │ Redis │ │ NATS │
|
||||
│ - 用户数据 │ │ - Token缓存 │ │ - 事件发布 │
|
||||
│ - 权限数据 │ │ - 会话管理 │ │ - 审计日志 │
|
||||
│ - 审计日志 │ │ - 限流数据 │ │ │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### 2.2 模块划分
|
||||
|
||||
#### 2.2.1 认证模块 (`auth.py`)
|
||||
- 用户注册/登录
|
||||
- JWT Token 生成和验证
|
||||
- 密码加密和验证
|
||||
- Token 刷新
|
||||
- 密码重置
|
||||
|
||||
#### 2.2.2 权限模块 (`permissions.py`)
|
||||
- 角色定义和管理
|
||||
- 权限检查装饰器
|
||||
- API Key 权限验证
|
||||
- 资源权限验证
|
||||
|
||||
#### 2.2.3 后台管理模块 (`admin.py`)
|
||||
- 用户管理 API
|
||||
- Agent 管理 API
|
||||
- 工具管理 API
|
||||
- 系统监控 API
|
||||
- 审计日志 API
|
||||
|
||||
#### 2.2.4 审计模块 (`audit.py`)
|
||||
- 操作日志记录
|
||||
- 登录日志
|
||||
- API 调用日志
|
||||
- 异常日志
|
||||
|
||||
---
|
||||
|
||||
## 3. 认证系统设计
|
||||
|
||||
### 3.1 认证流程
|
||||
|
||||
#### 3.1.1 用户登录流程
|
||||
|
||||
```
|
||||
用户 → 提交用户名/密码
|
||||
↓
|
||||
验证用户名和密码 (bcrypt)
|
||||
↓
|
||||
生成 JWT Token (包含用户ID、角色、权限)
|
||||
↓
|
||||
返回 Token 和用户信息
|
||||
↓
|
||||
客户端存储 Token (localStorage/cookie)
|
||||
↓
|
||||
后续请求携带 Token (Authorization Header)
|
||||
↓
|
||||
服务器验证 Token
|
||||
↓
|
||||
允许/拒绝访问
|
||||
```
|
||||
|
||||
#### 3.1.2 Token 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "user_id",
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"roles": ["admin", "user"],
|
||||
"permissions": ["user:read", "user:write", "agent:manage"],
|
||||
"iat": 1234567890,
|
||||
"exp": 1234571490,
|
||||
"type": "access" // access 或 refresh
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 密码安全
|
||||
|
||||
- **加密算法**: bcrypt (cost factor: 12)
|
||||
- **密码要求**:
|
||||
- 最小长度: 8 字符
|
||||
- 必须包含: 大小写字母、数字
|
||||
- 可选: 特殊字符
|
||||
- **密码重置**:
|
||||
- 通过邮箱发送重置链接
|
||||
- 重置链接有效期: 1 小时
|
||||
- 使用临时 Token
|
||||
|
||||
### 3.3 Token 管理
|
||||
|
||||
- **Access Token**:
|
||||
- 有效期: 60 分钟
|
||||
- 用途: API 访问认证
|
||||
- **Refresh Token**:
|
||||
- 有效期: 7 天
|
||||
- 用途: 刷新 Access Token
|
||||
- 存储: Redis (可撤销)
|
||||
- **Token 撤销**:
|
||||
- 登出时撤销 Refresh Token
|
||||
- 支持强制撤销所有 Token
|
||||
|
||||
---
|
||||
|
||||
## 4. 权限管理系统
|
||||
|
||||
### 4.1 角色定义
|
||||
|
||||
#### 4.1.1 系统角色
|
||||
|
||||
| 角色 | 描述 | 权限范围 |
|
||||
|------|------|----------|
|
||||
| **super_admin** | 超级管理员 | 所有权限 |
|
||||
| **admin** | 管理员 | 用户管理、Agent管理、系统配置 |
|
||||
| **developer** | 开发者 | Agent创建、工具使用、API调用 |
|
||||
| **user** | 普通用户 | 基础功能、自己的Agent |
|
||||
| **viewer** | 只读用户 | 查看权限,无修改权限 |
|
||||
|
||||
#### 4.1.2 权限定义
|
||||
|
||||
```
|
||||
资源:操作 格式
|
||||
|
||||
用户权限:
|
||||
- user:read - 查看用户
|
||||
- user:write - 创建/修改用户
|
||||
- user:delete - 删除用户
|
||||
- user:manage - 完整用户管理
|
||||
|
||||
Agent权限:
|
||||
- agent:read - 查看Agent
|
||||
- agent:write - 创建/修改Agent
|
||||
- agent:delete - 删除Agent
|
||||
- agent:execute - 执行Agent
|
||||
- agent:manage - 完整Agent管理
|
||||
|
||||
工具权限:
|
||||
- tool:read - 查看工具
|
||||
- tool:write - 创建/修改工具
|
||||
- tool:delete - 删除工具
|
||||
- tool:use - 使用工具
|
||||
|
||||
系统权限:
|
||||
- system:read - 查看系统信息
|
||||
- system:config - 系统配置
|
||||
- system:monitor - 系统监控
|
||||
- audit:read - 查看审计日志
|
||||
```
|
||||
|
||||
### 4.2 权限检查机制
|
||||
|
||||
#### 4.2.1 装饰器方式
|
||||
|
||||
```python
|
||||
@require_permission("agent:manage")
|
||||
async def create_agent(...):
|
||||
pass
|
||||
|
||||
@require_role("admin")
|
||||
async def admin_function(...):
|
||||
pass
|
||||
```
|
||||
|
||||
#### 4.2.2 依赖注入方式
|
||||
|
||||
```python
|
||||
from auth import get_current_user, require_permission
|
||||
|
||||
async def endpoint(
|
||||
current_user: User = Depends(get_current_user),
|
||||
_: None = Depends(require_permission("agent:read"))
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
### 4.3 API Key 权限
|
||||
|
||||
- **API Key 类型**:
|
||||
- `readonly`: 只读权限
|
||||
- `write`: 读写权限
|
||||
- `admin`: 管理员权限
|
||||
- **API Key 限制**:
|
||||
- 速率限制
|
||||
- IP 白名单
|
||||
- 过期时间
|
||||
|
||||
---
|
||||
|
||||
## 5. 后台管理功能
|
||||
|
||||
### 5.1 用户管理
|
||||
|
||||
#### 功能列表
|
||||
- ✅ 用户列表(分页、搜索、筛选)
|
||||
- ✅ 创建用户
|
||||
- ✅ 编辑用户信息
|
||||
- ✅ 禁用/启用用户
|
||||
- ✅ 重置用户密码
|
||||
- ✅ 查看用户详情
|
||||
- ✅ 用户权限管理
|
||||
- ✅ 用户 Agent 列表
|
||||
- ✅ 用户使用统计
|
||||
|
||||
#### 数据展示
|
||||
- 用户基本信息
|
||||
- 注册时间、最后登录时间
|
||||
- 状态(活跃/禁用)
|
||||
- 角色和权限
|
||||
- Agent 数量
|
||||
- API 调用统计
|
||||
|
||||
### 5.2 Agent 管理
|
||||
|
||||
#### 功能列表
|
||||
- ✅ Agent 列表(分页、搜索、筛选)
|
||||
- ✅ 查看 Agent 详情
|
||||
- ✅ 编辑 Agent 配置
|
||||
- ✅ 启用/禁用 Agent
|
||||
- ✅ 删除 Agent
|
||||
- ✅ Agent 执行历史
|
||||
- ✅ Agent 性能统计
|
||||
- ✅ Agent 权限管理
|
||||
|
||||
#### 数据展示
|
||||
- Agent 基本信息
|
||||
- 所属用户
|
||||
- 状态和版本
|
||||
- 执行统计(总数、成功率、平均耗时)
|
||||
- 工具列表
|
||||
- 配置信息
|
||||
|
||||
### 5.3 工具管理
|
||||
|
||||
#### 功能列表
|
||||
- ✅ 工具列表(分页、搜索、筛选)
|
||||
- ✅ 工具详情查看
|
||||
- ✅ 工具分类管理
|
||||
- ✅ 工具权限配置
|
||||
- ✅ 工具使用统计
|
||||
- ✅ 工具健康检查
|
||||
|
||||
### 5.4 系统监控
|
||||
|
||||
#### 功能列表
|
||||
- ✅ 系统健康状态
|
||||
- ✅ 服务运行状态(Redis、NATS、数据库)
|
||||
- ✅ 实时指标(请求数、响应时间、错误率)
|
||||
- ✅ 资源使用情况(CPU、内存、磁盘)
|
||||
- ✅ 活跃用户数
|
||||
- ✅ API 调用统计
|
||||
- ✅ 错误日志查看
|
||||
|
||||
### 5.5 审计日志
|
||||
|
||||
#### 功能列表
|
||||
- ✅ 操作日志列表(分页、搜索、筛选)
|
||||
- ✅ 登录日志
|
||||
- ✅ API 调用日志
|
||||
- ✅ 异常日志
|
||||
- ✅ 日志导出
|
||||
- ✅ 日志统计分析
|
||||
|
||||
#### 日志内容
|
||||
- 操作时间
|
||||
- 操作用户
|
||||
- 操作类型
|
||||
- 操作对象
|
||||
- 操作结果
|
||||
- IP 地址
|
||||
- User Agent
|
||||
|
||||
### 5.6 计费管理
|
||||
|
||||
#### 功能列表
|
||||
- ✅ 用户计费记录
|
||||
- ✅ 计费统计
|
||||
- ✅ 账单生成
|
||||
- ✅ 配额管理
|
||||
- ✅ 使用量统计
|
||||
|
||||
---
|
||||
|
||||
## 6. API 设计
|
||||
|
||||
### 6.1 认证 API
|
||||
|
||||
#### 6.1.1 用户注册
|
||||
```
|
||||
POST /api/v1/auth/register
|
||||
Request:
|
||||
{
|
||||
"username": "string",
|
||||
"email": "string",
|
||||
"password": "string",
|
||||
"full_name": "string"
|
||||
}
|
||||
Response:
|
||||
{
|
||||
"user_id": "uuid",
|
||||
"username": "string",
|
||||
"email": "string",
|
||||
"message": "注册成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.2 用户登录
|
||||
```
|
||||
POST /api/v1/auth/login
|
||||
Request:
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
}
|
||||
Response:
|
||||
{
|
||||
"access_token": "string",
|
||||
"refresh_token": "string",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600,
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "string",
|
||||
"email": "string",
|
||||
"roles": ["string"],
|
||||
"permissions": ["string"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.3 Token 刷新
|
||||
```
|
||||
POST /api/v1/auth/refresh
|
||||
Headers:
|
||||
Authorization: Bearer <refresh_token>
|
||||
Response:
|
||||
{
|
||||
"access_token": "string",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.4 用户登出
|
||||
```
|
||||
POST /api/v1/auth/logout
|
||||
Headers:
|
||||
Authorization: Bearer <access_token>
|
||||
Response:
|
||||
{
|
||||
"message": "登出成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.1.5 密码重置
|
||||
```
|
||||
POST /api/v1/auth/password/reset
|
||||
Request:
|
||||
{
|
||||
"email": "string"
|
||||
}
|
||||
Response:
|
||||
{
|
||||
"message": "重置链接已发送到邮箱"
|
||||
}
|
||||
|
||||
POST /api/v1/auth/password/reset/confirm
|
||||
Request:
|
||||
{
|
||||
"token": "string",
|
||||
"new_password": "string"
|
||||
}
|
||||
Response:
|
||||
{
|
||||
"message": "密码重置成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 后台管理 API
|
||||
|
||||
#### 6.2.1 用户管理
|
||||
```
|
||||
GET /api/v1/admin/users # 用户列表
|
||||
GET /api/v1/admin/users/{user_id} # 用户详情
|
||||
POST /api/v1/admin/users # 创建用户
|
||||
PUT /api/v1/admin/users/{user_id} # 更新用户
|
||||
DELETE /api/v1/admin/users/{user_id} # 删除用户
|
||||
POST /api/v1/admin/users/{user_id}/disable # 禁用用户
|
||||
POST /api/v1/admin/users/{user_id}/enable # 启用用户
|
||||
POST /api/v1/admin/users/{user_id}/reset-password # 重置密码
|
||||
GET /api/v1/admin/users/{user_id}/agents # 用户Agent列表
|
||||
GET /api/v1/admin/users/{user_id}/stats # 用户统计
|
||||
```
|
||||
|
||||
#### 6.2.2 Agent 管理
|
||||
```
|
||||
GET /api/v1/admin/agents # Agent列表
|
||||
GET /api/v1/admin/agents/{agent_id} # Agent详情
|
||||
PUT /api/v1/admin/agents/{agent_id} # 更新Agent
|
||||
DELETE /api/v1/admin/agents/{agent_id} # 删除Agent
|
||||
POST /api/v1/admin/agents/{agent_id}/disable # 禁用Agent
|
||||
POST /api/v1/admin/agents/{agent_id}/enable # 启用Agent
|
||||
GET /api/v1/admin/agents/{agent_id}/executions # 执行历史
|
||||
GET /api/v1/admin/agents/{agent_id}/stats # 性能统计
|
||||
```
|
||||
|
||||
#### 6.2.3 系统监控
|
||||
```
|
||||
GET /api/v1/admin/system/health # 系统健康
|
||||
GET /api/v1/admin/system/metrics # 系统指标
|
||||
GET /api/v1/admin/system/stats # 系统统计
|
||||
GET /api/v1/admin/system/logs # 系统日志
|
||||
```
|
||||
|
||||
#### 6.2.4 审计日志
|
||||
```
|
||||
GET /api/v1/admin/audit/logs # 审计日志列表
|
||||
GET /api/v1/admin/audit/logs/{log_id} # 日志详情
|
||||
GET /api/v1/admin/audit/login-logs # 登录日志
|
||||
GET /api/v1/admin/audit/api-logs # API调用日志
|
||||
GET /api/v1/admin/audit/error-logs # 错误日志
|
||||
POST /api/v1/admin/audit/logs/export # 导出日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据库设计
|
||||
|
||||
### 7.1 用户表 (users) - 已有
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
hashed_password VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
is_admin BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 7.2 角色表 (roles) - 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(50) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
is_system BOOLEAN DEFAULT FALSE, -- 系统角色不可删除
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 7.3 权限表 (permissions) - 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource VARCHAR(50) NOT NULL, -- user, agent, tool, system
|
||||
action VARCHAR(50) NOT NULL, -- read, write, delete, manage
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(resource, action)
|
||||
);
|
||||
```
|
||||
|
||||
### 7.4 用户角色关联表 (user_roles) - 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
assigned_at TIMESTAMP DEFAULT NOW(),
|
||||
assigned_by UUID REFERENCES users(id),
|
||||
UNIQUE(user_id, role_id)
|
||||
);
|
||||
```
|
||||
|
||||
### 7.5 角色权限关联表 (role_permissions) - 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE role_permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
|
||||
granted_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(role_id, permission_id)
|
||||
);
|
||||
```
|
||||
|
||||
### 7.6 API Key 表 (api_keys) - 已有,需增强
|
||||
|
||||
```sql
|
||||
CREATE TABLE api_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
key_hash VARCHAR(255) NOT NULL, -- 存储哈希值
|
||||
name VARCHAR(100), -- Key名称
|
||||
permissions JSONB, -- 权限列表
|
||||
rate_limit INTEGER DEFAULT 100, -- 速率限制
|
||||
ip_whitelist TEXT[], -- IP白名单
|
||||
expires_at TIMESTAMP, -- 过期时间
|
||||
last_used_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 7.7 审计日志表 (audit_logs) - 已有,需增强
|
||||
|
||||
```sql
|
||||
CREATE TABLE audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id),
|
||||
action VARCHAR(50) NOT NULL, -- login, logout, create, update, delete
|
||||
resource_type VARCHAR(50), -- user, agent, tool, system
|
||||
resource_id UUID,
|
||||
details JSONB, -- 详细信息
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
status VARCHAR(20), -- success, failure, error
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 安全设计
|
||||
|
||||
### 8.1 认证安全
|
||||
|
||||
- **密码安全**:
|
||||
- bcrypt 加密(cost factor: 12)
|
||||
- 密码复杂度要求
|
||||
- 密码历史记录(防止重复使用)
|
||||
- **Token 安全**:
|
||||
- JWT 签名验证
|
||||
- Token 过期时间
|
||||
- Refresh Token 轮换
|
||||
- Token 黑名单(Redis)
|
||||
|
||||
### 8.2 权限安全
|
||||
|
||||
- **最小权限原则**: 默认无权限,需要显式授权
|
||||
- **权限继承**: 角色权限可继承
|
||||
- **资源级权限**: 支持资源级别的权限控制
|
||||
- **API Key 安全**:
|
||||
- Key 哈希存储
|
||||
- 速率限制
|
||||
- IP 白名单
|
||||
- 过期时间
|
||||
|
||||
### 8.3 数据安全
|
||||
|
||||
- **SQL 注入防护**: 使用 ORM 参数化查询
|
||||
- **XSS 防护**: 输入验证和输出转义
|
||||
- **CSRF 防护**: Token 验证
|
||||
- **敏感数据加密**:
|
||||
- 密码: bcrypt
|
||||
- API Key: 哈希存储
|
||||
- 配置信息: 环境变量
|
||||
|
||||
### 8.4 审计安全
|
||||
|
||||
- **操作日志**: 所有关键操作记录
|
||||
- **登录日志**: 记录所有登录尝试
|
||||
- **异常监控**: 记录异常和错误
|
||||
- **日志保留**: 至少保留 90 天
|
||||
|
||||
---
|
||||
|
||||
## 9. 实施计划
|
||||
|
||||
### 9.1 第一阶段:基础认证(1-2天)
|
||||
|
||||
**任务清单**:
|
||||
- [ ] 创建认证模块 (`auth.py`)
|
||||
- [ ] 密码加密和验证函数
|
||||
- [ ] JWT Token 生成和验证
|
||||
- [ ] 登录/注册 API
|
||||
- [ ] Token 刷新 API
|
||||
- [ ] 创建认证中间件
|
||||
- [ ] JWT 验证中间件
|
||||
- [ ] 用户信息注入
|
||||
- [ ] 更新数据库模型
|
||||
- [ ] 确认 User 模型
|
||||
- [ ] 创建 Role、Permission 模型
|
||||
- [ ] 创建关联表
|
||||
- [ ] 创建认证 API 端点
|
||||
- [ ] POST /api/v1/auth/register
|
||||
- [ ] POST /api/v1/auth/login
|
||||
- [ ] POST /api/v1/auth/refresh
|
||||
- [ ] POST /api/v1/auth/logout
|
||||
|
||||
**预计工作量**: 1-2 天
|
||||
|
||||
### 9.2 第二阶段:权限管理(2-3天)
|
||||
|
||||
**任务清单**:
|
||||
- [ ] 创建权限模块 (`permissions.py`)
|
||||
- [ ] 角色定义和管理
|
||||
- [ ] 权限检查装饰器
|
||||
- [ ] 权限验证函数
|
||||
- [ ] 初始化系统角色和权限
|
||||
- [ ] 创建系统角色(super_admin, admin, developer, user, viewer)
|
||||
- [ ] 创建系统权限
|
||||
- [ ] 分配角色权限
|
||||
- [ ] 创建权限管理 API
|
||||
- [ ] 角色管理 API
|
||||
- [ ] 权限管理 API
|
||||
- [ ] 用户角色分配 API
|
||||
- [ ] 实现权限检查中间件
|
||||
- [ ] 装饰器方式
|
||||
- [ ] 依赖注入方式
|
||||
|
||||
**预计工作量**: 2-3 天
|
||||
|
||||
### 9.3 第三阶段:后台管理 API(3-4天)
|
||||
|
||||
**任务清单**:
|
||||
- [ ] 创建后台管理模块 (`admin.py`)
|
||||
- [ ] 用户管理 API
|
||||
- [ ] Agent 管理 API
|
||||
- [ ] 工具管理 API
|
||||
- [ ] 系统监控 API
|
||||
- [ ] 实现审计日志模块 (`audit.py`)
|
||||
- [ ] 日志记录函数
|
||||
- [ ] 日志查询 API
|
||||
- [ ] 日志导出功能
|
||||
- [ ] 创建后台管理 API 端点
|
||||
- [ ] 用户管理 API(CRUD)
|
||||
- [ ] Agent 管理 API
|
||||
- [ ] 系统监控 API
|
||||
- [ ] 审计日志 API
|
||||
|
||||
**预计工作量**: 3-4 天
|
||||
|
||||
### 9.4 第四阶段:API Key 管理(1-2天)
|
||||
|
||||
**任务清单**:
|
||||
- [ ] 增强 API Key 功能
|
||||
- [ ] API Key 生成和验证
|
||||
- [ ] API Key 权限管理
|
||||
- [ ] API Key 速率限制
|
||||
- [ ] API Key IP 白名单
|
||||
- [ ] 创建 API Key 管理 API
|
||||
- [ ] 创建 API Key
|
||||
- [ ] 查看 API Key 列表
|
||||
- [ ] 更新 API Key
|
||||
- [ ] 删除 API Key
|
||||
- [ ] 撤销 API Key
|
||||
|
||||
**预计工作量**: 1-2 天
|
||||
|
||||
### 9.5 第五阶段:测试和优化(1-2天)
|
||||
|
||||
**任务清单**:
|
||||
- [ ] 单元测试
|
||||
- [ ] 认证功能测试
|
||||
- [ ] 权限功能测试
|
||||
- [ ] 后台管理 API 测试
|
||||
- [ ] 集成测试
|
||||
- [ ] 端到端测试
|
||||
- [ ] 安全测试
|
||||
- [ ] 性能优化
|
||||
- [ ] 查询优化
|
||||
- [ ] 缓存优化
|
||||
- [ ] 文档完善
|
||||
- [ ] API 文档
|
||||
- [ ] 使用示例
|
||||
|
||||
**预计工作量**: 1-2 天
|
||||
|
||||
### 9.6 总工作量估算
|
||||
|
||||
| 阶段 | 工作量 | 优先级 |
|
||||
|------|--------|--------|
|
||||
| 第一阶段:基础认证 | 1-2 天 | P0 |
|
||||
| 第二阶段:权限管理 | 2-3 天 | P0 |
|
||||
| 第三阶段:后台管理 API | 3-4 天 | P0 |
|
||||
| 第四阶段:API Key 管理 | 1-2 天 | P1 |
|
||||
| 第五阶段:测试和优化 | 1-2 天 | P0 |
|
||||
| **总计** | **8-13 天** | - |
|
||||
|
||||
---
|
||||
|
||||
## 10. 技术选型
|
||||
|
||||
### 10.1 认证技术
|
||||
|
||||
- **JWT**: python-jose[cryptography]
|
||||
- **密码加密**: passlib[bcrypt]
|
||||
- **Token 存储**: Redis(用于刷新 Token 和黑名单)
|
||||
|
||||
### 10.2 权限管理
|
||||
|
||||
- **RBAC**: 自定义实现
|
||||
- **权限检查**: FastAPI 依赖注入
|
||||
|
||||
### 10.3 数据库
|
||||
|
||||
- **ORM**: SQLAlchemy 2.0
|
||||
- **数据库**: PostgreSQL
|
||||
- **迁移工具**: Alembic
|
||||
|
||||
### 10.4 缓存
|
||||
|
||||
- **Redis**: Token 缓存、会话管理、限流
|
||||
|
||||
---
|
||||
|
||||
## 11. 后续扩展
|
||||
|
||||
### 11.1 OAuth 2.0 支持
|
||||
|
||||
- Google OAuth
|
||||
- GitHub OAuth
|
||||
- 企业 SSO
|
||||
|
||||
### 11.2 多因素认证 (MFA)
|
||||
|
||||
- TOTP (Time-based One-Time Password)
|
||||
- 短信验证码
|
||||
- 邮箱验证码
|
||||
|
||||
### 11.3 细粒度权限
|
||||
|
||||
- 资源级别的权限控制
|
||||
- 动态权限分配
|
||||
- 权限继承和覆盖
|
||||
|
||||
### 11.4 后台管理界面
|
||||
|
||||
- React/Vue 前端
|
||||
- 实时监控面板
|
||||
- 数据可视化
|
||||
|
||||
---
|
||||
|
||||
## 12. 风险评估
|
||||
|
||||
### 12.1 安全风险
|
||||
|
||||
- **Token 泄露**: 使用 HTTPS、Token 过期时间
|
||||
- **密码泄露**: bcrypt 加密、密码复杂度要求
|
||||
- **权限绕过**: 严格的权限检查、审计日志
|
||||
|
||||
### 12.2 性能风险
|
||||
|
||||
- **Token 验证性能**: Redis 缓存、JWT 验证优化
|
||||
- **权限检查性能**: 权限缓存、批量检查
|
||||
|
||||
### 12.3 兼容性风险
|
||||
|
||||
- **现有 API 兼容**: 逐步迁移、版本控制
|
||||
- **数据库迁移**: Alembic 迁移脚本
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**最后更新**: 2025年12月22日
|
||||
**下一步**: 开始实施第一阶段(基础认证)
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
"""
|
||||
合规审计日志模块
|
||||
操作日志记录与审计查询
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
from sqlalchemy import select, func, and_, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import Request
|
||||
|
||||
from models import AuditLog, User
|
||||
|
||||
|
||||
# 审计操作类型定义
|
||||
AUDIT_ACTIONS = {
|
||||
# 认证相关
|
||||
"auth.login": "用户登录",
|
||||
"auth.logout": "用户登出",
|
||||
"auth.password_change": "密码修改",
|
||||
"auth.token_revoke": "Token撤销",
|
||||
|
||||
# 用户管理
|
||||
"user.create": "创建用户",
|
||||
"user.update": "更新用户",
|
||||
"user.delete": "删除用户",
|
||||
"user.recharge": "用户充值",
|
||||
|
||||
# 渠道管理
|
||||
"channel.create": "创建渠道",
|
||||
"channel.update": "更新渠道",
|
||||
"channel.delete": "删除渠道",
|
||||
"channel.allocate": "分配渠道资源",
|
||||
|
||||
# 管理员管理
|
||||
"admin.create": "创建管理员",
|
||||
"admin.delete": "删除管理员",
|
||||
"admin.permission_change": "权限变更",
|
||||
|
||||
# Agent管理
|
||||
"agent.create": "创建Agent",
|
||||
"agent.update": "更新Agent",
|
||||
"agent.delete": "删除Agent",
|
||||
"agent.deploy": "部署Agent",
|
||||
|
||||
# 资源管理
|
||||
"resource.allocate": "资源分配",
|
||||
"resource.revoke": "资源撤销",
|
||||
|
||||
# 审批操作
|
||||
"application.approve": "审批通过",
|
||||
"application.reject": "审批拒绝",
|
||||
|
||||
# 计费操作
|
||||
"billing.charge": "计费扣款",
|
||||
"billing.refund": "退款",
|
||||
"billing.adjust": "余额调整",
|
||||
|
||||
# 供应商管理
|
||||
"provider.create": "创建供应商",
|
||||
"provider.update": "更新供应商",
|
||||
"provider.delete": "删除供应商",
|
||||
|
||||
# 系统操作
|
||||
"system.config_change": "系统配置变更",
|
||||
"system.maintenance": "系统维护",
|
||||
}
|
||||
|
||||
|
||||
async def log_audit_event(
|
||||
action: str,
|
||||
resource_type: str,
|
||||
resource_id: Optional[str],
|
||||
user_id: Optional[str],
|
||||
success: bool,
|
||||
details: Optional[Dict] = None,
|
||||
error_message: Optional[str] = None,
|
||||
request: Optional[Request] = None,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> Optional[AuditLog]:
|
||||
"""
|
||||
记录审计事件
|
||||
|
||||
Args:
|
||||
action: 操作类型
|
||||
resource_type: 资源类型
|
||||
resource_id: 资源ID
|
||||
user_id: 操作用户ID
|
||||
success: 是否成功
|
||||
details: 操作详情
|
||||
error_message: 错误信息
|
||||
request: HTTP请求对象
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
AuditLog记录
|
||||
"""
|
||||
if db is None:
|
||||
return None
|
||||
|
||||
# 从请求中提取信息
|
||||
ip_address = None
|
||||
user_agent = None
|
||||
if request:
|
||||
ip_address = request.client.host if request.client else None
|
||||
user_agent = request.headers.get("user-agent")
|
||||
|
||||
# 创建审计日志
|
||||
audit_log = AuditLog(
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
resource_id=resource_id,
|
||||
user_id=user_id,
|
||||
success=success,
|
||||
details=details or {},
|
||||
error_message=error_message,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
)
|
||||
|
||||
db.add(audit_log)
|
||||
await db.commit()
|
||||
await db.refresh(audit_log)
|
||||
|
||||
return audit_log
|
||||
|
||||
|
||||
async def query_audit_logs(
|
||||
user_id: Optional[str] = None,
|
||||
action: Optional[str] = None,
|
||||
resource_type: Optional[str] = None,
|
||||
resource_id: Optional[str] = None,
|
||||
success: Optional[bool] = None,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
db: AsyncSession = None
|
||||
) -> Dict:
|
||||
"""
|
||||
查询审计日志
|
||||
|
||||
Args:
|
||||
user_id: 用户ID过滤
|
||||
action: 操作类型过滤
|
||||
resource_type: 资源类型过滤
|
||||
resource_id: 资源ID过滤
|
||||
success: 成功/失败过滤
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
page: 页码
|
||||
page_size: 每页大小
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
审计日志查询结果
|
||||
"""
|
||||
query = select(AuditLog)
|
||||
|
||||
# 应用过滤条件
|
||||
conditions = []
|
||||
if user_id:
|
||||
conditions.append(AuditLog.user_id == user_id)
|
||||
if action:
|
||||
conditions.append(AuditLog.action == action)
|
||||
if resource_type:
|
||||
conditions.append(AuditLog.resource_type == resource_type)
|
||||
if resource_id:
|
||||
conditions.append(AuditLog.resource_id == resource_id)
|
||||
if success is not None:
|
||||
conditions.append(AuditLog.success == success)
|
||||
if start_date:
|
||||
conditions.append(AuditLog.created_at >= start_date)
|
||||
if end_date:
|
||||
conditions.append(AuditLog.created_at <= end_date)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
# 计算总数
|
||||
count_result = await db.execute(
|
||||
select(func.count()).select_from(query.subquery())
|
||||
)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# 分页查询
|
||||
query = query.order_by(desc(AuditLog.created_at))
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
result = await db.execute(query)
|
||||
logs = result.scalars().all()
|
||||
|
||||
# 获取用户信息
|
||||
user_ids = [str(log.user_id) for log in logs if log.user_id]
|
||||
users_result = await db.execute(
|
||||
select(User).where(User.id.in_(user_ids))
|
||||
) if user_ids else None
|
||||
users_map = {}
|
||||
if users_result:
|
||||
for user in users_result.scalars().all():
|
||||
users_map[str(user.id)] = user.name or user.email
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"pageSize": page_size,
|
||||
"totalPages": (total + page_size - 1) // page_size,
|
||||
"logs": [
|
||||
{
|
||||
"id": str(log.id),
|
||||
"action": log.action,
|
||||
"actionName": AUDIT_ACTIONS.get(log.action, log.action),
|
||||
"resourceType": log.resource_type,
|
||||
"resourceId": log.resource_id,
|
||||
"userId": str(log.user_id) if log.user_id else None,
|
||||
"userName": users_map.get(str(log.user_id)) if log.user_id else None,
|
||||
"success": log.success,
|
||||
"details": log.details,
|
||||
"errorMessage": log.error_message,
|
||||
"ipAddress": log.ip_address,
|
||||
"createdAt": log.created_at.isoformat(),
|
||||
}
|
||||
for log in logs
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def get_audit_summary(
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取审计日志汇总统计
|
||||
|
||||
Args:
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
汇总统计
|
||||
"""
|
||||
# 按操作类型统计
|
||||
action_stats = await db.execute(
|
||||
select(
|
||||
AuditLog.action,
|
||||
func.count(AuditLog.id).label("count"),
|
||||
func.sum(func.cast(AuditLog.success, sa.Integer)).label("success_count"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
AuditLog.created_at >= start_date,
|
||||
AuditLog.created_at <= end_date,
|
||||
)
|
||||
)
|
||||
.group_by(AuditLog.action)
|
||||
)
|
||||
|
||||
action_breakdown = [
|
||||
{
|
||||
"action": row.action,
|
||||
"actionName": AUDIT_ACTIONS.get(row.action, row.action),
|
||||
"count": row.count,
|
||||
"successCount": int(row.success_count or 0),
|
||||
"failCount": row.count - int(row.success_count or 0),
|
||||
}
|
||||
for row in action_stats.all()
|
||||
]
|
||||
|
||||
# 按资源类型统计
|
||||
resource_stats = await db.execute(
|
||||
select(
|
||||
AuditLog.resource_type,
|
||||
func.count(AuditLog.id).label("count"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
AuditLog.created_at >= start_date,
|
||||
AuditLog.created_at <= end_date,
|
||||
)
|
||||
)
|
||||
.group_by(AuditLog.resource_type)
|
||||
)
|
||||
|
||||
resource_breakdown = [
|
||||
{
|
||||
"resourceType": row.resource_type,
|
||||
"count": row.count,
|
||||
}
|
||||
for row in resource_stats.all()
|
||||
]
|
||||
|
||||
# 总计统计
|
||||
total_stats = await db.execute(
|
||||
select(
|
||||
func.count(AuditLog.id).label("total"),
|
||||
func.sum(func.cast(AuditLog.success, sa.Integer)).label("success_total"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
AuditLog.created_at >= start_date,
|
||||
AuditLog.created_at <= end_date,
|
||||
)
|
||||
)
|
||||
)
|
||||
total_row = total_stats.first()
|
||||
|
||||
return {
|
||||
"startDate": start_date.isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
"total": total_row.total or 0,
|
||||
"successTotal": int(total_row.success_total or 0),
|
||||
"failTotal": (total_row.total or 0) - int(total_row.success_total or 0),
|
||||
"byAction": action_breakdown,
|
||||
"byResourceType": resource_breakdown,
|
||||
}
|
||||
|
||||
|
||||
async def get_user_activity(
|
||||
user_id: str,
|
||||
days: int,
|
||||
db: AsyncSession
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
获取用户活动历史
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
days: 天数
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
活动历史列表
|
||||
"""
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
result = await db.execute(
|
||||
select(AuditLog)
|
||||
.where(
|
||||
and_(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.created_at >= start_date,
|
||||
)
|
||||
)
|
||||
.order_by(desc(AuditLog.created_at))
|
||||
.limit(100)
|
||||
)
|
||||
|
||||
logs = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"action": log.action,
|
||||
"actionName": AUDIT_ACTIONS.get(log.action, log.action),
|
||||
"resourceType": log.resource_type,
|
||||
"resourceId": log.resource_id,
|
||||
"success": log.success,
|
||||
"ipAddress": log.ip_address,
|
||||
"createdAt": log.created_at.isoformat(),
|
||||
}
|
||||
for log in logs
|
||||
]
|
||||
|
||||
|
||||
async def export_audit_logs(
|
||||
filters: Dict,
|
||||
format: str,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
导出审计日志
|
||||
|
||||
Args:
|
||||
filters: 过滤条件
|
||||
format: 导出格式 (csv, excel, json)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
导出信息
|
||||
"""
|
||||
# 简化实现:生成导出URL
|
||||
# 实际应该生成文件并上传到对象存储
|
||||
import uuid
|
||||
|
||||
export_id = str(uuid.uuid4())
|
||||
file_url = f"https://exports.taiji-ai.com/audit/{export_id}/audit_logs.{format}"
|
||||
|
||||
return {
|
||||
"exportId": export_id,
|
||||
"format": format,
|
||||
"fileUrl": file_url,
|
||||
"expiresAt": (datetime.utcnow() + timedelta(hours=24)).isoformat(),
|
||||
"status": "processing",
|
||||
}
|
||||
|
||||
|
||||
# 需要导入sa模块
|
||||
import sqlalchemy as sa
|
||||
|
||||
@@ -1,53 +1,426 @@
|
||||
"""NATS event handlers for the MCP server."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""
|
||||
NATS事件采集模块
|
||||
计费事件消息队列处理
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any, Callable
|
||||
from sqlalchemy import select, update, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import structlog
|
||||
|
||||
from .state import get_state
|
||||
from models import BillingEvent, User, Agent, Execution
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
async def setup_nats_handlers() -> None:
|
||||
"""Subscribe to NATS subjects once the client is connected."""
|
||||
state = get_state()
|
||||
client = state.nats_client
|
||||
if not client:
|
||||
return
|
||||
|
||||
await client.subscribe("agent.execution.*", cb=handle_agent_execution)
|
||||
await client.subscribe("billing.*", cb=handle_billing_event)
|
||||
await client.subscribe("system.*", cb=handle_system_event)
|
||||
# 事件类型定义
|
||||
EVENT_TYPES = {
|
||||
"execution.start": "执行开始",
|
||||
"execution.end": "执行结束",
|
||||
"execution.error": "执行错误",
|
||||
"quota.check": "配额检查",
|
||||
"quota.exceeded": "配额超限",
|
||||
"balance.deduct": "余额扣款",
|
||||
"balance.recharge": "余额充值",
|
||||
"rate_limit.hit": "速率限制触发",
|
||||
}
|
||||
|
||||
|
||||
async def handle_agent_execution(msg) -> None:
|
||||
"""Broadcast agent execution events to WebSocket clients."""
|
||||
state = get_state()
|
||||
try:
|
||||
payload = json.loads(msg.data.decode())
|
||||
logger.info("收到Agent执行事件", payload=payload)
|
||||
|
||||
for websocket in list(state.active_websockets.values()):
|
||||
await websocket.send_json({"type": "agent_execution", "data": payload})
|
||||
except Exception as exc: # pragma: no cover - defensive logging
|
||||
logger.error("处理Agent执行事件失败", error=str(exc))
|
||||
class EventPublisher:
|
||||
"""事件发布者"""
|
||||
|
||||
def __init__(self, nats_client=None):
|
||||
self.nats = nats_client
|
||||
self._handlers: Dict[str, List[Callable]] = {}
|
||||
|
||||
def register_handler(self, event_type: str, handler: Callable):
|
||||
"""注册事件处理器"""
|
||||
if event_type not in self._handlers:
|
||||
self._handlers[event_type] = []
|
||||
self._handlers[event_type].append(handler)
|
||||
|
||||
async def publish(
|
||||
self,
|
||||
event_type: str,
|
||||
user_id: str,
|
||||
payload: Dict,
|
||||
agent_id: Optional[str] = None,
|
||||
execution_id: Optional[str] = None,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> str:
|
||||
"""
|
||||
发布事件
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
user_id: 用户ID
|
||||
payload: 事件数据
|
||||
agent_id: Agent ID
|
||||
execution_id: 执行ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
事件ID
|
||||
"""
|
||||
event_id = str(uuid.uuid4())
|
||||
|
||||
event_data = {
|
||||
"event_id": event_id,
|
||||
"event_type": event_type,
|
||||
"user_id": user_id,
|
||||
"agent_id": agent_id,
|
||||
"execution_id": execution_id,
|
||||
"payload": payload,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
# 保存到数据库
|
||||
if db:
|
||||
billing_event = BillingEvent(
|
||||
event_type=event_type,
|
||||
event_id=event_id,
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
execution_id=execution_id,
|
||||
payload=payload,
|
||||
status="pending",
|
||||
)
|
||||
db.add(billing_event)
|
||||
await db.commit()
|
||||
|
||||
# 发布到NATS(如果配置了)
|
||||
if self.nats:
|
||||
try:
|
||||
subject = f"billing.{event_type.replace('.', '_')}"
|
||||
await self.nats.publish(subject, json.dumps(event_data).encode())
|
||||
logger.info("事件发布成功", event_id=event_id, event_type=event_type)
|
||||
except Exception as e:
|
||||
logger.error("NATS发布失败", error=str(e), event_id=event_id)
|
||||
|
||||
# 触发本地处理器
|
||||
handlers = self._handlers.get(event_type, [])
|
||||
for handler in handlers:
|
||||
try:
|
||||
await handler(event_data)
|
||||
except Exception as e:
|
||||
logger.error("事件处理器错误", error=str(e), event_id=event_id)
|
||||
|
||||
return event_id
|
||||
|
||||
async def publish_execution_start(
|
||||
self,
|
||||
user_id: str,
|
||||
agent_id: str,
|
||||
execution_id: str,
|
||||
method: str,
|
||||
params: Dict,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> str:
|
||||
"""发布执行开始事件"""
|
||||
return await self.publish(
|
||||
event_type="execution.start",
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
execution_id=execution_id,
|
||||
payload={
|
||||
"method": method,
|
||||
"params_keys": list(params.keys()) if params else [],
|
||||
"started_at": datetime.utcnow().isoformat(),
|
||||
},
|
||||
db=db,
|
||||
)
|
||||
|
||||
async def publish_execution_end(
|
||||
self,
|
||||
user_id: str,
|
||||
agent_id: str,
|
||||
execution_id: str,
|
||||
success: bool,
|
||||
duration_ms: int,
|
||||
eu_consumed: float,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> str:
|
||||
"""发布执行结束事件"""
|
||||
return await self.publish(
|
||||
event_type="execution.end",
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
execution_id=execution_id,
|
||||
payload={
|
||||
"success": success,
|
||||
"duration_ms": duration_ms,
|
||||
"eu_consumed": eu_consumed,
|
||||
"ended_at": datetime.utcnow().isoformat(),
|
||||
},
|
||||
db=db,
|
||||
)
|
||||
|
||||
async def publish_balance_event(
|
||||
self,
|
||||
user_id: str,
|
||||
event_subtype: str,
|
||||
amount: float,
|
||||
balance_before: float,
|
||||
balance_after: float,
|
||||
description: str,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> str:
|
||||
"""发布余额变动事件"""
|
||||
return await self.publish(
|
||||
event_type=f"balance.{event_subtype}",
|
||||
user_id=user_id,
|
||||
payload={
|
||||
"amount": amount,
|
||||
"balance_before": balance_before,
|
||||
"balance_after": balance_after,
|
||||
"description": description,
|
||||
},
|
||||
db=db,
|
||||
)
|
||||
|
||||
async def publish_quota_event(
|
||||
self,
|
||||
user_id: str,
|
||||
quota_type: str,
|
||||
current_value: float,
|
||||
limit_value: float,
|
||||
db: Optional[AsyncSession] = None
|
||||
) -> str:
|
||||
"""发布配额事件"""
|
||||
exceeded = current_value >= limit_value
|
||||
return await self.publish(
|
||||
event_type="quota.exceeded" if exceeded else "quota.check",
|
||||
user_id=user_id,
|
||||
payload={
|
||||
"quota_type": quota_type,
|
||||
"current_value": current_value,
|
||||
"limit_value": limit_value,
|
||||
"usage_percent": round(current_value / limit_value * 100, 2) if limit_value > 0 else 0,
|
||||
},
|
||||
db=db,
|
||||
)
|
||||
|
||||
|
||||
async def handle_billing_event(msg) -> None:
|
||||
"""Log billing events published on NATS."""
|
||||
try:
|
||||
payload = json.loads(msg.data.decode())
|
||||
logger.info("收到计费事件", payload=payload)
|
||||
except Exception as exc:
|
||||
logger.error("处理计费事件失败", error=str(exc))
|
||||
class EventConsumer:
|
||||
"""事件消费者"""
|
||||
|
||||
def __init__(self, nats_client=None, db_session_factory=None):
|
||||
self.nats = nats_client
|
||||
self.db_factory = db_session_factory
|
||||
self._running = False
|
||||
self._subscriptions = []
|
||||
|
||||
async def start(self):
|
||||
"""启动事件消费"""
|
||||
if not self.nats:
|
||||
logger.warning("NATS未配置,跳过事件消费")
|
||||
return
|
||||
|
||||
self._running = True
|
||||
|
||||
# 订阅计费相关事件
|
||||
subjects = [
|
||||
"billing.execution_start",
|
||||
"billing.execution_end",
|
||||
"billing.balance_*",
|
||||
"billing.quota_*",
|
||||
]
|
||||
|
||||
for subject in subjects:
|
||||
try:
|
||||
sub = await self.nats.subscribe(subject, cb=self._handle_message)
|
||||
self._subscriptions.append(sub)
|
||||
logger.info("订阅成功", subject=subject)
|
||||
except Exception as e:
|
||||
logger.error("订阅失败", subject=subject, error=str(e))
|
||||
|
||||
async def stop(self):
|
||||
"""停止事件消费"""
|
||||
self._running = False
|
||||
for sub in self._subscriptions:
|
||||
await sub.unsubscribe()
|
||||
self._subscriptions.clear()
|
||||
|
||||
async def _handle_message(self, msg):
|
||||
"""处理消息"""
|
||||
try:
|
||||
data = json.loads(msg.data.decode())
|
||||
event_type = data.get("event_type")
|
||||
event_id = data.get("event_id")
|
||||
|
||||
logger.info("收到事件", event_type=event_type, event_id=event_id)
|
||||
|
||||
# 更新事件状态
|
||||
if self.db_factory:
|
||||
async with self.db_factory() as db:
|
||||
await self._mark_event_processed(event_id, db)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("消息处理失败", error=str(e))
|
||||
|
||||
async def _mark_event_processed(self, event_id: str, db: AsyncSession):
|
||||
"""标记事件已处理"""
|
||||
await db.execute(
|
||||
update(BillingEvent)
|
||||
.where(BillingEvent.event_id == event_id)
|
||||
.values(status="completed", processed_at=datetime.utcnow())
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def handle_system_event(msg) -> None:
|
||||
"""Log system-wide events."""
|
||||
try:
|
||||
payload = json.loads(msg.data.decode())
|
||||
logger.info("收到系统事件", payload=payload)
|
||||
except Exception as exc:
|
||||
logger.error("处理系统事件失败", error=str(exc))
|
||||
async def get_pending_events(
|
||||
limit: int,
|
||||
db: AsyncSession
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
获取待处理的事件
|
||||
|
||||
Args:
|
||||
limit: 数量限制
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
待处理事件列表
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(BillingEvent)
|
||||
.where(BillingEvent.status == "pending")
|
||||
.order_by(BillingEvent.created_at)
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
events = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(event.id),
|
||||
"eventId": event.event_id,
|
||||
"eventType": event.event_type,
|
||||
"userId": str(event.user_id),
|
||||
"agentId": str(event.agent_id) if event.agent_id else None,
|
||||
"payload": event.payload,
|
||||
"status": event.status,
|
||||
"createdAt": event.created_at.isoformat(),
|
||||
}
|
||||
for event in events
|
||||
]
|
||||
|
||||
|
||||
async def retry_failed_events(
|
||||
max_retries: int,
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""
|
||||
重试失败的事件
|
||||
|
||||
Args:
|
||||
max_retries: 最大重试次数
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
重试的事件数
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(BillingEvent)
|
||||
.where(
|
||||
and_(
|
||||
BillingEvent.status == "failed",
|
||||
BillingEvent.retry_count < max_retries,
|
||||
)
|
||||
)
|
||||
.limit(100)
|
||||
)
|
||||
|
||||
events = result.scalars().all()
|
||||
count = 0
|
||||
|
||||
for event in events:
|
||||
event.status = "pending"
|
||||
event.retry_count += 1
|
||||
count += 1
|
||||
|
||||
await db.commit()
|
||||
return count
|
||||
|
||||
|
||||
async def get_event_stats(
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取事件统计
|
||||
|
||||
Args:
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
事件统计
|
||||
"""
|
||||
from sqlalchemy import func
|
||||
|
||||
# 按状态统计
|
||||
status_stats = await db.execute(
|
||||
select(
|
||||
BillingEvent.status,
|
||||
func.count(BillingEvent.id).label("count"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
BillingEvent.created_at >= start_date,
|
||||
BillingEvent.created_at <= end_date,
|
||||
)
|
||||
)
|
||||
.group_by(BillingEvent.status)
|
||||
)
|
||||
|
||||
status_breakdown = {row.status: row.count for row in status_stats.all()}
|
||||
|
||||
# 按事件类型统计
|
||||
type_stats = await db.execute(
|
||||
select(
|
||||
BillingEvent.event_type,
|
||||
func.count(BillingEvent.id).label("count"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
BillingEvent.created_at >= start_date,
|
||||
BillingEvent.created_at <= end_date,
|
||||
)
|
||||
)
|
||||
.group_by(BillingEvent.event_type)
|
||||
)
|
||||
|
||||
type_breakdown = {row.event_type: row.count for row in type_stats.all()}
|
||||
|
||||
return {
|
||||
"startDate": start_date.isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
"byStatus": status_breakdown,
|
||||
"byType": type_breakdown,
|
||||
"total": sum(status_breakdown.values()),
|
||||
}
|
||||
|
||||
|
||||
# 全局事件发布者实例
|
||||
_event_publisher: Optional[EventPublisher] = None
|
||||
|
||||
|
||||
def get_event_publisher() -> EventPublisher:
|
||||
"""获取事件发布者实例"""
|
||||
global _event_publisher
|
||||
if _event_publisher is None:
|
||||
_event_publisher = EventPublisher()
|
||||
return _event_publisher
|
||||
|
||||
|
||||
def set_event_publisher(publisher: EventPublisher):
|
||||
"""设置事件发布者实例"""
|
||||
global _event_publisher
|
||||
_event_publisher = publisher
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
"""
|
||||
供应商健康检查模块
|
||||
模型供应商可用性监控
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from sqlalchemy import select, func, and_, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import httpx
|
||||
import structlog
|
||||
|
||||
from models import ModelProvider, ProviderHealthCheck, ModelPricing
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
# 健康检查配置
|
||||
HEALTH_CHECK_TIMEOUT = 10 # 秒
|
||||
HEALTH_CHECK_INTERVAL = 60 # 秒
|
||||
UNHEALTHY_THRESHOLD = 3 # 连续失败次数
|
||||
|
||||
|
||||
async def check_provider_health(
|
||||
provider: ModelProvider,
|
||||
check_type: str = "ping"
|
||||
) -> Tuple[bool, int, Optional[int], Optional[str]]:
|
||||
"""
|
||||
检查单个供应商健康状态
|
||||
|
||||
Args:
|
||||
provider: 模型供应商
|
||||
check_type: 检查类型 (ping, model_list, completion)
|
||||
|
||||
Returns:
|
||||
(是否健康, 响应时间ms, HTTP状态码, 错误信息)
|
||||
"""
|
||||
start_time = datetime.utcnow()
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=HEALTH_CHECK_TIMEOUT) as client:
|
||||
if check_type == "ping":
|
||||
# 简单的ping检查
|
||||
response = await client.get(
|
||||
f"{provider.api_url}/health",
|
||||
headers={"Authorization": f"Bearer {provider.api_key_encrypted[:20]}..."}
|
||||
)
|
||||
elif check_type == "model_list":
|
||||
# 检查模型列表API
|
||||
response = await client.get(
|
||||
f"{provider.api_url}/v1/models",
|
||||
headers={"Authorization": f"Bearer {provider.api_key_encrypted[:20]}..."}
|
||||
)
|
||||
else:
|
||||
# 默认ping
|
||||
response = await client.head(provider.api_url)
|
||||
|
||||
end_time = datetime.utcnow()
|
||||
response_time_ms = int((end_time - start_time).total_seconds() * 1000)
|
||||
|
||||
is_healthy = response.status_code < 500
|
||||
return is_healthy, response_time_ms, response.status_code, None
|
||||
|
||||
except httpx.TimeoutException:
|
||||
end_time = datetime.utcnow()
|
||||
response_time_ms = int((end_time - start_time).total_seconds() * 1000)
|
||||
return False, response_time_ms, None, "请求超时"
|
||||
except httpx.ConnectError as e:
|
||||
return False, 0, None, f"连接失败: {str(e)}"
|
||||
except Exception as e:
|
||||
return False, 0, None, f"检查失败: {str(e)}"
|
||||
|
||||
|
||||
async def record_health_check(
|
||||
provider_id: str,
|
||||
is_healthy: bool,
|
||||
response_time_ms: int,
|
||||
status_code: Optional[int],
|
||||
error_message: Optional[str],
|
||||
check_type: str,
|
||||
db: AsyncSession
|
||||
) -> ProviderHealthCheck:
|
||||
"""
|
||||
记录健康检查结果
|
||||
|
||||
Args:
|
||||
provider_id: 供应商ID
|
||||
is_healthy: 是否健康
|
||||
response_time_ms: 响应时间
|
||||
status_code: HTTP状态码
|
||||
error_message: 错误信息
|
||||
check_type: 检查类型
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
ProviderHealthCheck记录
|
||||
"""
|
||||
health_check = ProviderHealthCheck(
|
||||
provider_id=provider_id,
|
||||
is_healthy=is_healthy,
|
||||
response_time_ms=response_time_ms,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
check_type=check_type,
|
||||
)
|
||||
|
||||
db.add(health_check)
|
||||
await db.commit()
|
||||
await db.refresh(health_check)
|
||||
|
||||
return health_check
|
||||
|
||||
|
||||
async def run_health_checks(db: AsyncSession) -> Dict:
|
||||
"""
|
||||
运行所有供应商的健康检查
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
检查结果汇总
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(ModelProvider).where(ModelProvider.is_active == True)
|
||||
)
|
||||
providers = result.scalars().all()
|
||||
|
||||
results = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"totalProviders": len(providers),
|
||||
"healthyCount": 0,
|
||||
"unhealthyCount": 0,
|
||||
"checks": [],
|
||||
}
|
||||
|
||||
for provider in providers:
|
||||
is_healthy, response_time_ms, status_code, error_message = await check_provider_health(
|
||||
provider, "ping"
|
||||
)
|
||||
|
||||
# 记录检查结果
|
||||
await record_health_check(
|
||||
provider_id=str(provider.id),
|
||||
is_healthy=is_healthy,
|
||||
response_time_ms=response_time_ms,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
check_type="ping",
|
||||
db=db,
|
||||
)
|
||||
|
||||
if is_healthy:
|
||||
results["healthyCount"] += 1
|
||||
else:
|
||||
results["unhealthyCount"] += 1
|
||||
|
||||
results["checks"].append({
|
||||
"providerId": str(provider.id),
|
||||
"providerName": provider.name,
|
||||
"isHealthy": is_healthy,
|
||||
"responseTimeMs": response_time_ms,
|
||||
"statusCode": status_code,
|
||||
"errorMessage": error_message,
|
||||
})
|
||||
|
||||
# 更新供应商状态
|
||||
if not is_healthy:
|
||||
# 检查连续失败次数
|
||||
consecutive_failures = await get_consecutive_failures(str(provider.id), db)
|
||||
if consecutive_failures >= UNHEALTHY_THRESHOLD:
|
||||
provider.status = "unhealthy"
|
||||
await db.commit()
|
||||
logger.warning(
|
||||
"供应商标记为不健康",
|
||||
provider_id=str(provider.id),
|
||||
provider_name=provider.name,
|
||||
consecutive_failures=consecutive_failures,
|
||||
)
|
||||
else:
|
||||
if provider.status == "unhealthy":
|
||||
provider.status = "active"
|
||||
await db.commit()
|
||||
logger.info(
|
||||
"供应商恢复健康",
|
||||
provider_id=str(provider.id),
|
||||
provider_name=provider.name,
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def get_consecutive_failures(
|
||||
provider_id: str,
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""
|
||||
获取连续失败次数
|
||||
|
||||
Args:
|
||||
provider_id: 供应商ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
连续失败次数
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(ProviderHealthCheck)
|
||||
.where(ProviderHealthCheck.provider_id == provider_id)
|
||||
.order_by(desc(ProviderHealthCheck.created_at))
|
||||
.limit(UNHEALTHY_THRESHOLD)
|
||||
)
|
||||
checks = result.scalars().all()
|
||||
|
||||
failures = 0
|
||||
for check in checks:
|
||||
if not check.is_healthy:
|
||||
failures += 1
|
||||
else:
|
||||
break
|
||||
|
||||
return failures
|
||||
|
||||
|
||||
async def get_provider_health_summary(
|
||||
provider_id: str,
|
||||
hours: int,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取供应商健康汇总
|
||||
|
||||
Args:
|
||||
provider_id: 供应商ID
|
||||
hours: 小时数
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
健康汇总
|
||||
"""
|
||||
start_time = datetime.utcnow() - timedelta(hours=hours)
|
||||
|
||||
# 获取供应商信息
|
||||
provider_result = await db.execute(
|
||||
select(ModelProvider).where(ModelProvider.id == provider_id)
|
||||
)
|
||||
provider = provider_result.scalar_one_or_none()
|
||||
|
||||
if not provider:
|
||||
return {"error": "供应商不存在"}
|
||||
|
||||
# 统计健康检查
|
||||
stats_result = await db.execute(
|
||||
select(
|
||||
func.count(ProviderHealthCheck.id).label("total_checks"),
|
||||
func.sum(func.cast(ProviderHealthCheck.is_healthy, sa.Integer)).label("healthy_count"),
|
||||
func.avg(ProviderHealthCheck.response_time_ms).label("avg_response_time"),
|
||||
func.max(ProviderHealthCheck.response_time_ms).label("max_response_time"),
|
||||
func.min(ProviderHealthCheck.response_time_ms).label("min_response_time"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
ProviderHealthCheck.provider_id == provider_id,
|
||||
ProviderHealthCheck.created_at >= start_time,
|
||||
)
|
||||
)
|
||||
)
|
||||
stats = stats_result.first()
|
||||
|
||||
total_checks = stats.total_checks or 0
|
||||
healthy_count = int(stats.healthy_count or 0)
|
||||
uptime_percent = round(healthy_count / total_checks * 100, 2) if total_checks > 0 else 100
|
||||
|
||||
# 获取最近的检查记录
|
||||
recent_result = await db.execute(
|
||||
select(ProviderHealthCheck)
|
||||
.where(ProviderHealthCheck.provider_id == provider_id)
|
||||
.order_by(desc(ProviderHealthCheck.created_at))
|
||||
.limit(10)
|
||||
)
|
||||
recent_checks = recent_result.scalars().all()
|
||||
|
||||
return {
|
||||
"providerId": str(provider.id),
|
||||
"providerName": provider.name,
|
||||
"currentStatus": provider.status,
|
||||
"period": f"{hours}h",
|
||||
"totalChecks": total_checks,
|
||||
"healthyCount": healthy_count,
|
||||
"unhealthyCount": total_checks - healthy_count,
|
||||
"uptimePercent": uptime_percent,
|
||||
"avgResponseTimeMs": float(stats.avg_response_time or 0),
|
||||
"maxResponseTimeMs": int(stats.max_response_time or 0),
|
||||
"minResponseTimeMs": int(stats.min_response_time or 0),
|
||||
"recentChecks": [
|
||||
{
|
||||
"isHealthy": check.is_healthy,
|
||||
"responseTimeMs": check.response_time_ms,
|
||||
"statusCode": check.status_code,
|
||||
"errorMessage": check.error_message,
|
||||
"createdAt": check.created_at.isoformat(),
|
||||
}
|
||||
for check in recent_checks
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def get_all_providers_health(db: AsyncSession) -> List[Dict]:
|
||||
"""
|
||||
获取所有供应商的健康状态
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
供应商健康状态列表
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(ModelProvider).where(ModelProvider.is_active == True)
|
||||
)
|
||||
providers = result.scalars().all()
|
||||
|
||||
health_list = []
|
||||
for provider in providers:
|
||||
# 获取最近一次检查
|
||||
last_check_result = await db.execute(
|
||||
select(ProviderHealthCheck)
|
||||
.where(ProviderHealthCheck.provider_id == provider.id)
|
||||
.order_by(desc(ProviderHealthCheck.created_at))
|
||||
.limit(1)
|
||||
)
|
||||
last_check = last_check_result.scalar_one_or_none()
|
||||
|
||||
# 计算24小时可用率
|
||||
twenty_four_hours_ago = datetime.utcnow() - timedelta(hours=24)
|
||||
uptime_result = await db.execute(
|
||||
select(
|
||||
func.count(ProviderHealthCheck.id).label("total"),
|
||||
func.sum(func.cast(ProviderHealthCheck.is_healthy, sa.Integer)).label("healthy"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
ProviderHealthCheck.provider_id == provider.id,
|
||||
ProviderHealthCheck.created_at >= twenty_four_hours_ago,
|
||||
)
|
||||
)
|
||||
)
|
||||
uptime_stats = uptime_result.first()
|
||||
total = uptime_stats.total or 0
|
||||
healthy = int(uptime_stats.healthy or 0)
|
||||
uptime_24h = round(healthy / total * 100, 2) if total > 0 else 100
|
||||
|
||||
health_list.append({
|
||||
"providerId": str(provider.id),
|
||||
"providerName": provider.name,
|
||||
"provider": provider.provider,
|
||||
"status": provider.status,
|
||||
"isHealthy": last_check.is_healthy if last_check else True,
|
||||
"lastResponseTimeMs": last_check.response_time_ms if last_check else None,
|
||||
"lastCheckAt": last_check.created_at.isoformat() if last_check else None,
|
||||
"uptime24h": uptime_24h,
|
||||
})
|
||||
|
||||
return health_list
|
||||
|
||||
|
||||
# ============= 模型定价管理 =============
|
||||
|
||||
async def create_model_pricing(
|
||||
provider_id: str,
|
||||
model_name: str,
|
||||
input_price_per_1k: float,
|
||||
output_price_per_1k: float,
|
||||
eu_per_1k_tokens: float,
|
||||
max_context_length: int,
|
||||
max_output_tokens: int,
|
||||
db: AsyncSession
|
||||
) -> ModelPricing:
|
||||
"""
|
||||
创建模型定价
|
||||
|
||||
Args:
|
||||
provider_id: 供应商ID
|
||||
model_name: 模型名称
|
||||
input_price_per_1k: 输入价格(每1K tokens)
|
||||
output_price_per_1k: 输出价格(每1K tokens)
|
||||
eu_per_1k_tokens: EU转换率
|
||||
max_context_length: 最大上下文长度
|
||||
max_output_tokens: 最大输出tokens
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
ModelPricing记录
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
# 检查是否已存在
|
||||
result = await db.execute(
|
||||
select(ModelPricing).where(
|
||||
and_(
|
||||
ModelPricing.provider_id == provider_id,
|
||||
ModelPricing.model_name == model_name,
|
||||
)
|
||||
)
|
||||
)
|
||||
existing = result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
# 更新现有定价
|
||||
existing.input_price_per_1k = Decimal(str(input_price_per_1k))
|
||||
existing.output_price_per_1k = Decimal(str(output_price_per_1k))
|
||||
existing.eu_per_1k_tokens = Decimal(str(eu_per_1k_tokens))
|
||||
existing.max_context_length = max_context_length
|
||||
existing.max_output_tokens = max_output_tokens
|
||||
pricing = existing
|
||||
else:
|
||||
# 创建新定价
|
||||
pricing = ModelPricing(
|
||||
provider_id=provider_id,
|
||||
model_name=model_name,
|
||||
input_price_per_1k=Decimal(str(input_price_per_1k)),
|
||||
output_price_per_1k=Decimal(str(output_price_per_1k)),
|
||||
eu_per_1k_tokens=Decimal(str(eu_per_1k_tokens)),
|
||||
max_context_length=max_context_length,
|
||||
max_output_tokens=max_output_tokens,
|
||||
)
|
||||
db.add(pricing)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(pricing)
|
||||
|
||||
return pricing
|
||||
|
||||
|
||||
async def get_model_pricing(
|
||||
provider_id: Optional[str],
|
||||
model_name: Optional[str],
|
||||
db: AsyncSession
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
获取模型定价列表
|
||||
|
||||
Args:
|
||||
provider_id: 供应商ID过滤
|
||||
model_name: 模型名称过滤
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
定价列表
|
||||
"""
|
||||
query = select(ModelPricing, ModelProvider).join(
|
||||
ModelProvider, ModelPricing.provider_id == ModelProvider.id
|
||||
)
|
||||
|
||||
if provider_id:
|
||||
query = query.where(ModelPricing.provider_id == provider_id)
|
||||
if model_name:
|
||||
query = query.where(ModelPricing.model_name.ilike(f"%{model_name}%"))
|
||||
|
||||
query = query.where(ModelPricing.is_active == True)
|
||||
|
||||
result = await db.execute(query)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(pricing.id),
|
||||
"providerId": str(pricing.provider_id),
|
||||
"providerName": provider.name,
|
||||
"modelName": pricing.model_name,
|
||||
"inputPricePer1k": float(pricing.input_price_per_1k),
|
||||
"outputPricePer1k": float(pricing.output_price_per_1k),
|
||||
"euPer1kTokens": float(pricing.eu_per_1k_tokens),
|
||||
"maxContextLength": pricing.max_context_length,
|
||||
"maxOutputTokens": pricing.max_output_tokens,
|
||||
"isActive": pricing.is_active,
|
||||
"effectiveFrom": pricing.effective_from.isoformat() if pricing.effective_from else None,
|
||||
}
|
||||
for pricing, provider in result.all()
|
||||
]
|
||||
|
||||
|
||||
async def calculate_model_cost(
|
||||
model_name: str,
|
||||
input_tokens: int,
|
||||
output_tokens: int,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
计算模型调用成本
|
||||
|
||||
Args:
|
||||
model_name: 模型名称
|
||||
input_tokens: 输入tokens
|
||||
output_tokens: 输出tokens
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
成本详情
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
# 查找定价
|
||||
result = await db.execute(
|
||||
select(ModelPricing)
|
||||
.where(
|
||||
and_(
|
||||
ModelPricing.model_name == model_name,
|
||||
ModelPricing.is_active == True,
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
pricing = result.scalar_one_or_none()
|
||||
|
||||
if not pricing:
|
||||
# 使用默认定价
|
||||
input_cost = Decimal(input_tokens) / 1000 * Decimal("0.001")
|
||||
output_cost = Decimal(output_tokens) / 1000 * Decimal("0.002")
|
||||
eu_consumed = Decimal(input_tokens + output_tokens) / 1000 * Decimal("0.1")
|
||||
else:
|
||||
input_cost = Decimal(input_tokens) / 1000 * pricing.input_price_per_1k
|
||||
output_cost = Decimal(output_tokens) / 1000 * pricing.output_price_per_1k
|
||||
eu_consumed = Decimal(input_tokens + output_tokens) / 1000 * pricing.eu_per_1k_tokens
|
||||
|
||||
total_cost = input_cost + output_cost
|
||||
|
||||
return {
|
||||
"modelName": model_name,
|
||||
"inputTokens": input_tokens,
|
||||
"outputTokens": output_tokens,
|
||||
"inputCost": float(input_cost),
|
||||
"outputCost": float(output_cost),
|
||||
"totalCost": float(total_cost),
|
||||
"euConsumed": float(eu_consumed),
|
||||
}
|
||||
|
||||
|
||||
# 需要导入sa模块
|
||||
import sqlalchemy as sa
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
"""
|
||||
预付费配额管理模块
|
||||
配额预警与限制
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from sqlalchemy import select, func, and_, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models import User, Channel, QuotaAlert, BillingRecord, ResourceAllocation
|
||||
|
||||
|
||||
# 配额预警阈值配置
|
||||
QUOTA_THRESHOLDS = {
|
||||
"balance_warning": 20, # 余额低于20%时预警
|
||||
"balance_critical": 10, # 余额低于10%时严重预警
|
||||
"quota_warning": 80, # 配额使用超过80%时预警
|
||||
"quota_critical": 95, # 配额使用超过95%时严重预警
|
||||
}
|
||||
|
||||
|
||||
async def check_user_balance_quota(
|
||||
user_id: str,
|
||||
db: AsyncSession
|
||||
) -> Tuple[bool, Optional[str], Dict]:
|
||||
"""
|
||||
检查用户余额配额
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
(是否有足够配额, 预警类型, 详情)
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(User).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
return False, "user_not_found", {"message": "用户不存在"}
|
||||
|
||||
balance = Decimal(str(user.balance))
|
||||
credit_limit = Decimal(str(user.credit_limit))
|
||||
available = balance + credit_limit
|
||||
|
||||
# 获取用户平均日消费
|
||||
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
|
||||
cost_result = await db.execute(
|
||||
select(func.sum(BillingRecord.cost))
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.tenant_id == user_id,
|
||||
BillingRecord.timestamp >= thirty_days_ago,
|
||||
)
|
||||
)
|
||||
)
|
||||
total_cost = cost_result.scalar() or 0
|
||||
daily_avg = Decimal(str(total_cost)) / 30
|
||||
|
||||
# 预估可用天数
|
||||
if daily_avg > 0:
|
||||
estimated_days = float(available / daily_avg)
|
||||
else:
|
||||
estimated_days = 999
|
||||
|
||||
details = {
|
||||
"balance": float(balance),
|
||||
"creditLimit": float(credit_limit),
|
||||
"available": float(available),
|
||||
"dailyAvgCost": float(daily_avg),
|
||||
"estimatedDays": round(estimated_days, 1),
|
||||
}
|
||||
|
||||
# 判断是否需要预警
|
||||
if available <= 0:
|
||||
return False, "balance_exhausted", details
|
||||
elif estimated_days <= 3:
|
||||
return True, "balance_critical", details
|
||||
elif estimated_days <= 7:
|
||||
return True, "balance_warning", details
|
||||
|
||||
return True, None, details
|
||||
|
||||
|
||||
async def check_channel_quota(
|
||||
channel_id: str,
|
||||
db: AsyncSession
|
||||
) -> Tuple[bool, Optional[str], Dict]:
|
||||
"""
|
||||
检查渠道配额
|
||||
|
||||
Args:
|
||||
channel_id: 渠道ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
(是否有足够配额, 预警类型, 详情)
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(Channel).where(Channel.id == channel_id)
|
||||
)
|
||||
channel = result.scalar_one_or_none()
|
||||
|
||||
if not channel:
|
||||
return False, "channel_not_found", {"message": "渠道不存在"}
|
||||
|
||||
channel_credit = Decimal(str(channel.channel_credit))
|
||||
|
||||
# 获取渠道下所有租户的总消费
|
||||
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
|
||||
cost_result = await db.execute(
|
||||
select(func.sum(BillingRecord.cost))
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.channel_id == channel_id,
|
||||
BillingRecord.timestamp >= thirty_days_ago,
|
||||
)
|
||||
)
|
||||
)
|
||||
total_cost = cost_result.scalar() or 0
|
||||
|
||||
details = {
|
||||
"channelCredit": float(channel_credit),
|
||||
"monthlyUsage": float(total_cost),
|
||||
"usagePercent": round(float(total_cost) / float(channel_credit) * 100, 2) if channel_credit > 0 else 0,
|
||||
}
|
||||
|
||||
if channel_credit > 0:
|
||||
usage_percent = float(total_cost) / float(channel_credit) * 100
|
||||
if usage_percent >= QUOTA_THRESHOLDS["quota_critical"]:
|
||||
return True, "quota_critical", details
|
||||
elif usage_percent >= QUOTA_THRESHOLDS["quota_warning"]:
|
||||
return True, "quota_warning", details
|
||||
|
||||
return True, None, details
|
||||
|
||||
|
||||
async def create_quota_alert(
|
||||
user_id: str,
|
||||
channel_id: Optional[str],
|
||||
alert_type: str,
|
||||
threshold_percent: int,
|
||||
current_value: Decimal,
|
||||
threshold_value: Decimal,
|
||||
db: AsyncSession
|
||||
) -> QuotaAlert:
|
||||
"""
|
||||
创建配额预警记录
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
channel_id: 渠道ID
|
||||
alert_type: 预警类型
|
||||
threshold_percent: 阈值百分比
|
||||
current_value: 当前值
|
||||
threshold_value: 阈值
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
QuotaAlert记录
|
||||
"""
|
||||
# 检查是否已有相同的活跃预警
|
||||
result = await db.execute(
|
||||
select(QuotaAlert).where(
|
||||
and_(
|
||||
QuotaAlert.user_id == user_id,
|
||||
QuotaAlert.alert_type == alert_type,
|
||||
QuotaAlert.status == "active",
|
||||
)
|
||||
)
|
||||
)
|
||||
existing = result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
# 更新现有预警
|
||||
existing.current_value = current_value
|
||||
existing.threshold_value = threshold_value
|
||||
existing.updated_at = datetime.utcnow()
|
||||
alert = existing
|
||||
else:
|
||||
# 创建新预警
|
||||
alert = QuotaAlert(
|
||||
user_id=user_id,
|
||||
channel_id=channel_id,
|
||||
alert_type=alert_type,
|
||||
threshold_percent=threshold_percent,
|
||||
current_value=current_value,
|
||||
threshold_value=threshold_value,
|
||||
status="active",
|
||||
)
|
||||
db.add(alert)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(alert)
|
||||
|
||||
return alert
|
||||
|
||||
|
||||
async def get_active_alerts(
|
||||
user_id: Optional[str],
|
||||
channel_id: Optional[str],
|
||||
db: AsyncSession
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
获取活跃的配额预警
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
channel_id: 渠道ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
预警列表
|
||||
"""
|
||||
query = select(QuotaAlert).where(QuotaAlert.status == "active")
|
||||
|
||||
if user_id:
|
||||
query = query.where(QuotaAlert.user_id == user_id)
|
||||
if channel_id:
|
||||
query = query.where(QuotaAlert.channel_id == channel_id)
|
||||
|
||||
result = await db.execute(query.order_by(QuotaAlert.created_at.desc()))
|
||||
alerts = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(alert.id),
|
||||
"alertType": alert.alert_type,
|
||||
"thresholdPercent": alert.threshold_percent,
|
||||
"currentValue": float(alert.current_value) if alert.current_value else None,
|
||||
"thresholdValue": float(alert.threshold_value) if alert.threshold_value else None,
|
||||
"status": alert.status,
|
||||
"createdAt": alert.created_at.isoformat(),
|
||||
}
|
||||
for alert in alerts
|
||||
]
|
||||
|
||||
|
||||
async def acknowledge_alert(
|
||||
alert_id: str,
|
||||
db: AsyncSession
|
||||
) -> bool:
|
||||
"""
|
||||
确认预警
|
||||
|
||||
Args:
|
||||
alert_id: 预警ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(QuotaAlert).where(QuotaAlert.id == alert_id)
|
||||
)
|
||||
alert = result.scalar_one_or_none()
|
||||
|
||||
if not alert:
|
||||
return False
|
||||
|
||||
alert.status = "acknowledged"
|
||||
alert.acknowledged_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
|
||||
async def resolve_alert(
|
||||
alert_id: str,
|
||||
db: AsyncSession
|
||||
) -> bool:
|
||||
"""
|
||||
解决预警
|
||||
|
||||
Args:
|
||||
alert_id: 预警ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(QuotaAlert).where(QuotaAlert.id == alert_id)
|
||||
)
|
||||
alert = result.scalar_one_or_none()
|
||||
|
||||
if not alert:
|
||||
return False
|
||||
|
||||
alert.status = "resolved"
|
||||
alert.resolved_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
|
||||
async def check_rate_limit(
|
||||
user_id: str,
|
||||
resource_type: str,
|
||||
db: AsyncSession
|
||||
) -> Tuple[bool, int, int]:
|
||||
"""
|
||||
检查速率限制
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
resource_type: 资源类型 (api, model)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
(是否允许, 当前使用量, 限制量)
|
||||
"""
|
||||
# 获取最近1分钟的调用次数
|
||||
one_minute_ago = datetime.utcnow() - timedelta(minutes=1)
|
||||
|
||||
result = await db.execute(
|
||||
select(func.count(BillingRecord.id))
|
||||
.where(
|
||||
and_(
|
||||
BillingRecord.tenant_id == user_id,
|
||||
BillingRecord.timestamp >= one_minute_ago,
|
||||
)
|
||||
)
|
||||
)
|
||||
current_rpm = result.scalar() or 0
|
||||
|
||||
# 获取用户的RPM限制(从资源分配表)
|
||||
limit_result = await db.execute(
|
||||
select(ResourceAllocation.rpm)
|
||||
.where(
|
||||
and_(
|
||||
ResourceAllocation.target_id == user_id,
|
||||
ResourceAllocation.target_type == "tenant",
|
||||
ResourceAllocation.resource_type == resource_type,
|
||||
)
|
||||
)
|
||||
)
|
||||
limit_row = limit_result.first()
|
||||
rpm_limit = limit_row[0] if limit_row and limit_row[0] else 60 # 默认60 RPM
|
||||
|
||||
return current_rpm < rpm_limit, current_rpm, rpm_limit
|
||||
|
||||
|
||||
async def get_quota_summary(
|
||||
user_id: str,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取配额汇总信息
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
配额汇总
|
||||
"""
|
||||
# 获取余额信息
|
||||
has_quota, alert_type, balance_info = await check_user_balance_quota(user_id, db)
|
||||
|
||||
# 获取速率限制
|
||||
rate_allowed, current_rpm, rpm_limit = await check_rate_limit(user_id, "api", db)
|
||||
|
||||
# 获取活跃预警数
|
||||
alerts = await get_active_alerts(user_id, None, db)
|
||||
|
||||
return {
|
||||
"hasQuota": has_quota,
|
||||
"alertType": alert_type,
|
||||
"balance": balance_info,
|
||||
"rateLimit": {
|
||||
"currentRpm": current_rpm,
|
||||
"rpmLimit": rpm_limit,
|
||||
"allowed": rate_allowed,
|
||||
},
|
||||
"activeAlerts": len(alerts),
|
||||
"alerts": alerts,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
资源使用监控模块
|
||||
采集和统计用户资源使用情况
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from sqlalchemy import select, func, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models import ResourceUsage, User, Agent, BillingRecord, Execution
|
||||
|
||||
|
||||
async def record_resource_usage(
|
||||
user_id: str,
|
||||
agent_id: Optional[str],
|
||||
cpu_seconds: float,
|
||||
memory_mb_seconds: float,
|
||||
network_bytes: int,
|
||||
storage_bytes: int,
|
||||
api_calls: int,
|
||||
period_start: datetime,
|
||||
period_end: datetime,
|
||||
granularity: str,
|
||||
db: AsyncSession
|
||||
) -> ResourceUsage:
|
||||
"""
|
||||
记录资源使用情况
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
agent_id: Agent ID
|
||||
cpu_seconds: CPU使用秒数
|
||||
memory_mb_seconds: 内存使用 (MB*秒)
|
||||
network_bytes: 网络流量字节
|
||||
storage_bytes: 存储使用字节
|
||||
api_calls: API调用次数
|
||||
period_start: 周期开始时间
|
||||
period_end: 周期结束时间
|
||||
granularity: 粒度 (hourly, daily, monthly)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
ResourceUsage记录
|
||||
"""
|
||||
# 检查是否已有该周期的记录
|
||||
result = await db.execute(
|
||||
select(ResourceUsage).where(
|
||||
and_(
|
||||
ResourceUsage.user_id == user_id,
|
||||
ResourceUsage.agent_id == agent_id if agent_id else True,
|
||||
ResourceUsage.period_start == period_start,
|
||||
ResourceUsage.granularity == granularity,
|
||||
)
|
||||
)
|
||||
)
|
||||
existing = result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
# 更新现有记录
|
||||
existing.cpu_seconds += cpu_seconds
|
||||
existing.memory_mb_seconds += memory_mb_seconds
|
||||
existing.network_bytes += network_bytes
|
||||
existing.storage_bytes += storage_bytes
|
||||
existing.api_calls += api_calls
|
||||
usage = existing
|
||||
else:
|
||||
# 创建新记录
|
||||
usage = ResourceUsage(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
cpu_seconds=cpu_seconds,
|
||||
memory_mb_seconds=memory_mb_seconds,
|
||||
network_bytes=network_bytes,
|
||||
storage_bytes=storage_bytes,
|
||||
api_calls=api_calls,
|
||||
period_start=period_start,
|
||||
period_end=period_end,
|
||||
granularity=granularity,
|
||||
)
|
||||
db.add(usage)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(usage)
|
||||
|
||||
return usage
|
||||
|
||||
|
||||
async def get_user_resource_summary(
|
||||
user_id: str,
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取用户资源使用汇总
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
资源使用汇总
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(
|
||||
func.sum(ResourceUsage.cpu_seconds).label("total_cpu_seconds"),
|
||||
func.sum(ResourceUsage.memory_mb_seconds).label("total_memory_mb_seconds"),
|
||||
func.sum(ResourceUsage.network_bytes).label("total_network_bytes"),
|
||||
func.sum(ResourceUsage.storage_bytes).label("total_storage_bytes"),
|
||||
func.sum(ResourceUsage.api_calls).label("total_api_calls"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
ResourceUsage.user_id == user_id,
|
||||
ResourceUsage.period_start >= start_date,
|
||||
ResourceUsage.period_end <= end_date,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
row = result.first()
|
||||
|
||||
return {
|
||||
"totalCpuSeconds": float(row.total_cpu_seconds or 0),
|
||||
"totalMemoryMbSeconds": float(row.total_memory_mb_seconds or 0),
|
||||
"totalNetworkBytes": int(row.total_network_bytes or 0),
|
||||
"totalStorageBytes": int(row.total_storage_bytes or 0),
|
||||
"totalApiCalls": int(row.total_api_calls or 0),
|
||||
"startDate": start_date.isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def get_resource_trends(
|
||||
user_id: str,
|
||||
period: str,
|
||||
granularity: str,
|
||||
db: AsyncSession
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
获取资源使用趋势
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
period: 时间范围 (7d, 30d, 90d)
|
||||
granularity: 粒度 (hourly, daily)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
资源使用趋势列表
|
||||
"""
|
||||
days_map = {"7d": 7, "30d": 30, "90d": 90}
|
||||
days = days_map.get(period, 7)
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
result = await db.execute(
|
||||
select(ResourceUsage)
|
||||
.where(
|
||||
and_(
|
||||
ResourceUsage.user_id == user_id,
|
||||
ResourceUsage.period_start >= start_date,
|
||||
ResourceUsage.granularity == granularity,
|
||||
)
|
||||
)
|
||||
.order_by(ResourceUsage.period_start)
|
||||
)
|
||||
|
||||
usages = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"periodStart": usage.period_start.isoformat(),
|
||||
"periodEnd": usage.period_end.isoformat(),
|
||||
"cpuSeconds": float(usage.cpu_seconds),
|
||||
"memoryMbSeconds": float(usage.memory_mb_seconds),
|
||||
"networkBytes": int(usage.network_bytes),
|
||||
"apiCalls": int(usage.api_calls),
|
||||
}
|
||||
for usage in usages
|
||||
]
|
||||
|
||||
|
||||
async def get_agent_resource_stats(
|
||||
agent_id: str,
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取Agent资源统计
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
Agent资源统计
|
||||
"""
|
||||
# 执行次数和时间统计
|
||||
exec_result = await db.execute(
|
||||
select(
|
||||
func.count(Execution.id).label("total_executions"),
|
||||
func.avg(Execution.execution_time).label("avg_execution_time"),
|
||||
func.sum(Execution.eu_consumed).label("total_eu"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
Execution.agent_id == agent_id,
|
||||
Execution.started_at >= start_date,
|
||||
Execution.started_at <= end_date,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
exec_row = exec_result.first()
|
||||
|
||||
# 成功率
|
||||
success_result = await db.execute(
|
||||
select(func.count(Execution.id))
|
||||
.where(
|
||||
and_(
|
||||
Execution.agent_id == agent_id,
|
||||
Execution.started_at >= start_date,
|
||||
Execution.status == "completed",
|
||||
)
|
||||
)
|
||||
)
|
||||
success_count = success_result.scalar() or 0
|
||||
total_count = exec_row.total_executions or 1
|
||||
|
||||
return {
|
||||
"agentId": agent_id,
|
||||
"totalExecutions": int(exec_row.total_executions or 0),
|
||||
"avgExecutionTime": float(exec_row.avg_execution_time or 0),
|
||||
"totalEuConsumed": float(exec_row.total_eu or 0),
|
||||
"successRate": round(success_count / total_count * 100, 2) if total_count > 0 else 0,
|
||||
"startDate": start_date.isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def get_platform_resource_overview(db: AsyncSession) -> Dict:
|
||||
"""
|
||||
获取平台资源概览(管理员视图)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
平台资源概览
|
||||
"""
|
||||
now = datetime.utcnow()
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
# 今日调用次数
|
||||
today_calls = await db.execute(
|
||||
select(func.count(BillingRecord.id))
|
||||
.where(BillingRecord.timestamp >= today_start)
|
||||
)
|
||||
|
||||
# 本月调用次数
|
||||
month_calls = await db.execute(
|
||||
select(func.count(BillingRecord.id))
|
||||
.where(BillingRecord.timestamp >= month_start)
|
||||
)
|
||||
|
||||
# 活跃用户数
|
||||
active_users = await db.execute(
|
||||
select(func.count(func.distinct(BillingRecord.tenant_id)))
|
||||
.where(BillingRecord.timestamp >= today_start)
|
||||
)
|
||||
|
||||
# 活跃Agent数
|
||||
active_agents = await db.execute(
|
||||
select(func.count(Agent.id))
|
||||
.where(Agent.status == "active")
|
||||
)
|
||||
|
||||
# 总EU消耗
|
||||
total_eu = await db.execute(
|
||||
select(func.sum(BillingRecord.eu))
|
||||
.where(BillingRecord.timestamp >= month_start)
|
||||
)
|
||||
|
||||
return {
|
||||
"todayCalls": today_calls.scalar() or 0,
|
||||
"monthCalls": month_calls.scalar() or 0,
|
||||
"activeUsersToday": active_users.scalar() or 0,
|
||||
"activeAgents": active_agents.scalar() or 0,
|
||||
"monthTotalEu": int(total_eu.scalar() or 0),
|
||||
"timestamp": now.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
async def aggregate_hourly_usage(db: AsyncSession) -> int:
|
||||
"""
|
||||
聚合小时级资源使用数据(定时任务调用)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
聚合的记录数
|
||||
"""
|
||||
# 获取上一小时的时间范围
|
||||
now = datetime.utcnow()
|
||||
period_end = now.replace(minute=0, second=0, microsecond=0)
|
||||
period_start = period_end - timedelta(hours=1)
|
||||
|
||||
# 从执行记录聚合
|
||||
result = await db.execute(
|
||||
select(
|
||||
Execution.agent_id,
|
||||
func.count(Execution.id).label("api_calls"),
|
||||
func.sum(Execution.execution_time).label("total_time"),
|
||||
func.sum(Execution.cpu_usage).label("cpu_seconds"),
|
||||
func.sum(Execution.memory_usage).label("memory_usage"),
|
||||
)
|
||||
.where(
|
||||
and_(
|
||||
Execution.started_at >= period_start,
|
||||
Execution.started_at < period_end,
|
||||
)
|
||||
)
|
||||
.group_by(Execution.agent_id)
|
||||
)
|
||||
|
||||
count = 0
|
||||
for row in result.all():
|
||||
if row.agent_id:
|
||||
# 获取Agent所有者
|
||||
agent_result = await db.execute(
|
||||
select(Agent.owner_id).where(Agent.id == row.agent_id)
|
||||
)
|
||||
owner = agent_result.scalar()
|
||||
|
||||
if owner:
|
||||
await record_resource_usage(
|
||||
user_id=str(owner),
|
||||
agent_id=str(row.agent_id),
|
||||
cpu_seconds=float(row.cpu_seconds or 0),
|
||||
memory_mb_seconds=float(row.memory_usage or 0) * (float(row.total_time or 0) / 1000),
|
||||
network_bytes=0,
|
||||
storage_bytes=0,
|
||||
api_calls=int(row.api_calls or 0),
|
||||
period_start=period_start,
|
||||
period_end=period_end,
|
||||
granularity="hourly",
|
||||
db=db,
|
||||
)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi import FastAPI
|
||||
|
||||
from . import (
|
||||
agents, frontend_integration, health, metrics, monitoring, tools, websocket,
|
||||
auth, user, channel, admin, providers
|
||||
auth, user, channel, admin, providers, billing_admin
|
||||
)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ def register_routes(app: FastAPI) -> None:
|
||||
channel.router,
|
||||
admin.router,
|
||||
providers.router,
|
||||
billing_admin.router, # 计费与资源管理路由
|
||||
frontend_integration.router,
|
||||
agents.router,
|
||||
tools.router,
|
||||
|
||||
@@ -161,11 +161,41 @@ async def login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
|
||||
|
||||
|
||||
@router.post("/logout", response_model=SuccessResponse)
|
||||
async def logout(principal: dict = Depends(require_auth)):
|
||||
async def logout(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
用户登出
|
||||
|
||||
将当前Token加入黑名单
|
||||
"""
|
||||
# 实际应该将token加入黑名单,这里简化处理
|
||||
from datetime import datetime, timedelta
|
||||
from app.token_blacklist import add_token_to_blacklist
|
||||
|
||||
user_id = principal.get("user_id")
|
||||
claims = principal.get("claims", {})
|
||||
|
||||
# 获取Token的过期时间(从claims中提取)
|
||||
exp = claims.get("exp")
|
||||
if exp:
|
||||
expires_at = datetime.utcfromtimestamp(exp)
|
||||
else:
|
||||
# 默认7天后过期
|
||||
expires_at = datetime.utcnow() + timedelta(days=7)
|
||||
|
||||
# 生成token的唯一标识(使用sub + 时间戳)
|
||||
token_jti = f"logout_{user_id}_{datetime.utcnow().timestamp()}"
|
||||
|
||||
# 添加到黑名单
|
||||
await add_token_to_blacklist(
|
||||
token_jti=token_jti,
|
||||
user_id=user_id,
|
||||
expires_at=expires_at,
|
||||
reason="logout",
|
||||
db=db,
|
||||
)
|
||||
|
||||
return SuccessResponse(message="登出成功")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
"""
|
||||
计费与资源管理API路由
|
||||
包含配额管理、资源监控、事件查询、审计日志、供应商健康等功能
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel, Field
|
||||
from decimal import Decimal
|
||||
|
||||
from database import get_db
|
||||
from app.auth import require_auth
|
||||
from app.permissions import has_permission
|
||||
from app.schemas import SuccessResponse
|
||||
|
||||
# 导入功能模块
|
||||
from app.quota_manager import (
|
||||
check_user_balance_quota,
|
||||
check_channel_quota,
|
||||
get_active_alerts,
|
||||
acknowledge_alert,
|
||||
resolve_alert,
|
||||
get_quota_summary,
|
||||
)
|
||||
from app.resource_monitor import (
|
||||
get_user_resource_summary,
|
||||
get_resource_trends,
|
||||
get_agent_resource_stats,
|
||||
get_platform_resource_overview,
|
||||
)
|
||||
from app.events import (
|
||||
get_pending_events,
|
||||
retry_failed_events,
|
||||
get_event_stats,
|
||||
)
|
||||
from app.tracing import (
|
||||
get_execution_trace,
|
||||
query_traces,
|
||||
get_trace_stats,
|
||||
)
|
||||
from app.audit import (
|
||||
query_audit_logs,
|
||||
get_audit_summary,
|
||||
get_user_activity,
|
||||
log_audit_event,
|
||||
)
|
||||
from app.provider_health import (
|
||||
run_health_checks,
|
||||
get_provider_health_summary,
|
||||
get_all_providers_health,
|
||||
create_model_pricing,
|
||||
get_model_pricing,
|
||||
calculate_model_cost,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/billing-admin", tags=["计费与资源管理"])
|
||||
|
||||
|
||||
def _get_role(principal: dict) -> str:
|
||||
"""从principal获取角色"""
|
||||
return principal.get("claims", {}).get("role", "")
|
||||
|
||||
|
||||
def _verify_admin_permission(principal: dict):
|
||||
"""验证管理员权限"""
|
||||
role = _get_role(principal)
|
||||
if role not in ["super_admin", "billing_admin", "operations_admin"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限"
|
||||
)
|
||||
|
||||
|
||||
def _verify_write_permission(principal: dict):
|
||||
"""验证写入权限"""
|
||||
role = _get_role(principal)
|
||||
if role not in ["super_admin", "billing_admin"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要写入权限"
|
||||
)
|
||||
|
||||
|
||||
# ============= 配额管理 =============
|
||||
|
||||
@router.get("/quota/user/{user_id}", response_model=SuccessResponse)
|
||||
async def get_user_quota(
|
||||
user_id: str,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取用户配额信息
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
summary = await get_quota_summary(user_id, db)
|
||||
return SuccessResponse(data=summary)
|
||||
|
||||
|
||||
@router.get("/quota/channel/{channel_id}", response_model=SuccessResponse)
|
||||
async def get_channel_quota(
|
||||
channel_id: str,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取渠道配额信息
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
has_quota, alert_type, details = await check_channel_quota(channel_id, db)
|
||||
return SuccessResponse(data={
|
||||
"hasQuota": has_quota,
|
||||
"alertType": alert_type,
|
||||
**details,
|
||||
})
|
||||
|
||||
|
||||
@router.get("/quota/alerts", response_model=SuccessResponse)
|
||||
async def list_quota_alerts(
|
||||
user_id: Optional[str] = Query(None),
|
||||
channel_id: Optional[str] = Query(None),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取配额预警列表
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
alerts = await get_active_alerts(user_id, channel_id, db)
|
||||
return SuccessResponse(data={"alerts": alerts, "count": len(alerts)})
|
||||
|
||||
|
||||
@router.put("/quota/alerts/{alert_id}/acknowledge", response_model=SuccessResponse)
|
||||
async def ack_quota_alert(
|
||||
alert_id: str,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
确认配额预警
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
success = await acknowledge_alert(alert_id, db)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="预警不存在")
|
||||
|
||||
return SuccessResponse(message="预警已确认")
|
||||
|
||||
|
||||
@router.put("/quota/alerts/{alert_id}/resolve", response_model=SuccessResponse)
|
||||
async def resolve_quota_alert(
|
||||
alert_id: str,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
解决配额预警
|
||||
"""
|
||||
_verify_write_permission(principal)
|
||||
|
||||
success = await resolve_alert(alert_id, db)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="预警不存在")
|
||||
|
||||
return SuccessResponse(message="预警已解决")
|
||||
|
||||
|
||||
# ============= 资源监控 =============
|
||||
|
||||
@router.get("/resources/overview", response_model=SuccessResponse)
|
||||
async def get_resources_overview(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取平台资源概览
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
overview = await get_platform_resource_overview(db)
|
||||
return SuccessResponse(data=overview)
|
||||
|
||||
|
||||
@router.get("/resources/user/{user_id}", response_model=SuccessResponse)
|
||||
async def get_user_resources(
|
||||
user_id: str,
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取用户资源使用汇总
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
||||
|
||||
summary = await get_user_resource_summary(user_id, start_dt, end_dt, db)
|
||||
return SuccessResponse(data=summary)
|
||||
|
||||
|
||||
@router.get("/resources/trends", response_model=SuccessResponse)
|
||||
async def get_resource_usage_trends(
|
||||
user_id: str = Query(...),
|
||||
period: str = Query("7d", pattern="^(7d|30d|90d)$"),
|
||||
granularity: str = Query("daily", pattern="^(hourly|daily)$"),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取资源使用趋势
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
trends = await get_resource_trends(user_id, period, granularity, db)
|
||||
return SuccessResponse(data={"trends": trends})
|
||||
|
||||
|
||||
@router.get("/resources/agent/{agent_id}", response_model=SuccessResponse)
|
||||
async def get_agent_resources(
|
||||
agent_id: str,
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取Agent资源统计
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
||||
|
||||
stats = await get_agent_resource_stats(agent_id, start_dt, end_dt, db)
|
||||
return SuccessResponse(data=stats)
|
||||
|
||||
|
||||
# ============= 事件管理 =============
|
||||
|
||||
@router.get("/events/pending", response_model=SuccessResponse)
|
||||
async def list_pending_events(
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取待处理的事件
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
events = await get_pending_events(limit, db)
|
||||
return SuccessResponse(data={"events": events, "count": len(events)})
|
||||
|
||||
|
||||
@router.post("/events/retry-failed", response_model=SuccessResponse)
|
||||
async def retry_failed_billing_events(
|
||||
max_retries: int = Query(3, ge=1, le=10),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
重试失败的事件
|
||||
"""
|
||||
_verify_write_permission(principal)
|
||||
|
||||
count = await retry_failed_events(max_retries, db)
|
||||
return SuccessResponse(data={"retriedCount": count}, message=f"已重试 {count} 个事件")
|
||||
|
||||
|
||||
@router.get("/events/stats", response_model=SuccessResponse)
|
||||
async def get_billing_event_stats(
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取事件统计
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
||||
|
||||
stats = await get_event_stats(start_dt, end_dt, db)
|
||||
return SuccessResponse(data=stats)
|
||||
|
||||
|
||||
# ============= 追踪管理 =============
|
||||
|
||||
@router.get("/traces/execution/{execution_id}", response_model=SuccessResponse)
|
||||
async def get_execution_traces(
|
||||
execution_id: str,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取执行追踪详情
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
trace = await get_execution_trace(execution_id, db)
|
||||
return SuccessResponse(data=trace)
|
||||
|
||||
|
||||
@router.get("/traces", response_model=SuccessResponse)
|
||||
async def list_traces(
|
||||
user_id: Optional[str] = Query(None),
|
||||
agent_id: Optional[str] = Query(None),
|
||||
status: Optional[str] = Query(None),
|
||||
start_date: Optional[str] = Query(None),
|
||||
end_date: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
查询追踪记录
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00")) if start_date else None
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00")) if end_date else None
|
||||
|
||||
result = await query_traces(
|
||||
user_id=user_id,
|
||||
agent_id=agent_id,
|
||||
status=status,
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
db=db,
|
||||
)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@router.get("/traces/stats", response_model=SuccessResponse)
|
||||
async def get_traces_stats(
|
||||
user_id: Optional[str] = Query(None),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取追踪统计
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
||||
|
||||
stats = await get_trace_stats(user_id, start_dt, end_dt, db)
|
||||
return SuccessResponse(data=stats)
|
||||
|
||||
|
||||
# ============= 审计日志 =============
|
||||
|
||||
@router.get("/audit/logs", response_model=SuccessResponse)
|
||||
async def list_audit_logs(
|
||||
user_id: Optional[str] = Query(None),
|
||||
action: Optional[str] = Query(None),
|
||||
resource_type: Optional[str] = Query(None),
|
||||
success: Optional[bool] = Query(None),
|
||||
start_date: Optional[str] = Query(None),
|
||||
end_date: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
查询审计日志
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00")) if start_date else None
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00")) if end_date else None
|
||||
|
||||
result = await query_audit_logs(
|
||||
user_id=user_id,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
success=success,
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
db=db,
|
||||
)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@router.get("/audit/summary", response_model=SuccessResponse)
|
||||
async def get_audit_logs_summary(
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取审计日志汇总
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
||||
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
||||
|
||||
summary = await get_audit_summary(start_dt, end_dt, db)
|
||||
return SuccessResponse(data=summary)
|
||||
|
||||
|
||||
@router.get("/audit/user/{user_id}/activity", response_model=SuccessResponse)
|
||||
async def get_user_audit_activity(
|
||||
user_id: str,
|
||||
days: int = Query(30, ge=1, le=90),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取用户活动历史
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
activity = await get_user_activity(user_id, days, db)
|
||||
return SuccessResponse(data={"activity": activity})
|
||||
|
||||
|
||||
# ============= 供应商健康检查 =============
|
||||
|
||||
@router.get("/providers/health", response_model=SuccessResponse)
|
||||
async def get_providers_health(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取所有供应商健康状态
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
health_list = await get_all_providers_health(db)
|
||||
return SuccessResponse(data={"providers": health_list})
|
||||
|
||||
|
||||
@router.get("/providers/{provider_id}/health", response_model=SuccessResponse)
|
||||
async def get_provider_health(
|
||||
provider_id: str,
|
||||
hours: int = Query(24, ge=1, le=168),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取单个供应商健康详情
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
summary = await get_provider_health_summary(provider_id, hours, db)
|
||||
return SuccessResponse(data=summary)
|
||||
|
||||
|
||||
@router.post("/providers/health-check", response_model=SuccessResponse)
|
||||
async def run_providers_health_check(
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
执行所有供应商健康检查
|
||||
"""
|
||||
_verify_write_permission(principal)
|
||||
|
||||
results = await run_health_checks(db)
|
||||
return SuccessResponse(data=results)
|
||||
|
||||
|
||||
# ============= 模型定价管理 =============
|
||||
|
||||
class ModelPricingRequest(BaseModel):
|
||||
provider_id: str = Field(..., alias="providerId")
|
||||
model_name: str = Field(..., alias="modelName")
|
||||
input_price_per_1k: float = Field(..., alias="inputPricePer1k")
|
||||
output_price_per_1k: float = Field(..., alias="outputPricePer1k")
|
||||
eu_per_1k_tokens: float = Field(0.1, alias="euPer1kTokens")
|
||||
max_context_length: int = Field(4096, alias="maxContextLength")
|
||||
max_output_tokens: int = Field(2048, alias="maxOutputTokens")
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
|
||||
@router.get("/pricing/models", response_model=SuccessResponse)
|
||||
async def list_model_pricing(
|
||||
provider_id: Optional[str] = Query(None),
|
||||
model_name: Optional[str] = Query(None),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
获取模型定价列表
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
pricing_list = await get_model_pricing(provider_id, model_name, db)
|
||||
return SuccessResponse(data={"pricing": pricing_list})
|
||||
|
||||
|
||||
@router.post("/pricing/models", response_model=SuccessResponse)
|
||||
async def create_or_update_model_pricing(
|
||||
req: ModelPricingRequest,
|
||||
request: Request,
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
创建或更新模型定价
|
||||
"""
|
||||
_verify_write_permission(principal)
|
||||
|
||||
pricing = await create_model_pricing(
|
||||
provider_id=req.provider_id,
|
||||
model_name=req.model_name,
|
||||
input_price_per_1k=req.input_price_per_1k,
|
||||
output_price_per_1k=req.output_price_per_1k,
|
||||
eu_per_1k_tokens=req.eu_per_1k_tokens,
|
||||
max_context_length=req.max_context_length,
|
||||
max_output_tokens=req.max_output_tokens,
|
||||
db=db,
|
||||
)
|
||||
|
||||
# 记录审计日志
|
||||
await log_audit_event(
|
||||
action="provider.pricing_update",
|
||||
resource_type="model_pricing",
|
||||
resource_id=str(pricing.id),
|
||||
user_id=principal.get("user_id"),
|
||||
success=True,
|
||||
details={"modelName": req.model_name},
|
||||
request=request,
|
||||
db=db,
|
||||
)
|
||||
|
||||
return SuccessResponse(
|
||||
data={"id": str(pricing.id), "modelName": pricing.model_name},
|
||||
message="模型定价已更新"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/pricing/calculate", response_model=SuccessResponse)
|
||||
async def calculate_pricing(
|
||||
model_name: str = Query(...),
|
||||
input_tokens: int = Query(..., ge=0),
|
||||
output_tokens: int = Query(..., ge=0),
|
||||
principal: dict = Depends(require_auth),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
计算模型调用成本
|
||||
"""
|
||||
_verify_admin_permission(principal)
|
||||
|
||||
result = await calculate_model_cost(model_name, input_tokens, output_tokens, db)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Token黑名单管理模块
|
||||
用于JWT登出和Token失效控制
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import select, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models import TokenBlacklist
|
||||
|
||||
|
||||
async def add_token_to_blacklist(
|
||||
token_jti: str,
|
||||
user_id: str,
|
||||
expires_at: datetime,
|
||||
reason: str,
|
||||
db: AsyncSession
|
||||
) -> TokenBlacklist:
|
||||
"""
|
||||
将Token添加到黑名单
|
||||
|
||||
Args:
|
||||
token_jti: JWT ID (jti claim)
|
||||
user_id: 用户ID
|
||||
expires_at: Token过期时间
|
||||
reason: 黑名单原因 (logout, revoked, security)
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
TokenBlacklist记录
|
||||
"""
|
||||
blacklist_entry = TokenBlacklist(
|
||||
token_jti=token_jti,
|
||||
user_id=user_id,
|
||||
expires_at=expires_at,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
db.add(blacklist_entry)
|
||||
await db.commit()
|
||||
await db.refresh(blacklist_entry)
|
||||
|
||||
return blacklist_entry
|
||||
|
||||
|
||||
async def is_token_blacklisted(
|
||||
token_jti: str,
|
||||
db: AsyncSession
|
||||
) -> bool:
|
||||
"""
|
||||
检查Token是否在黑名单中
|
||||
|
||||
Args:
|
||||
token_jti: JWT ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
是否在黑名单中
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(TokenBlacklist).where(TokenBlacklist.token_jti == token_jti)
|
||||
)
|
||||
return result.scalar_one_or_none() is not None
|
||||
|
||||
|
||||
async def revoke_all_user_tokens(
|
||||
user_id: str,
|
||||
reason: str,
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""
|
||||
撤销用户的所有Token
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
reason: 撤销原因
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
影响的Token数量
|
||||
"""
|
||||
# 这里需要配合JWT的jti claim使用
|
||||
# 实际实现中,应该在生成JWT时记录jti,然后在这里批量加入黑名单
|
||||
# 简化实现:直接标记用户的所有未过期Token
|
||||
from datetime import timedelta
|
||||
|
||||
# 创建一个特殊的黑名单条目,表示该用户在此时间之前的所有Token都失效
|
||||
blacklist_entry = TokenBlacklist(
|
||||
token_jti=f"user_revoke_{user_id}_{datetime.utcnow().timestamp()}",
|
||||
user_id=user_id,
|
||||
expires_at=datetime.utcnow() + timedelta(days=7), # 7天后自动清理
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
db.add(blacklist_entry)
|
||||
await db.commit()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
async def cleanup_expired_tokens(db: AsyncSession) -> int:
|
||||
"""
|
||||
清理过期的黑名单记录
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
清理的记录数
|
||||
"""
|
||||
result = await db.execute(
|
||||
delete(TokenBlacklist).where(
|
||||
TokenBlacklist.expires_at < datetime.utcnow()
|
||||
)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return result.rowcount
|
||||
|
||||
|
||||
async def get_user_blacklist_count(
|
||||
user_id: str,
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""
|
||||
获取用户的黑名单Token数量
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
黑名单数量
|
||||
"""
|
||||
from sqlalchemy import func
|
||||
|
||||
result = await db.execute(
|
||||
select(func.count(TokenBlacklist.id)).where(
|
||||
TokenBlacklist.user_id == user_id,
|
||||
TokenBlacklist.expires_at > datetime.utcnow()
|
||||
)
|
||||
)
|
||||
|
||||
return result.scalar() or 0
|
||||
|
||||
@@ -0,0 +1,497 @@
|
||||
"""
|
||||
Agent轨迹追踪模块
|
||||
执行轨迹记录与查询
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Optional, Any
|
||||
from sqlalchemy import select, func, and_, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import structlog
|
||||
|
||||
from models import AgentTrace, Execution, Agent, User
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TraceContext:
|
||||
"""追踪上下文"""
|
||||
|
||||
def __init__(self, trace_id: Optional[str] = None):
|
||||
self.trace_id = trace_id or str(uuid.uuid4())
|
||||
self.spans: List[Dict] = []
|
||||
self._current_span_id: Optional[str] = None
|
||||
|
||||
def start_span(
|
||||
self,
|
||||
operation_name: str,
|
||||
operation_type: str,
|
||||
parent_span_id: Optional[str] = None
|
||||
) -> str:
|
||||
"""开始一个新的span"""
|
||||
span_id = str(uuid.uuid4())
|
||||
span = {
|
||||
"span_id": span_id,
|
||||
"parent_span_id": parent_span_id or self._current_span_id,
|
||||
"operation_name": operation_name,
|
||||
"operation_type": operation_type,
|
||||
"started_at": datetime.utcnow(),
|
||||
"ended_at": None,
|
||||
"status": "running",
|
||||
"input_data": None,
|
||||
"output_data": None,
|
||||
"error_type": None,
|
||||
"error_message": None,
|
||||
}
|
||||
self.spans.append(span)
|
||||
self._current_span_id = span_id
|
||||
return span_id
|
||||
|
||||
def end_span(
|
||||
self,
|
||||
span_id: str,
|
||||
status: str = "success",
|
||||
output_data: Optional[Dict] = None,
|
||||
error_type: Optional[str] = None,
|
||||
error_message: Optional[str] = None
|
||||
):
|
||||
"""结束一个span"""
|
||||
for span in self.spans:
|
||||
if span["span_id"] == span_id:
|
||||
span["ended_at"] = datetime.utcnow()
|
||||
span["status"] = status
|
||||
span["output_data"] = output_data
|
||||
span["error_type"] = error_type
|
||||
span["error_message"] = error_message
|
||||
break
|
||||
|
||||
def set_span_input(self, span_id: str, input_data: Dict):
|
||||
"""设置span的输入数据"""
|
||||
for span in self.spans:
|
||||
if span["span_id"] == span_id:
|
||||
span["input_data"] = input_data
|
||||
break
|
||||
|
||||
|
||||
async def create_trace_record(
|
||||
execution_id: str,
|
||||
agent_id: str,
|
||||
user_id: str,
|
||||
trace_id: str,
|
||||
span_id: str,
|
||||
parent_span_id: Optional[str],
|
||||
operation_name: str,
|
||||
operation_type: str,
|
||||
started_at: datetime,
|
||||
ended_at: Optional[datetime],
|
||||
status: str,
|
||||
input_data: Optional[Dict],
|
||||
output_data: Optional[Dict],
|
||||
error_type: Optional[str],
|
||||
error_message: Optional[str],
|
||||
tokens_used: int,
|
||||
eu_consumed: Decimal,
|
||||
db: AsyncSession
|
||||
) -> AgentTrace:
|
||||
"""
|
||||
创建追踪记录
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
agent_id: Agent ID
|
||||
user_id: 用户ID
|
||||
trace_id: 追踪ID
|
||||
span_id: Span ID
|
||||
parent_span_id: 父Span ID
|
||||
operation_name: 操作名称
|
||||
operation_type: 操作类型
|
||||
started_at: 开始时间
|
||||
ended_at: 结束时间
|
||||
status: 状态
|
||||
input_data: 输入数据
|
||||
output_data: 输出数据
|
||||
error_type: 错误类型
|
||||
error_message: 错误消息
|
||||
tokens_used: 使用的Token数
|
||||
eu_consumed: 消耗的EU
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
AgentTrace记录
|
||||
"""
|
||||
# 计算持续时间
|
||||
duration_ms = None
|
||||
if ended_at and started_at:
|
||||
duration_ms = int((ended_at - started_at).total_seconds() * 1000)
|
||||
|
||||
# 对输入输出数据进行脱敏
|
||||
sanitized_input = _sanitize_data(input_data) if input_data else None
|
||||
sanitized_output = _sanitize_data(output_data) if output_data else None
|
||||
|
||||
trace = AgentTrace(
|
||||
execution_id=execution_id,
|
||||
agent_id=agent_id,
|
||||
user_id=user_id,
|
||||
trace_id=trace_id,
|
||||
span_id=span_id,
|
||||
parent_span_id=parent_span_id,
|
||||
operation_name=operation_name,
|
||||
operation_type=operation_type,
|
||||
started_at=started_at,
|
||||
ended_at=ended_at,
|
||||
duration_ms=duration_ms,
|
||||
input_data=sanitized_input,
|
||||
output_data=sanitized_output,
|
||||
status=status,
|
||||
error_type=error_type,
|
||||
error_message=error_message,
|
||||
tokens_used=tokens_used,
|
||||
eu_consumed=eu_consumed,
|
||||
)
|
||||
|
||||
db.add(trace)
|
||||
await db.commit()
|
||||
await db.refresh(trace)
|
||||
|
||||
return trace
|
||||
|
||||
|
||||
async def save_trace_context(
|
||||
trace_context: TraceContext,
|
||||
execution_id: str,
|
||||
agent_id: str,
|
||||
user_id: str,
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""
|
||||
保存完整的追踪上下文
|
||||
|
||||
Args:
|
||||
trace_context: 追踪上下文
|
||||
execution_id: 执行ID
|
||||
agent_id: Agent ID
|
||||
user_id: 用户ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
保存的span数量
|
||||
"""
|
||||
count = 0
|
||||
for span in trace_context.spans:
|
||||
await create_trace_record(
|
||||
execution_id=execution_id,
|
||||
agent_id=agent_id,
|
||||
user_id=user_id,
|
||||
trace_id=trace_context.trace_id,
|
||||
span_id=span["span_id"],
|
||||
parent_span_id=span["parent_span_id"],
|
||||
operation_name=span["operation_name"],
|
||||
operation_type=span["operation_type"],
|
||||
started_at=span["started_at"],
|
||||
ended_at=span["ended_at"],
|
||||
status=span["status"],
|
||||
input_data=span["input_data"],
|
||||
output_data=span["output_data"],
|
||||
error_type=span["error_type"],
|
||||
error_message=span["error_message"],
|
||||
tokens_used=0,
|
||||
eu_consumed=Decimal(0),
|
||||
db=db,
|
||||
)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
async def get_execution_trace(
|
||||
execution_id: str,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取执行的完整追踪
|
||||
|
||||
Args:
|
||||
execution_id: 执行ID
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
执行追踪详情
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(AgentTrace)
|
||||
.where(AgentTrace.execution_id == execution_id)
|
||||
.order_by(AgentTrace.started_at)
|
||||
)
|
||||
traces = result.scalars().all()
|
||||
|
||||
if not traces:
|
||||
return {"executionId": execution_id, "spans": []}
|
||||
|
||||
trace_id = traces[0].trace_id if traces else None
|
||||
|
||||
# 构建span树
|
||||
spans = []
|
||||
for trace in traces:
|
||||
spans.append({
|
||||
"spanId": trace.span_id,
|
||||
"parentSpanId": trace.parent_span_id,
|
||||
"operationName": trace.operation_name,
|
||||
"operationType": trace.operation_type,
|
||||
"startedAt": trace.started_at.isoformat(),
|
||||
"endedAt": trace.ended_at.isoformat() if trace.ended_at else None,
|
||||
"durationMs": trace.duration_ms,
|
||||
"status": trace.status,
|
||||
"inputData": trace.input_data,
|
||||
"outputData": trace.output_data,
|
||||
"errorType": trace.error_type,
|
||||
"errorMessage": trace.error_message,
|
||||
"tokensUsed": trace.tokens_used,
|
||||
"euConsumed": float(trace.eu_consumed) if trace.eu_consumed else 0,
|
||||
})
|
||||
|
||||
# 计算总持续时间和EU
|
||||
total_duration = sum(s["durationMs"] or 0 for s in spans)
|
||||
total_eu = sum(s["euConsumed"] for s in spans)
|
||||
|
||||
return {
|
||||
"executionId": execution_id,
|
||||
"traceId": trace_id,
|
||||
"spans": spans,
|
||||
"totalDurationMs": total_duration,
|
||||
"totalEuConsumed": total_eu,
|
||||
"spanCount": len(spans),
|
||||
}
|
||||
|
||||
|
||||
async def query_traces(
|
||||
user_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
db: AsyncSession = None
|
||||
) -> Dict:
|
||||
"""
|
||||
查询追踪记录
|
||||
|
||||
Args:
|
||||
user_id: 用户ID过滤
|
||||
agent_id: Agent ID过滤
|
||||
status: 状态过滤
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
page: 页码
|
||||
page_size: 每页大小
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
查询结果
|
||||
"""
|
||||
# 按trace_id分组查询
|
||||
query = select(
|
||||
AgentTrace.trace_id,
|
||||
AgentTrace.execution_id,
|
||||
AgentTrace.agent_id,
|
||||
AgentTrace.user_id,
|
||||
func.min(AgentTrace.started_at).label("started_at"),
|
||||
func.max(AgentTrace.ended_at).label("ended_at"),
|
||||
func.count(AgentTrace.id).label("span_count"),
|
||||
func.sum(AgentTrace.tokens_used).label("total_tokens"),
|
||||
func.sum(AgentTrace.eu_consumed).label("total_eu"),
|
||||
)
|
||||
|
||||
# 应用过滤条件
|
||||
conditions = []
|
||||
if user_id:
|
||||
conditions.append(AgentTrace.user_id == user_id)
|
||||
if agent_id:
|
||||
conditions.append(AgentTrace.agent_id == agent_id)
|
||||
if status:
|
||||
conditions.append(AgentTrace.status == status)
|
||||
if start_date:
|
||||
conditions.append(AgentTrace.started_at >= start_date)
|
||||
if end_date:
|
||||
conditions.append(AgentTrace.started_at <= end_date)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
query = query.group_by(
|
||||
AgentTrace.trace_id,
|
||||
AgentTrace.execution_id,
|
||||
AgentTrace.agent_id,
|
||||
AgentTrace.user_id,
|
||||
)
|
||||
|
||||
# 计算总数
|
||||
count_query = select(func.count(func.distinct(AgentTrace.trace_id)))
|
||||
if conditions:
|
||||
count_query = count_query.where(and_(*conditions))
|
||||
count_result = await db.execute(count_query)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# 分页
|
||||
query = query.order_by(desc(func.min(AgentTrace.started_at)))
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
result = await db.execute(query)
|
||||
rows = result.all()
|
||||
|
||||
# 获取Agent名称
|
||||
agent_ids = list(set(row.agent_id for row in rows if row.agent_id))
|
||||
agents_map = {}
|
||||
if agent_ids:
|
||||
agents_result = await db.execute(
|
||||
select(Agent).where(Agent.id.in_(agent_ids))
|
||||
)
|
||||
for agent in agents_result.scalars().all():
|
||||
agents_map[str(agent.id)] = agent.name
|
||||
|
||||
traces = []
|
||||
for row in rows:
|
||||
duration_ms = None
|
||||
if row.ended_at and row.started_at:
|
||||
duration_ms = int((row.ended_at - row.started_at).total_seconds() * 1000)
|
||||
|
||||
traces.append({
|
||||
"traceId": row.trace_id,
|
||||
"executionId": str(row.execution_id),
|
||||
"agentId": str(row.agent_id),
|
||||
"agentName": agents_map.get(str(row.agent_id), "Unknown"),
|
||||
"userId": str(row.user_id),
|
||||
"startedAt": row.started_at.isoformat(),
|
||||
"endedAt": row.ended_at.isoformat() if row.ended_at else None,
|
||||
"durationMs": duration_ms,
|
||||
"spanCount": row.span_count,
|
||||
"totalTokens": int(row.total_tokens or 0),
|
||||
"totalEu": float(row.total_eu or 0),
|
||||
})
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"pageSize": page_size,
|
||||
"totalPages": (total + page_size - 1) // page_size,
|
||||
"traces": traces,
|
||||
}
|
||||
|
||||
|
||||
async def get_trace_stats(
|
||||
user_id: Optional[str],
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
db: AsyncSession
|
||||
) -> Dict:
|
||||
"""
|
||||
获取追踪统计
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
db: 数据库会话
|
||||
|
||||
Returns:
|
||||
追踪统计
|
||||
"""
|
||||
conditions = [
|
||||
AgentTrace.started_at >= start_date,
|
||||
AgentTrace.started_at <= end_date,
|
||||
]
|
||||
if user_id:
|
||||
conditions.append(AgentTrace.user_id == user_id)
|
||||
|
||||
# 总体统计
|
||||
stats_result = await db.execute(
|
||||
select(
|
||||
func.count(func.distinct(AgentTrace.trace_id)).label("total_traces"),
|
||||
func.count(AgentTrace.id).label("total_spans"),
|
||||
func.sum(AgentTrace.duration_ms).label("total_duration"),
|
||||
func.avg(AgentTrace.duration_ms).label("avg_duration"),
|
||||
func.sum(AgentTrace.tokens_used).label("total_tokens"),
|
||||
func.sum(AgentTrace.eu_consumed).label("total_eu"),
|
||||
)
|
||||
.where(and_(*conditions))
|
||||
)
|
||||
stats = stats_result.first()
|
||||
|
||||
# 按状态统计
|
||||
status_stats = await db.execute(
|
||||
select(
|
||||
AgentTrace.status,
|
||||
func.count(AgentTrace.id).label("count"),
|
||||
)
|
||||
.where(and_(*conditions))
|
||||
.group_by(AgentTrace.status)
|
||||
)
|
||||
status_breakdown = {row.status: row.count for row in status_stats.all()}
|
||||
|
||||
# 按操作类型统计
|
||||
type_stats = await db.execute(
|
||||
select(
|
||||
AgentTrace.operation_type,
|
||||
func.count(AgentTrace.id).label("count"),
|
||||
func.avg(AgentTrace.duration_ms).label("avg_duration"),
|
||||
)
|
||||
.where(and_(*conditions))
|
||||
.group_by(AgentTrace.operation_type)
|
||||
)
|
||||
type_breakdown = [
|
||||
{
|
||||
"operationType": row.operation_type,
|
||||
"count": row.count,
|
||||
"avgDurationMs": float(row.avg_duration or 0),
|
||||
}
|
||||
for row in type_stats.all()
|
||||
]
|
||||
|
||||
return {
|
||||
"startDate": start_date.isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
"totalTraces": stats.total_traces or 0,
|
||||
"totalSpans": stats.total_spans or 0,
|
||||
"totalDurationMs": int(stats.total_duration or 0),
|
||||
"avgDurationMs": float(stats.avg_duration or 0),
|
||||
"totalTokens": int(stats.total_tokens or 0),
|
||||
"totalEu": float(stats.total_eu or 0),
|
||||
"byStatus": status_breakdown,
|
||||
"byOperationType": type_breakdown,
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_data(data: Dict) -> Dict:
|
||||
"""
|
||||
对敏感数据进行脱敏处理
|
||||
|
||||
Args:
|
||||
data: 原始数据
|
||||
|
||||
Returns:
|
||||
脱敏后的数据
|
||||
"""
|
||||
if not data:
|
||||
return data
|
||||
|
||||
# 敏感字段列表
|
||||
sensitive_fields = {
|
||||
"password", "secret", "token", "api_key", "apikey",
|
||||
"authorization", "auth", "credential", "key",
|
||||
}
|
||||
|
||||
def _sanitize_value(key: str, value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
return {k: _sanitize_value(k, v) for k, v in value.items()}
|
||||
elif isinstance(value, list):
|
||||
return [_sanitize_value(key, v) for v in value]
|
||||
elif key.lower() in sensitive_fields:
|
||||
return "***REDACTED***"
|
||||
elif isinstance(value, str) and len(value) > 1000:
|
||||
return value[:1000] + "...[truncated]"
|
||||
return value
|
||||
|
||||
return {k: _sanitize_value(k, v) for k, v in data.items()}
|
||||
|
||||
@@ -618,3 +618,225 @@ class ProviderModel(BaseModel, Base):
|
||||
# 别名:Tenant 指向 User(租户即为user)
|
||||
Tenant = User
|
||||
|
||||
|
||||
class TokenBlacklist(BaseModel, Base):
|
||||
"""Token黑名单(用于JWT登出和Token失效)"""
|
||||
__tablename__ = "token_blacklist"
|
||||
|
||||
token_jti = Column(String(255), unique=True, nullable=False) # JWT ID
|
||||
user_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
|
||||
expires_at = Column(DateTime, nullable=False) # Token过期时间
|
||||
reason = Column(String(100)) # 黑名单原因:logout, revoked, security
|
||||
|
||||
# 关联关系
|
||||
user = relationship("User")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_token_blacklist_jti", token_jti),
|
||||
Index("idx_token_blacklist_expires", expires_at),
|
||||
)
|
||||
|
||||
|
||||
class ResourceUsage(BaseModel, Base):
|
||||
"""资源使用记录(用于监控和统计)"""
|
||||
__tablename__ = "resource_usage"
|
||||
|
||||
user_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
|
||||
agent_id = Column(GUID(), ForeignKey("agents.id"))
|
||||
|
||||
# 资源使用量
|
||||
cpu_seconds = Column(sa.Float, default=0.0) # CPU使用秒数
|
||||
memory_mb_seconds = Column(sa.Float, default=0.0) # 内存MB*秒
|
||||
network_bytes = Column(sa.BigInteger, default=0) # 网络流量字节
|
||||
storage_bytes = Column(sa.BigInteger, default=0) # 存储使用字节
|
||||
api_calls = Column(Integer, default=0) # API调用次数
|
||||
|
||||
# 时间范围
|
||||
period_start = Column(DateTime, nullable=False)
|
||||
period_end = Column(DateTime, nullable=False)
|
||||
granularity = Column(String(20), default="hourly") # hourly, daily, monthly
|
||||
|
||||
# 关联关系
|
||||
user = relationship("User")
|
||||
agent = relationship("Agent")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_resource_usage_user", user_id),
|
||||
Index("idx_resource_usage_period", period_start, period_end),
|
||||
Index("idx_resource_usage_granularity", granularity),
|
||||
)
|
||||
|
||||
|
||||
class QuotaAlert(BaseModel, Base):
|
||||
"""配额预警记录"""
|
||||
__tablename__ = "quota_alerts"
|
||||
|
||||
user_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
|
||||
channel_id = Column(GUID(), ForeignKey("channels.id"))
|
||||
|
||||
# 预警信息
|
||||
alert_type = Column(String(50), nullable=False) # balance_low, quota_exceeded, rate_limit
|
||||
threshold_percent = Column(Integer) # 触发阈值百分比
|
||||
current_value = Column(sa.Numeric(12, 4)) # 当前值
|
||||
threshold_value = Column(sa.Numeric(12, 4)) # 阈值
|
||||
|
||||
# 状态
|
||||
status = Column(String(20), default="active") # active, acknowledged, resolved
|
||||
acknowledged_at = Column(DateTime)
|
||||
resolved_at = Column(DateTime)
|
||||
|
||||
# 通知
|
||||
notification_sent = Column(Boolean, default=False)
|
||||
notification_channel = Column(String(50)) # email, webhook, sms
|
||||
|
||||
# 关联关系
|
||||
user = relationship("User")
|
||||
channel = relationship("Channel")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_quota_alert_user", user_id),
|
||||
Index("idx_quota_alert_status", status),
|
||||
Index("idx_quota_alert_type", alert_type),
|
||||
)
|
||||
|
||||
|
||||
class ModelPricing(BaseModel, Base):
|
||||
"""模型定价配置"""
|
||||
__tablename__ = "model_pricing"
|
||||
|
||||
provider_id = Column(GUID(), ForeignKey("model_providers.id"), nullable=False)
|
||||
model_name = Column(String(100), nullable=False)
|
||||
|
||||
# 定价(每1K tokens)
|
||||
input_price_per_1k = Column(sa.Numeric(10, 6), nullable=False) # 输入价格
|
||||
output_price_per_1k = Column(sa.Numeric(10, 6), nullable=False) # 输出价格
|
||||
|
||||
# EU转换
|
||||
eu_per_1k_tokens = Column(sa.Numeric(8, 4), default=0.1) # 每1K tokens消耗的EU
|
||||
|
||||
# 限制
|
||||
max_context_length = Column(Integer, default=4096)
|
||||
max_output_tokens = Column(Integer, default=2048)
|
||||
|
||||
# 状态
|
||||
is_active = Column(Boolean, default=True)
|
||||
effective_from = Column(DateTime, default=datetime.utcnow)
|
||||
effective_until = Column(DateTime)
|
||||
|
||||
# 关联关系
|
||||
provider = relationship("ModelProvider")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_model_pricing_provider", provider_id),
|
||||
Index("idx_model_pricing_model", model_name),
|
||||
UniqueConstraint("provider_id", "model_name", name="uq_provider_model_pricing"),
|
||||
)
|
||||
|
||||
|
||||
class ProviderHealthCheck(BaseModel, Base):
|
||||
"""供应商健康检查记录"""
|
||||
__tablename__ = "provider_health_checks"
|
||||
|
||||
provider_id = Column(GUID(), ForeignKey("model_providers.id"), nullable=False)
|
||||
|
||||
# 检查结果
|
||||
is_healthy = Column(Boolean, nullable=False)
|
||||
response_time_ms = Column(Integer) # 响应时间毫秒
|
||||
status_code = Column(Integer) # HTTP状态码
|
||||
error_message = Column(Text)
|
||||
|
||||
# 检查类型
|
||||
check_type = Column(String(50), default="ping") # ping, model_list, completion
|
||||
|
||||
# 关联关系
|
||||
provider = relationship("ModelProvider")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_provider_health_provider", provider_id),
|
||||
Index("idx_provider_health_time", created_at),
|
||||
Index("idx_provider_health_status", is_healthy),
|
||||
)
|
||||
|
||||
|
||||
class AgentTrace(BaseModel, Base):
|
||||
"""Agent执行轨迹(详细追踪记录)"""
|
||||
__tablename__ = "agent_traces"
|
||||
|
||||
execution_id = Column(GUID(), ForeignKey("executions.id"), nullable=False)
|
||||
agent_id = Column(GUID(), ForeignKey("agents.id"), nullable=False)
|
||||
user_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# 轨迹信息
|
||||
trace_id = Column(String(100), nullable=False) # 分布式追踪ID
|
||||
span_id = Column(String(100), nullable=False) # 跨度ID
|
||||
parent_span_id = Column(String(100)) # 父跨度ID
|
||||
|
||||
# 操作信息
|
||||
operation_name = Column(String(200), nullable=False)
|
||||
operation_type = Column(String(50)) # tool_call, llm_call, function_exec
|
||||
|
||||
# 时间信息
|
||||
started_at = Column(DateTime, nullable=False)
|
||||
ended_at = Column(DateTime)
|
||||
duration_ms = Column(Integer)
|
||||
|
||||
# 输入输出
|
||||
input_data = Column(JSON) # 输入数据(脱敏)
|
||||
output_data = Column(JSON) # 输出数据(脱敏)
|
||||
|
||||
# 状态
|
||||
status = Column(String(20), nullable=False) # running, success, error
|
||||
error_type = Column(String(100))
|
||||
error_message = Column(Text)
|
||||
|
||||
# 资源消耗
|
||||
tokens_used = Column(Integer, default=0)
|
||||
eu_consumed = Column(sa.Numeric(10, 4), default=0)
|
||||
|
||||
# 关联关系
|
||||
execution = relationship("Execution")
|
||||
agent = relationship("Agent")
|
||||
user = relationship("User")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_agent_trace_execution", execution_id),
|
||||
Index("idx_agent_trace_trace_id", trace_id),
|
||||
Index("idx_agent_trace_user", user_id),
|
||||
Index("idx_agent_trace_time", started_at),
|
||||
)
|
||||
|
||||
|
||||
class BillingEvent(BaseModel, Base):
|
||||
"""计费事件(用于NATS消息队列)"""
|
||||
__tablename__ = "billing_events"
|
||||
|
||||
# 事件信息
|
||||
event_type = Column(String(50), nullable=False) # execution_start, execution_end, quota_check
|
||||
event_id = Column(String(100), unique=True, nullable=False)
|
||||
|
||||
# 关联实体
|
||||
user_id = Column(GUID(), ForeignKey("users.id"), nullable=False)
|
||||
agent_id = Column(GUID(), ForeignKey("agents.id"))
|
||||
execution_id = Column(GUID(), ForeignKey("executions.id"))
|
||||
|
||||
# 事件数据
|
||||
payload = Column(JSON, nullable=False)
|
||||
|
||||
# 处理状态
|
||||
status = Column(String(20), default="pending") # pending, processing, completed, failed
|
||||
processed_at = Column(DateTime)
|
||||
retry_count = Column(Integer, default=0)
|
||||
error_message = Column(Text)
|
||||
|
||||
# 关联关系
|
||||
user = relationship("User")
|
||||
agent = relationship("Agent")
|
||||
execution = relationship("Execution")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_billing_event_type", event_type),
|
||||
Index("idx_billing_event_status", status),
|
||||
Index("idx_billing_event_user", user_id),
|
||||
Index("idx_billing_event_time", created_at),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user