Files
taiji-AI-PAD/Docs/项目文档/计费管理三维度接口文档.md
2026-03-10 06:40:38 +00:00

387 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 计费管理三维度接口文档
## 接口信息
**接口路径**: `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 使用记录和模型调用记录