forked from xiaohei/taiji-AI-PAD
387 lines
9.5 KiB
Markdown
387 lines
9.5 KiB
Markdown
# 计费管理三维度接口文档
|
||
|
||
## 接口信息
|
||
|
||
**接口路径**: `GET /api/admin/billing/overview`
|
||
|
||
**功能**: 获取计费管理三维度统计数据(渠道维度、租户维度、调用记录)
|
||
|
||
**认证**: 需要 Bearer Token
|
||
|
||
**权限**: 管理员角色(super_admin、billing_admin、operations_admin)
|
||
|
||
---
|
||
|
||
## 请求参数
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| startTime | string | ✅ | 开始时间,ISO 8601格式,如 `2026-03-01T00:00:00Z` |
|
||
| endTime | string | ✅ | 结束时间,ISO 8601格式,如 `2026-03-31T23:59:59Z` |
|
||
| channelName | string | ❌ | 渠道名称筛选 |
|
||
| tenantName | string | ❌ | 租户名称筛选 |
|
||
| minCalls | int | ❌ | 最小调用次数筛选 |
|
||
| maxCalls | int | ❌ | 最大调用次数筛选 |
|
||
| export | string | ❌ | 导出格式:`excel`、`csv`、`pdf` |
|
||
|
||
---
|
||
|
||
## 响应数据结构
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"channelStats": [...],
|
||
"tenantStats": [...],
|
||
"callRecords": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 一、渠道维度 (channelStats)
|
||
|
||
### 数据结构
|
||
|
||
```json
|
||
{
|
||
"channelStats": [
|
||
{
|
||
"channelId": "550e8400-e29b-41d4-a716-446655440001",
|
||
"channelName": "渠道A",
|
||
"calls": 1500,
|
||
"totalEU": 7500.0,
|
||
"totalCost": 750.00
|
||
},
|
||
{
|
||
"channelId": "550e8400-e29b-41d4-a716-446655440002",
|
||
"channelName": "渠道B",
|
||
"calls": 800,
|
||
"totalEU": 4000.0,
|
||
"totalCost": 400.00
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| channelId | string | 渠道ID(UUID) |
|
||
| channelName | string | 渠道名称 |
|
||
| calls | int | 调用次数 |
|
||
| totalEU | float | 总EU消耗 |
|
||
| totalCost | float | 渠道总价(USD) |
|
||
|
||
### 前端汇总计算
|
||
|
||
```javascript
|
||
// 渠道总数
|
||
const channelCount = channelStats.length;
|
||
|
||
// 总计费额
|
||
const totalCost = channelStats.reduce((sum, c) => sum + c.totalCost, 0);
|
||
|
||
// 总EU消耗
|
||
const totalEU = channelStats.reduce((sum, c) => sum + c.totalEU, 0);
|
||
```
|
||
|
||
---
|
||
|
||
## 二、租户维度 (tenantStats)
|
||
|
||
### 数据结构
|
||
|
||
```json
|
||
{
|
||
"tenantStats": [
|
||
{
|
||
"tenantId": "b0d02105-55f8-41a7-b09a-bba8f49a9d62",
|
||
"tenantName": "xiaohei",
|
||
"channelName": "66",
|
||
"calls": 84,
|
||
"totalEU": 44197.14,
|
||
"totalCost": 12.2905,
|
||
"avgCost": 0.1463
|
||
},
|
||
{
|
||
"tenantId": "6b49508d-9c2c-4eac-99e1-c36263fd1699",
|
||
"tenantName": "cccccccc",
|
||
"channelName": "66",
|
||
"calls": 2,
|
||
"totalEU": 18.0,
|
||
"totalCost": 0.0046,
|
||
"avgCost": 0.0023
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| tenantId | string | 租户ID(UUID) |
|
||
| tenantName | string | 租户名称 |
|
||
| channelName | string | 所属渠道名称(无渠道时显示"无渠道") |
|
||
| calls | int | 调用次数(Agent使用 + 模型调用) |
|
||
| totalEU | float | 总EU消耗 |
|
||
| totalCost | float | 用户总价(USD) |
|
||
| avgCost | float | 平均消费(totalCost / calls) |
|
||
|
||
### 前端汇总计算
|
||
|
||
```javascript
|
||
// 租户总数
|
||
const tenantCount = tenantStats.length;
|
||
|
||
// 用户总价
|
||
const userTotalCost = tenantStats.reduce((sum, t) => sum + t.totalCost, 0);
|
||
|
||
// 平均消费
|
||
const avgCost = tenantCount > 0 ? userTotalCost / tenantCount : 0;
|
||
```
|
||
|
||
---
|
||
|
||
## 三、调用记录 (callRecords)
|
||
|
||
调用记录包含两种类型:
|
||
- **agent**: Agent 使用记录(来自 AgentBillingRecord 表)
|
||
- **model**: 模型调用记录(来自 ModelBillingRecord 表,LiteLLM 回调数据)
|
||
|
||
### 数据结构
|
||
|
||
```json
|
||
{
|
||
"callRecords": [
|
||
{
|
||
"id": "7d984099-534f-4b8b-a892-74dead3fd5fd",
|
||
"type": "agent",
|
||
"timestamp": "2026-03-09T07:05:41.118384",
|
||
"channelName": "无渠道",
|
||
"tenantName": "xiaohei",
|
||
"agentName": "search-agent-b0d02105-21015e",
|
||
"modelName": "taiji/claude-sonnet-4-5",
|
||
"duration": 83430,
|
||
"eu": 8343,
|
||
"cost": 2.3175
|
||
},
|
||
{
|
||
"id": "d69e4da8-0852-4e35-94dc-ee2bd584e9ca",
|
||
"type": "model",
|
||
"timestamp": "2026-03-05T16:02:47.186423",
|
||
"channelName": "66",
|
||
"tenantName": "xiaohei",
|
||
"agentName": null,
|
||
"modelName": "openrouter/nousresearch/hermes-3-llama-3.1-405b",
|
||
"duration": 44.76,
|
||
"eu": 0.3862,
|
||
"cost": 0.003862,
|
||
"inputTokens": 2932,
|
||
"outputTokens": 930,
|
||
"totalTokens": 3862
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| id | string | 调用ID(UUID) |
|
||
| type | string | 记录类型:`agent`(Agent使用)或 `model`(模型调用) |
|
||
| timestamp | string | 时间戳(ISO 8601格式) |
|
||
| channelName | string | 渠道名称(无渠道时显示"无渠道") |
|
||
| tenantName | string | 租户名称 |
|
||
| agentName | string | Agent名称(模型调用时为 null) |
|
||
| modelName | string | 模型名称(如 `taiji/claude-sonnet-4-5`) |
|
||
| duration | float | 时长(秒)- Agent为运行时长,模型为响应时间 |
|
||
| eu | float | EU消耗 |
|
||
| cost | float | 单次调用总价(USD) |
|
||
| inputTokens | int | 输入Token数(仅 type=model 时有值) |
|
||
| outputTokens | int | 输出Token数(仅 type=model 时有值) |
|
||
| totalTokens | int | 总Token数(仅 type=model 时有值) |
|
||
|
||
### EU计算规则
|
||
|
||
**Agent 使用(type=agent):**
|
||
```
|
||
1 EU = 10秒运行时间
|
||
不足10秒按1 EU计算
|
||
|
||
公式:EU = ceil(duration / 10)
|
||
|
||
示例:
|
||
- 5秒 → 1 EU
|
||
- 10秒 → 1 EU
|
||
- 15秒 → 2 EU
|
||
- 35秒 → 4 EU
|
||
```
|
||
|
||
**模型调用(type=model):**
|
||
```
|
||
EU 按 Token 数量计算
|
||
公式由 LiteLLM 回调提供
|
||
```
|
||
|
||
---
|
||
|
||
## 前端调用示例
|
||
|
||
### 请求示例
|
||
|
||
```javascript
|
||
const response = await fetch(
|
||
'/api/admin/billing/overview?' + new URLSearchParams({
|
||
startTime: '2026-03-01T00:00:00Z',
|
||
endTime: '2026-03-31T23:59:59Z'
|
||
}),
|
||
{
|
||
method: 'GET',
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
}
|
||
);
|
||
|
||
const data = await response.json();
|
||
```
|
||
|
||
### 数据处理示例
|
||
|
||
```javascript
|
||
if (data.success) {
|
||
const { channelStats, tenantStats, callRecords } = data.data;
|
||
|
||
// ========== 渠道维度汇总 ==========
|
||
const channelSummary = {
|
||
totalChannels: channelStats.length,
|
||
totalCost: channelStats.reduce((sum, c) => sum + c.totalCost, 0),
|
||
totalEU: channelStats.reduce((sum, c) => sum + c.totalEU, 0)
|
||
};
|
||
|
||
// ========== 租户维度汇总 ==========
|
||
const tenantSummary = {
|
||
totalTenants: tenantStats.length,
|
||
userTotalCost: tenantStats.reduce((sum, t) => sum + t.totalCost, 0),
|
||
avgCost: tenantStats.length > 0
|
||
? tenantStats.reduce((sum, t) => sum + t.totalCost, 0) / tenantStats.length
|
||
: 0
|
||
};
|
||
|
||
// ========== 渠道计费详情表格数据 ==========
|
||
const channelTableData = channelStats.map(c => ({
|
||
渠道名称: c.channelName,
|
||
调用次数: c.calls,
|
||
总EU: c.totalEU,
|
||
渠道总价: `$${c.totalCost.toFixed(2)}`
|
||
}));
|
||
|
||
// ========== 租户计费详情表格数据 ==========
|
||
const tenantTableData = tenantStats.map(t => ({
|
||
租户名称: t.tenantName,
|
||
所属渠道: t.channelName,
|
||
调用次数: t.calls,
|
||
总EU: t.totalEU,
|
||
用户总价: `$${t.totalCost.toFixed(2)}`
|
||
}));
|
||
|
||
// ========== 调用记录明细表格数据 ==========
|
||
const callTableData = callRecords.map(r => ({
|
||
调用ID: r.id,
|
||
租户: r.tenantName,
|
||
渠道: r.channelName,
|
||
调用时间: r.agentName,
|
||
'时长(秒)': r.duration,
|
||
EU: r.eu,
|
||
单次调用总价: `$${r.cost.toFixed(2)}`,
|
||
时间戳: r.timestamp
|
||
}));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 前端页面字段映射
|
||
|
||
### 渠道维度卡片
|
||
|
||
| 前端显示 | 数据来源 |
|
||
|---------|---------|
|
||
| 渠道总数 | `channelStats.length` |
|
||
| 总计费额 | `channelStats.reduce((sum, c) => sum + c.totalCost, 0)` |
|
||
| 总EU消耗 | `channelStats.reduce((sum, c) => sum + c.totalEU, 0)` |
|
||
|
||
### 渠道计费详情表格
|
||
|
||
| 表头 | 字段 |
|
||
|------|------|
|
||
| 渠道名称 | `channelName` |
|
||
| 调用次数 | `calls` |
|
||
| 总EU | `totalEU` |
|
||
| 渠道总价 | `totalCost` |
|
||
|
||
### 租户维度卡片
|
||
|
||
| 前端显示 | 数据来源 |
|
||
|---------|---------|
|
||
| 租户总数 | `tenantStats.length` |
|
||
| 用户总价 | `tenantStats.reduce((sum, t) => sum + t.totalCost, 0)` |
|
||
| 平均消费 | `用户总价 / 租户总数` |
|
||
|
||
### 租户计费详情表格
|
||
|
||
| 表头 | 字段 |
|
||
|------|------|
|
||
| 租户名称 | `tenantName` |
|
||
| 所属渠道 | `channelName` |
|
||
| 调用次数 | `calls` |
|
||
| 总EU | `totalEU` |
|
||
| 用户总价 | `totalCost` |
|
||
|
||
### 调用记录明细表格
|
||
|
||
| 表头 | 字段 |
|
||
|------|------|
|
||
| 调用ID | `id` |
|
||
| 类型 | `type`(agent/model) |
|
||
| 租户 | `tenantName` |
|
||
| 渠道 | `channelName` |
|
||
| Agent名称 | `agentName` |
|
||
| 模型名称 | `modelName` |
|
||
| 时长(秒) | `duration` |
|
||
| EU | `eu` |
|
||
| 单次调用总价 | `cost` |
|
||
| 输入Token | `inputTokens`(仅模型调用) |
|
||
| 输出Token | `outputTokens`(仅模型调用) |
|
||
| 总Token | `totalTokens`(仅模型调用) |
|
||
| 时间戳 | `timestamp` |
|
||
|
||
---
|
||
|
||
## 数据来源说明
|
||
|
||
本接口从以下两个表聚合数据:
|
||
|
||
| 表名 | 说明 | 对应 type |
|
||
|------|------|----------|
|
||
| AgentBillingRecord | Agent 使用计费记录 | `agent` |
|
||
| ModelBillingRecord | 模型调用计费记录(LiteLLM 回调) | `model` |
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **时间格式**: 请求参数中的时间需要使用 ISO 8601 格式
|
||
2. **金额单位**: 所有金额字段单位为 USD(美元)
|
||
3. **调用记录限制**: 默认返回最近100条记录(Agent 50条 + 模型 50条),按时间倒序排列
|
||
4. **EU计算**: Agent 使用按 1 EU = 10秒计算;模型调用按 Token 数量计算
|
||
5. **渠道名称**: 如果租户未关联渠道,显示"无渠道"
|
||
6. **记录类型**: 通过 `type` 字段区分 Agent 使用记录和模型调用记录
|