forked from xiaohei/taiji-AI-PAD
更新模型计费
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
# Agent 启动业务流程分析报告
|
||||
|
||||
> **版本**: v1.0.0
|
||||
> **创建时间**: 2026-01-08
|
||||
> **分析目标**: 确认平台 Agent 和自定义 Agent 的启动业务流程实现状态
|
||||
|
||||
---
|
||||
|
||||
## 1. 业务流程概述
|
||||
|
||||
### 1.1 业务逻辑1:平台 Agent 启动流程
|
||||
|
||||
**流程描述**:渠道分配平台 Agent 给租户后,平台 Agent 通过 Agent Manager 服务启动到 AKS 中开始运行。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CA as 渠道管理员
|
||||
participant MCP as mcp-server
|
||||
participant AM as Agent Manager
|
||||
participant AKS as Azure Kubernetes
|
||||
|
||||
CA->>MCP: 1. 申请平台 Agent 配额
|
||||
MCP->>MCP: 2. 创建 ResourceApplication
|
||||
Note over MCP: 状态: pending
|
||||
|
||||
rect rgb(200, 220, 255)
|
||||
Note over MCP: 管理员审批流程
|
||||
MCP->>MCP: 3. 管理员审批通过
|
||||
MCP->>MCP: 4. 创建渠道 PlatformAgentQuota
|
||||
end
|
||||
|
||||
CA->>MCP: 5. 分配平台 Agent 给租户
|
||||
MCP->>MCP: 6. 检查渠道配额
|
||||
MCP->>MCP: 7. 创建租户 PlatformAgentQuota
|
||||
MCP->>AM: 8. 调用 POST /agents 创建 Pod
|
||||
AM->>AKS: 9. 创建 K8s Pod
|
||||
AKS-->>AM: 10. 返回 Pod 状态
|
||||
AM-->>MCP: 11. 返回创建结果
|
||||
MCP->>MCP: 12. 创建 Agent 记录
|
||||
MCP-->>CA: 13. 返回分配成功
|
||||
```
|
||||
|
||||
### 1.2 业务逻辑2:自定义 Agent 启动流程
|
||||
|
||||
**流程描述**:渠道分配自定义 Agent 配额给租户后,租户通过 mcp-server 平台配置相关配置文件后,自定义 Agent 通过 Agent Manager 服务启动到 AKS 中。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CA as 渠道管理员
|
||||
participant T as 租户
|
||||
participant MCP as mcp-server
|
||||
participant AM as Agent Manager
|
||||
participant AKS as Azure Kubernetes
|
||||
|
||||
CA->>MCP: 1. 分配自定义 Agent 配额给租户
|
||||
MCP->>MCP: 2. 创建 TenantCustomAgentQuota
|
||||
Note over MCP: CPU/内存配额
|
||||
|
||||
T->>MCP: 3. 创建自定义 Agent
|
||||
Note over T,MCP: 提供模板、环境变量、资源配置
|
||||
MCP->>MCP: 4. 检查租户配额
|
||||
MCP->>MCP: 5. 查询租户 LiteLLM Key(如指定模型)
|
||||
MCP->>MCP: 6. 构建环境变量(注入 LiteLLM 配置)
|
||||
MCP->>AM: 7. 调用 POST /agents 创建 Pod
|
||||
AM->>AKS: 8. 创建 K8s Pod
|
||||
AKS-->>AM: 9. 返回 Pod 状态
|
||||
AM-->>MCP: 10. 返回创建结果
|
||||
MCP->>MCP: 11. 更新配额使用量
|
||||
MCP->>MCP: 12. 创建 AgentBillingRecord
|
||||
MCP-->>T: 13. 返回创建成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 实现状态分析
|
||||
|
||||
### 2.1 平台 Agent 启动流程 - ✅ 已实现
|
||||
|
||||
| 步骤 | 功能 | 实现文件 | 实现状态 |
|
||||
|------|------|----------|----------|
|
||||
| 1 | 渠道申请平台 Agent 配额 | [`platform_agent_quota.py:239-316`](services/mcp-server/app/routes/platform_agent_quota.py:239) | ✅ 已实现 |
|
||||
| 2 | 管理员审批申请 | [`platform_agent_quota.py:626-721`](services/mcp-server/app/routes/platform_agent_quota.py:626) | ✅ 已实现 |
|
||||
| 3 | 渠道分配平台 Agent 给租户 | [`platform_agent_quota.py:404-579`](services/mcp-server/app/routes/platform_agent_quota.py:404) | ✅ 已实现 |
|
||||
| 4 | 调用 Agent Manager 创建 Pod | [`platform_agent_quota.py:520-524`](services/mcp-server/app/routes/platform_agent_quota.py:520) | ✅ 已实现 |
|
||||
| 5 | 创建 Agent 数据库记录 | [`platform_agent_quota.py:540-551`](services/mcp-server/app/routes/platform_agent_quota.py:540) | ✅ 已实现 |
|
||||
| 6 | 更新配额使用量 | [`platform_agent_quota.py:527-537`](services/mcp-server/app/routes/platform_agent_quota.py:527) | ✅ 已实现 |
|
||||
|
||||
**关键代码位置**:
|
||||
|
||||
- **渠道分配接口**: [`POST /api/channel/tenants/{tenant_id}/platform-agents`](services/mcp-server/app/routes/platform_agent_quota.py:404)
|
||||
- **Agent Manager 客户端**: [`agent_manager_client.py:751-830`](services/mcp-server/app/agent_manager_client.py:751)
|
||||
|
||||
**实现细节**:
|
||||
|
||||
```python
|
||||
# platform_agent_quota.py:520-524 - 调用 Agent Manager 创建 Pod
|
||||
result = await client.create_agent(
|
||||
name=pod_name,
|
||||
template=request.templateName,
|
||||
config=agent_config
|
||||
)
|
||||
```
|
||||
|
||||
### 2.2 自定义 Agent 启动流程 - ✅ 已实现
|
||||
|
||||
| 步骤 | 功能 | 实现文件 | 实现状态 |
|
||||
|------|------|----------|----------|
|
||||
| 1 | 渠道分配自定义 Agent 配额 | [`channel.py:564-651`](services/mcp-server/app/routes/channel.py:564) | ✅ 已实现 |
|
||||
| 2 | 租户查看配额 | [`user.py:342-400`](services/mcp-server/app/routes/user.py:342) | ✅ 已实现 |
|
||||
| 3 | 租户创建自定义 Agent | [`user.py:1539-1712`](services/mcp-server/app/routes/user.py:1539) | ✅ 已实现 |
|
||||
| 4 | 检查配额 | [`user.py:1563-1600`](services/mcp-server/app/routes/user.py:1563) | ✅ 已实现 |
|
||||
| 5 | 查询 LiteLLM Key 并注入环境变量 | [`user.py:1609-1645`](services/mcp-server/app/routes/user.py:1609) | ✅ 已实现 |
|
||||
| 6 | 调用 Agent Manager 创建 Pod | [`user.py:1660-1666`](services/mcp-server/app/routes/user.py:1660) | ✅ 已实现 |
|
||||
| 7 | 更新配额使用量 | [`user.py:1669-1671`](services/mcp-server/app/routes/user.py:1669) | ✅ 已实现 |
|
||||
| 8 | 创建计费记录 | [`user.py:1674-1684`](services/mcp-server/app/routes/user.py:1674) | ✅ 已实现 |
|
||||
|
||||
**关键代码位置**:
|
||||
|
||||
- **创建自定义 Agent 接口**: [`POST /api/user/custom-agents`](services/mcp-server/app/routes/user.py:1539)
|
||||
- **LiteLLM Key 注入**: [`user.py:1609-1645`](services/mcp-server/app/routes/user.py:1609)
|
||||
|
||||
**实现细节**:
|
||||
|
||||
```python
|
||||
# user.py:1631-1639 - 注入 LiteLLM 环境变量
|
||||
env_vars["OPENAI_API_BASE"] = settings.litellm_url
|
||||
env_vars["OPENAI_API_KEY"] = decrypted_key
|
||||
env_vars["MODEL_NAME"] = model_name
|
||||
env_vars["LITELLM_MODEL"] = model_name
|
||||
|
||||
# user.py:1660-1666 - 调用 Agent Manager 创建 Pod
|
||||
result = await client.create_custom_agent(
|
||||
name=req.name,
|
||||
template=req.template,
|
||||
user_id=str(user_id),
|
||||
env_vars=env_vars,
|
||||
config=agent_config
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口清单
|
||||
|
||||
### 3.1 平台 Agent 相关接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 查看可用平台 Agent | GET | `/api/channel/available-platform-agents` | 渠道查看可申请的平台 Agent |
|
||||
| 申请平台 Agent 配额 | POST | `/api/channel/applications/platform-agents` | 渠道申请配额 |
|
||||
| 查看申请列表 | GET | `/api/channel/applications/platform-agents` | 渠道查看自己的申请 |
|
||||
| 审批申请 | PUT | `/api/admin/applications/platform-agents/{id}/review` | 管理员审批 |
|
||||
| 分配给租户 | POST | `/api/channel/tenants/{tenant_id}/platform-agents` | **核心接口:分配并启动 Pod** |
|
||||
| 查看渠道配额 | GET | `/api/channel/platform-agents` | 渠道查看已有配额 |
|
||||
| 租户查看配额 | GET | `/api/user/platform-agents` | 租户查看自己的配额 |
|
||||
| 停止 Agent | DELETE | `/api/user/platform-agents/{agent_name}` | 租户停止 Agent |
|
||||
|
||||
### 3.2 自定义 Agent 相关接口
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 分配配额给租户 | PUT | `/api/channel/tenants/{tenant_id}/resources` | 渠道分配 CPU/内存配额 |
|
||||
| 查看配额 | GET | `/api/user/custom-agent-quota` | 租户查看自己的配额 |
|
||||
| 查看模板 | GET | `/api/user/custom-agents/templates` | 租户查看可用模板 |
|
||||
| 创建自定义 Agent | POST | `/api/user/custom-agents` | **核心接口:创建并启动 Pod** |
|
||||
| 查看 Agent 列表 | GET | `/api/user/custom-agents` | 租户查看自己的 Agent |
|
||||
| 删除 Agent | DELETE | `/api/user/custom-agents/{name}` | 租户删除 Agent |
|
||||
| 扩缩容 | PUT | `/api/user/custom-agents/{name}/scale` | 租户调整资源 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Agent Manager 客户端接口
|
||||
|
||||
mcp-server 通过 [`AgentManagerClient`](services/mcp-server/app/agent_manager_client.py:535) 与 Agent Manager 服务交互:
|
||||
|
||||
| 方法 | Agent Manager 接口 | 说明 |
|
||||
|------|-------------------|------|
|
||||
| `create_agent()` | POST /agents | 创建 Agent Pod |
|
||||
| `create_platform_agent()` | POST /agents | 创建平台 Agent(便捷方法) |
|
||||
| `create_custom_agent()` | POST /agents | 创建自定义 Agent(便捷方法) |
|
||||
| `delete_agent()` | DELETE /agents/{name} | 删除 Agent Pod |
|
||||
| `get_agent_status()` | GET /agents/{name}/status | 获取 Agent 状态 |
|
||||
| `get_agent_metrics()` | GET /agents/{name}/metrics | 获取资源使用情况 |
|
||||
| `list_agents()` | GET /agents | 列出所有 Agent |
|
||||
| `list_platform_templates()` | GET /templates/platform | 获取平台模板 |
|
||||
| `list_custom_templates()` | GET /templates/custom | 获取自定义模板 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据模型
|
||||
|
||||
### 5.1 配额管理
|
||||
|
||||
```
|
||||
PlatformAgentQuota
|
||||
├── target_id: UUID (渠道ID 或 租户ID)
|
||||
├── target_type: str (channel 或 tenant)
|
||||
├── template_name: str (模板名称)
|
||||
├── pod_quota: int (配额上限)
|
||||
├── pod_used: int (已使用)
|
||||
└── allocated_at: datetime
|
||||
|
||||
TenantCustomAgentQuota
|
||||
├── tenant_id: UUID
|
||||
├── cpu_quota: float (CPU 配额,核心数)
|
||||
├── memory_quota: float (内存配额,GB)
|
||||
├── cpu_used: float
|
||||
├── memory_used: float
|
||||
└── agent_count: int
|
||||
```
|
||||
|
||||
### 5.2 Agent 记录
|
||||
|
||||
```
|
||||
Agent
|
||||
├── name: str
|
||||
├── type: str (platform 或 custom)
|
||||
├── template: str
|
||||
├── pod_name: str
|
||||
├── k8s_namespace: str
|
||||
├── k8s_status: str
|
||||
├── service_port: int
|
||||
├── owner_id: UUID (租户ID)
|
||||
└── status: str
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 结论
|
||||
|
||||
### 6.1 实现状态总结
|
||||
|
||||
| 业务流程 | 实现状态 | 说明 |
|
||||
|----------|----------|------|
|
||||
| 平台 Agent 启动流程 | ✅ **完整实现** | 渠道分配时立即启动 Pod |
|
||||
| 自定义 Agent 启动流程 | ✅ **完整实现** | 租户创建时启动 Pod,支持 LiteLLM 集成 |
|
||||
|
||||
### 6.2 关键实现特点
|
||||
|
||||
1. **平台 Agent**:
|
||||
- 渠道分配配额给租户时,**立即调用 Agent Manager 启动 Pod**
|
||||
- 配额管理采用两级结构:渠道配额 → 租户配额
|
||||
- 支持配额使用量追踪(pod_used)
|
||||
|
||||
2. **自定义 Agent**:
|
||||
- 租户创建 Agent 时,**立即调用 Agent Manager 启动 Pod**
|
||||
- 支持 LiteLLM 环境变量自动注入(OPENAI_API_BASE, OPENAI_API_KEY, MODEL_NAME)
|
||||
- 配额管理基于 CPU/内存资源
|
||||
|
||||
3. **LiteLLM 集成**:
|
||||
- 根据 [`Agent-Manager接口变动需求文档.md`](plans/Agent-Manager接口变动需求文档.md) 的设计
|
||||
- 自定义 Agent 创建时会自动注入 LiteLLM 相关环境变量
|
||||
- 环境变量通过 `env_vars` 参数传递给 Agent Manager
|
||||
|
||||
### 6.3 待确认事项
|
||||
|
||||
1. **Agent Manager 服务**:需要确认 Agent Manager 服务是否已部署并正常运行
|
||||
2. **敏感信息处理**:根据需求文档,建议 Agent Manager 使用 K8s Secret 存储敏感环境变量(如 OPENAI_API_KEY)
|
||||
3. **模板配置**:管理员需要通过 `/api/admin/platform-agents/templates/{name}/config` 接口配置平台 Agent 模板的资源限制
|
||||
|
||||
---
|
||||
|
||||
## 7. 相关文件索引
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| [`services/mcp-server/app/routes/platform_agent_quota.py`](services/mcp-server/app/routes/platform_agent_quota.py) | 平台 Agent 配额管理路由 |
|
||||
| [`services/mcp-server/app/routes/user.py`](services/mcp-server/app/routes/user.py) | 用户侧 API(含自定义 Agent) |
|
||||
| [`services/mcp-server/app/routes/channel.py`](services/mcp-server/app/routes/channel.py) | 渠道 API(含配额分配) |
|
||||
| [`services/mcp-server/app/agent_manager_client.py`](services/mcp-server/app/agent_manager_client.py) | Agent Manager 客户端 |
|
||||
| [`plans/Agent-Manager接口变动需求文档.md`](plans/Agent-Manager接口变动需求文档.md) | Agent Manager 接口变动需求 |
|
||||
@@ -0,0 +1,255 @@
|
||||
# 当前计费模式分析报告
|
||||
|
||||
> **版本**: v1.0.0
|
||||
> **创建时间**: 2026-01-08
|
||||
> **分析目标**: 分析当前 Agent 计费模式的实现状态和存在的问题
|
||||
|
||||
---
|
||||
|
||||
## 1. 计费模式概述
|
||||
|
||||
### 1.1 计费表结构
|
||||
|
||||
系统中存在**两个计费相关的表**:
|
||||
|
||||
| 表名 | 用途 | 主要字段 |
|
||||
|------|------|----------|
|
||||
| `billing_records` | 通用计费记录(按调用计费) | `tenant_id`, `agent_name`, `duration`, `eu`, `cost` |
|
||||
| `agent_billing_records` | Agent 运行计费记录(按运行时长计费) | `user_id`, `agent_name`, `start_time`, `end_time`, `duration_seconds`, `eu_consumed`, `cost` |
|
||||
|
||||
### 1.2 计费模型定义
|
||||
|
||||
**[`BillingRecord`](services/mcp-server/models.py:532)** - 通用计费记录:
|
||||
```python
|
||||
class BillingRecord(BaseModel, Base):
|
||||
__tablename__ = "billing_records"
|
||||
|
||||
timestamp = Column(DateTime)
|
||||
channel_id = Column(GUID())
|
||||
tenant_id = Column(GUID())
|
||||
agent_id = Column(GUID())
|
||||
agent_name = Column(String(100))
|
||||
duration = Column(Integer) # 秒
|
||||
eu = Column(Integer) # 执行单元:1 EU = 10秒
|
||||
cost = Column(Numeric(12, 4))
|
||||
```
|
||||
|
||||
**[`AgentBillingRecord`](services/mcp-server/models.py:1150)** - Agent 运行计费记录:
|
||||
```python
|
||||
class AgentBillingRecord(BaseModel, Base):
|
||||
__tablename__ = "agent_billing_records"
|
||||
|
||||
user_id = Column(GUID())
|
||||
channel_id = Column(GUID())
|
||||
agent_name = Column(String(100))
|
||||
agent_type = Column(String(20)) # 模板名称
|
||||
is_platform_agent = Column(Boolean)
|
||||
start_time = Column(DateTime) # Agent 启动时间
|
||||
end_time = Column(DateTime) # Agent 停止时间
|
||||
duration_seconds = Column(Integer)
|
||||
eu_consumed = Column(Integer)
|
||||
cpu_used = Column(String)
|
||||
memory_used = Column(String)
|
||||
cost = Column(Numeric(12, 4))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 计费触发点分析
|
||||
|
||||
### 2.1 平台 Agent 计费
|
||||
|
||||
| 操作 | 触发位置 | 计费记录创建 | 状态 |
|
||||
|------|----------|--------------|------|
|
||||
| 渠道分配平台 Agent 给租户 | [`platform_agent_quota.py:404-579`](services/mcp-server/app/routes/platform_agent_quota.py:404) | ❌ **未创建** | 🔴 缺失 |
|
||||
| 用户使用平台 Agent | [`user.py:1282-1386`](services/mcp-server/app/routes/user.py:1282) | ✅ 创建 `AgentBillingRecord` | ✅ 已实现 |
|
||||
| 停止平台 Agent | [`user.py:1388-1456`](services/mcp-server/app/routes/user.py:1388) | ✅ 更新 `end_time` | ✅ 已实现 |
|
||||
|
||||
### 2.2 自定义 Agent 计费
|
||||
|
||||
| 操作 | 触发位置 | 计费记录创建 | 状态 |
|
||||
|------|----------|--------------|------|
|
||||
| 创建自定义 Agent | [`user.py:1539-1712`](services/mcp-server/app/routes/user.py:1539) | ✅ 创建 `AgentBillingRecord` | ✅ 已实现 |
|
||||
| 删除自定义 Agent | [`user.py:1715-1797`](services/mcp-server/app/routes/user.py:1715) | ✅ 更新 `end_time` | ✅ 已实现 |
|
||||
| 扩缩容自定义 Agent | [`user.py:1800-1924`](services/mcp-server/app/routes/user.py:1800) | ✅ 更新资源使用量 | ✅ 已实现 |
|
||||
|
||||
### 2.3 计费记录创建代码示例
|
||||
|
||||
**自定义 Agent 创建时的计费记录**([`user.py:1674-1684`](services/mcp-server/app/routes/user.py:1674)):
|
||||
```python
|
||||
# 记录计费
|
||||
billing_record = AgentBillingRecord(
|
||||
user_id=user_id,
|
||||
channel_id=channel_id,
|
||||
agent_type=req.template,
|
||||
agent_name=req.name,
|
||||
is_platform_agent=False,
|
||||
start_time=datetime.utcnow(),
|
||||
cpu_used=req.cpuRequest,
|
||||
memory_used=req.memoryRequest,
|
||||
)
|
||||
db.add(billing_record)
|
||||
```
|
||||
|
||||
**平台 Agent 使用时的计费记录**([`user.py:1351-1361`](services/mcp-server/app/routes/user.py:1351)):
|
||||
```python
|
||||
# 记录计费
|
||||
billing_record = AgentBillingRecord(
|
||||
user_id=user_id,
|
||||
channel_id=channel_id,
|
||||
agent_type=req.agentType,
|
||||
agent_name=instance_name,
|
||||
is_platform_agent=True,
|
||||
start_time=datetime.utcnow(),
|
||||
cpu_used=quota.cpu_per_pod or "100m",
|
||||
memory_used=quota.memory_per_pod or "128Mi",
|
||||
)
|
||||
db.add(billing_record)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Dashboard 数据查询分析
|
||||
|
||||
### 3.1 `/api/v1/monitoring/dashboard` 接口
|
||||
|
||||
**接口位置**: [`monitoring.py:65-132`](services/mcp-server/app/routes/monitoring.py:65)
|
||||
|
||||
**查询逻辑**([`monitoring.py:541-552`](services/mcp-server/monitoring.py:541)):
|
||||
```python
|
||||
# 查询 billing_records 表
|
||||
total_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(SUM(eu), 0) as total_eu,
|
||||
COALESCE(SUM(cost), 0) as total_cost,
|
||||
COUNT(*) as total_calls
|
||||
FROM billing_records
|
||||
WHERE tenant_id = :tenant_id
|
||||
AND timestamp > NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
```
|
||||
|
||||
**问题**: 查询的是 `billing_records` 表,但 Agent 运行计费写入的是 `agent_billing_records` 表。
|
||||
|
||||
---
|
||||
|
||||
## 4. EU 计算规则
|
||||
|
||||
### 4.1 基于时长的 EU 计算
|
||||
|
||||
**计算公式**([`billing.py:50-60`](services/mcp-server/app/billing.py:50)):
|
||||
```python
|
||||
def calculate_eu(duration_seconds: int) -> int:
|
||||
"""
|
||||
计算EU:1 EU = 10秒,不足10秒按1 EU计算
|
||||
"""
|
||||
return math.ceil(duration_seconds / 10)
|
||||
```
|
||||
|
||||
### 4.2 成本计算
|
||||
|
||||
**按订阅等级定价**([`billing.py:18-26`](services/mcp-server/app/billing.py:18)):
|
||||
```python
|
||||
EU_PRICING = {
|
||||
"free": Decimal("0.015"), # 入门级:$0.015 / EU
|
||||
"starter": Decimal("0.015"), # 入门级别名
|
||||
"pro": Decimal("0.02"), # 专业级:$0.02 / EU
|
||||
"enterprise": Decimal("0.03"), # 企业级:$0.03 / EU
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 平台 Agent 固定价格
|
||||
|
||||
**按模板定价**([`billing.py:474-480`](services/mcp-server/app/billing.py:474)):
|
||||
```python
|
||||
PLATFORM_AGENT_PRICING = {
|
||||
"gpt-assistant": Decimal("0.10"), # GPT 助手每小时 $0.10
|
||||
"code-reviewer": Decimal("0.15"), # 代码审查每小时 $0.15
|
||||
"data-analyst": Decimal("0.12"), # 数据分析每小时 $0.12
|
||||
"default": Decimal("0.10"), # 默认每小时 $0.10
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 自定义 Agent 资源计费
|
||||
|
||||
**按资源使用量计费**([`billing.py:468-472`](services/mcp-server/app/billing.py:468)):
|
||||
```python
|
||||
AGENT_RESOURCE_PRICING = {
|
||||
"cpu_per_hour": Decimal("0.05"), # CPU 每核每小时 $0.05
|
||||
"memory_per_gb_hour": Decimal("0.01"), # 内存每 GB 每小时 $0.01
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 计费流程图
|
||||
|
||||
### 5.1 平台 Agent 计费流程(设计)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CA as 渠道管理员
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
participant AM as Agent Manager
|
||||
|
||||
CA->>MCP: 分配平台 Agent 给租户
|
||||
MCP->>AM: 创建 Pod
|
||||
AM-->>MCP: Pod 创建成功
|
||||
MCP->>DB: 创建 AgentBillingRecord (start_time)
|
||||
Note over DB: 开始计费
|
||||
|
||||
rect rgb(255, 200, 200)
|
||||
Note over MCP,DB: ⚠️ 当前实现缺失此步骤
|
||||
end
|
||||
|
||||
MCP-->>CA: 分配成功
|
||||
|
||||
Note over DB: Agent 运行中...
|
||||
|
||||
CA->>MCP: 停止 Agent
|
||||
MCP->>AM: 删除 Pod
|
||||
MCP->>DB: 更新 AgentBillingRecord (end_time, duration, cost)
|
||||
Note over DB: 结束计费
|
||||
```
|
||||
|
||||
### 5.2 自定义 Agent 计费流程(已实现)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as 租户
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
participant AM as Agent Manager
|
||||
|
||||
T->>MCP: 创建自定义 Agent
|
||||
MCP->>MCP: 检查配额
|
||||
MCP->>AM: 创建 Pod
|
||||
AM-->>MCP: Pod 创建成功
|
||||
MCP->>DB: 创建 AgentBillingRecord (start_time)
|
||||
Note over DB: 开始计费 ✅
|
||||
MCP-->>T: 创建成功
|
||||
|
||||
Note over DB: Agent 运行中...
|
||||
|
||||
T->>MCP: 删除 Agent
|
||||
MCP->>AM: 删除 Pod
|
||||
MCP->>DB: 更新 AgentBillingRecord (end_time, duration, cost)
|
||||
Note over DB: 结束计费 ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文件索引
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| [`services/mcp-server/models.py`](services/mcp-server/models.py) | 数据模型定义(BillingRecord, AgentBillingRecord) |
|
||||
| [`services/mcp-server/app/billing.py`](services/mcp-server/app/billing.py) | 计费逻辑(EU 计算、成本计算) |
|
||||
| [`services/mcp-server/monitoring.py`](services/mcp-server/monitoring.py) | 监控模块(Dashboard 数据查询) |
|
||||
| [`services/mcp-server/app/routes/monitoring.py`](services/mcp-server/app/routes/monitoring.py) | 监控 API 路由 |
|
||||
| [`services/mcp-server/app/routes/user.py`](services/mcp-server/app/routes/user.py) | 用户 API(Agent 创建/删除计费) |
|
||||
| [`services/mcp-server/app/routes/platform_agent_quota.py`](services/mcp-server/app/routes/platform_agent_quota.py) | 平台 Agent 配额管理 |
|
||||
@@ -0,0 +1,444 @@
|
||||
# 模型 Token 计费方案分析
|
||||
|
||||
> **版本**: v1.0.0
|
||||
> **创建时间**: 2026-01-08
|
||||
> **分析目标**: 分析模型 Token 使用量计费的实现方案
|
||||
|
||||
---
|
||||
|
||||
## 1. 当前系统架构
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "mcp-server"
|
||||
A[租户 Agent] --> B[LiteLLM Client]
|
||||
B --> C[TenantModelKey 表]
|
||||
end
|
||||
|
||||
subgraph "LiteLLM Gateway"
|
||||
D[/chat/completions] --> E[模型路由]
|
||||
E --> F[OpenRouter/OpenAI/Anthropic]
|
||||
G[/spend/logs] --> H[用量数据]
|
||||
I[PostgreSQL] --> H
|
||||
end
|
||||
|
||||
B -->|API Key| D
|
||||
B -->|查询用量| G
|
||||
```
|
||||
|
||||
### 1.1 当前 LiteLLM 集成状态
|
||||
|
||||
| 功能 | 实现状态 | 说明 |
|
||||
|------|----------|------|
|
||||
| Team 管理 | ✅ 已实现 | 渠道 = LiteLLM Team |
|
||||
| Key 管理 | ✅ 已实现 | 租户 = LiteLLM Key |
|
||||
| RPM/TPM 限制 | ✅ 已实现 | 通过 Key 配置 |
|
||||
| Budget 限制 | ✅ 已实现 | max_budget + budget_duration |
|
||||
| 用量查询 | ⚠️ 部分实现 | [`get_spend_logs()`](services/mcp-server/app/litellm_client.py:446) 已有,但未集成到计费 |
|
||||
|
||||
### 1.2 LiteLLM 配置
|
||||
|
||||
当前配置([`litellm.yaml`](services/model-gateway/config/litellm.yaml))已启用:
|
||||
- `database_url`: 使用 PostgreSQL 存储用量数据
|
||||
- `success_callback: ["langfuse"]`: 成功回调
|
||||
- `failure_callback: ["langfuse"]`: 失败回调
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案对比分析
|
||||
|
||||
### 方案 1:读取 LiteLLM 网关日志(实时记录)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Agent as 租户 Agent
|
||||
participant LiteLLM as LiteLLM Gateway
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
|
||||
Agent->>LiteLLM: 调用模型 API
|
||||
LiteLLM->>LiteLLM: 记录日志
|
||||
LiteLLM-->>Agent: 返回结果
|
||||
|
||||
Note over LiteLLM,MCP: 方案1A: 日志文件监听
|
||||
LiteLLM->>MCP: 日志文件变化
|
||||
MCP->>DB: 解析并写入计费记录
|
||||
|
||||
Note over LiteLLM,MCP: 方案1B: Webhook 回调
|
||||
LiteLLM->>MCP: POST /webhook/litellm
|
||||
MCP->>DB: 写入计费记录
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 实时性高,调用完成立即记录
|
||||
- ✅ 数据准确,直接从源头获取
|
||||
- ✅ 可以获取详细的请求/响应信息
|
||||
|
||||
**缺点**:
|
||||
- ❌ 需要配置日志监听或 Webhook
|
||||
- ❌ 日志解析复杂,格式可能变化
|
||||
- ❌ 增加系统耦合度
|
||||
|
||||
**实现复杂度**: 中等
|
||||
|
||||
---
|
||||
|
||||
### 方案 2:读取 LiteLLM 数据库
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Agent as 租户 Agent
|
||||
participant LiteLLM as LiteLLM Gateway
|
||||
participant LLMDB as LiteLLM PostgreSQL
|
||||
participant MCP as mcp-server
|
||||
participant DB as mcp-server DB
|
||||
|
||||
Agent->>LiteLLM: 调用模型 API
|
||||
LiteLLM->>LLMDB: 记录用量
|
||||
LiteLLM-->>Agent: 返回结果
|
||||
|
||||
Note over MCP: 定时任务(每分钟)
|
||||
MCP->>LLMDB: 查询新增用量记录
|
||||
MCP->>DB: 同步到计费表
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 数据完整,包含所有历史记录
|
||||
- ✅ 可以批量同步,减少 API 调用
|
||||
- ✅ 不依赖 LiteLLM API 可用性
|
||||
|
||||
**缺点**:
|
||||
- ❌ 需要直接访问 LiteLLM 数据库
|
||||
- ❌ 依赖 LiteLLM 内部表结构(可能变化)
|
||||
- ❌ 实时性较差(取决于同步频率)
|
||||
- ❌ 数据库耦合,升级 LiteLLM 可能出问题
|
||||
|
||||
**实现复杂度**: 中等
|
||||
|
||||
---
|
||||
|
||||
### 方案 3:使用 LiteLLM Spend API(推荐)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Agent as 租户 Agent
|
||||
participant LiteLLM as LiteLLM Gateway
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
|
||||
Agent->>LiteLLM: 调用模型 API
|
||||
LiteLLM-->>Agent: 返回结果
|
||||
|
||||
Note over MCP: 定时任务或按需查询
|
||||
MCP->>LiteLLM: GET /spend/logs?api_key=xxx
|
||||
LiteLLM-->>MCP: 返回用量数据
|
||||
MCP->>DB: 写入计费记录
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 使用官方 API,稳定可靠
|
||||
- ✅ 已有 [`get_spend_logs()`](services/mcp-server/app/litellm_client.py:446) 实现
|
||||
- ✅ 不依赖内部实现细节
|
||||
- ✅ 支持按 Key、Team、时间范围查询
|
||||
|
||||
**缺点**:
|
||||
- ❌ 实时性取决于查询频率
|
||||
- ❌ 需要定时任务或触发机制
|
||||
- ❌ 大量 Key 时 API 调用开销大
|
||||
|
||||
**实现复杂度**: 低
|
||||
|
||||
---
|
||||
|
||||
### 方案 4:LiteLLM Callback/Webhook(推荐)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Agent as 租户 Agent
|
||||
participant LiteLLM as LiteLLM Gateway
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
|
||||
Agent->>LiteLLM: 调用模型 API
|
||||
LiteLLM-->>Agent: 返回结果
|
||||
|
||||
Note over LiteLLM,MCP: 异步回调
|
||||
LiteLLM->>MCP: POST /api/v1/billing/litellm-callback
|
||||
Note right of MCP: 包含: api_key, model, tokens, cost
|
||||
MCP->>DB: 写入计费记录
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 实时性最高,调用完成立即通知
|
||||
- ✅ LiteLLM 原生支持 custom callback
|
||||
- ✅ 数据准确,包含完整的用量信息
|
||||
- ✅ 解耦设计,mcp-server 被动接收
|
||||
|
||||
**缺点**:
|
||||
- ❌ 需要配置 LiteLLM callback
|
||||
- ❌ 需要处理回调失败重试
|
||||
- ❌ mcp-server 需要暴露 webhook 端点
|
||||
|
||||
**实现复杂度**: 中等
|
||||
|
||||
---
|
||||
|
||||
### 方案 5:Agent 侧上报(补充方案)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Agent as 租户 Agent
|
||||
participant LiteLLM as LiteLLM Gateway
|
||||
participant MCP as mcp-server
|
||||
participant DB as 数据库
|
||||
|
||||
Agent->>LiteLLM: 调用模型 API
|
||||
LiteLLM-->>Agent: 返回结果(含 usage)
|
||||
|
||||
Note over Agent: 解析 response.usage
|
||||
Agent->>MCP: POST /api/v1/billing/report-usage
|
||||
Note right of MCP: 包含: model, input_tokens, output_tokens
|
||||
MCP->>DB: 写入计费记录
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 不依赖 LiteLLM 配置
|
||||
- ✅ Agent 可以添加业务上下文
|
||||
- ✅ 灵活性高
|
||||
|
||||
**缺点**:
|
||||
- ❌ 依赖 Agent 正确上报
|
||||
- ❌ 可能被绕过或伪造
|
||||
- ❌ 需要修改所有 Agent 代码
|
||||
|
||||
**实现复杂度**: 高(需要修改 Agent)
|
||||
|
||||
---
|
||||
|
||||
## 3. 推荐方案
|
||||
|
||||
### 3.1 短期方案:LiteLLM Spend API(方案 3)
|
||||
|
||||
**理由**:
|
||||
1. 已有 [`get_spend_logs()`](services/mcp-server/app/litellm_client.py:446) 实现
|
||||
2. 实现成本最低
|
||||
3. 可以快速上线
|
||||
|
||||
**实现步骤**:
|
||||
|
||||
1. **创建定时任务**:每分钟同步用量数据
|
||||
2. **修改 Dashboard 查询**:从同步后的数据查询
|
||||
3. **添加增量同步逻辑**:记录上次同步时间
|
||||
|
||||
```python
|
||||
# 伪代码示例
|
||||
async def sync_litellm_spend():
|
||||
"""定时同步 LiteLLM 用量数据"""
|
||||
litellm_client = get_litellm_client()
|
||||
|
||||
# 获取所有租户的 Key
|
||||
tenant_keys = await db.execute(select(TenantModelKey))
|
||||
|
||||
for key in tenant_keys:
|
||||
# 查询该 Key 的用量
|
||||
spend_logs = await litellm_client.get_spend_logs(
|
||||
api_key=key.litellm_key_id,
|
||||
start_date=last_sync_time
|
||||
)
|
||||
|
||||
# 写入计费记录
|
||||
for log in spend_logs:
|
||||
billing_record = ModelBillingRecord(
|
||||
tenant_id=key.tenant_id,
|
||||
model_name=log["model"],
|
||||
input_tokens=log["prompt_tokens"],
|
||||
output_tokens=log["completion_tokens"],
|
||||
cost=log["spend"],
|
||||
timestamp=log["created_at"]
|
||||
)
|
||||
db.add(billing_record)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 长期方案:LiteLLM Callback(方案 4)
|
||||
|
||||
**理由**:
|
||||
1. 实时性最好
|
||||
2. 架构更优雅
|
||||
3. 可扩展性强
|
||||
|
||||
**实现步骤**:
|
||||
|
||||
1. **配置 LiteLLM Callback**
|
||||
|
||||
修改 [`litellm.yaml`](services/model-gateway/config/litellm.yaml):
|
||||
|
||||
```yaml
|
||||
general_settings:
|
||||
# ... 其他配置
|
||||
|
||||
# 添加自定义回调
|
||||
success_callback: ["langfuse", "custom_callback"]
|
||||
failure_callback: ["langfuse"]
|
||||
|
||||
# 自定义回调配置
|
||||
callbacks:
|
||||
custom_callback:
|
||||
callback_name: "custom_callback"
|
||||
callback_type: "success"
|
||||
callback_vars:
|
||||
callback_url: "http://mcp-server:8000/api/v1/billing/litellm-callback"
|
||||
callback_api_key: "os.environ/MCP_CALLBACK_KEY"
|
||||
```
|
||||
|
||||
2. **在 mcp-server 添加 Webhook 端点**
|
||||
|
||||
```python
|
||||
# services/mcp-server/app/routes/billing_webhook.py
|
||||
|
||||
@router.post("/api/v1/billing/litellm-callback")
|
||||
async def litellm_callback(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
接收 LiteLLM 的用量回调
|
||||
|
||||
LiteLLM 会在每次成功调用后发送:
|
||||
- api_key: 使用的 API Key
|
||||
- model: 模型名称
|
||||
- prompt_tokens: 输入 Token 数
|
||||
- completion_tokens: 输出 Token 数
|
||||
- total_tokens: 总 Token 数
|
||||
- spend: 花费金额
|
||||
- metadata: 元数据(包含 tenant_id)
|
||||
"""
|
||||
data = await request.json()
|
||||
|
||||
# 根据 api_key 查找租户
|
||||
api_key = data.get("api_key")
|
||||
tenant_key = await db.execute(
|
||||
select(TenantModelKey).where(
|
||||
TenantModelKey.litellm_key_id == api_key
|
||||
)
|
||||
)
|
||||
|
||||
if not tenant_key:
|
||||
return {"status": "ignored", "reason": "unknown_key"}
|
||||
|
||||
# 创建计费记录
|
||||
billing_record = ModelBillingRecord(
|
||||
tenant_id=tenant_key.tenant_id,
|
||||
channel_id=tenant_key.channel_id,
|
||||
model_name=data.get("model"),
|
||||
input_tokens=data.get("prompt_tokens", 0),
|
||||
output_tokens=data.get("completion_tokens", 0),
|
||||
total_tokens=data.get("total_tokens", 0),
|
||||
cost=data.get("spend", 0),
|
||||
timestamp=datetime.utcnow()
|
||||
)
|
||||
db.add(billing_record)
|
||||
await db.commit()
|
||||
|
||||
return {"status": "ok"}
|
||||
```
|
||||
|
||||
3. **创建新的计费表**
|
||||
|
||||
```sql
|
||||
-- 模型调用计费记录表
|
||||
CREATE TABLE model_billing_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES users(id),
|
||||
channel_id UUID REFERENCES channels(id),
|
||||
|
||||
-- 模型信息
|
||||
model_name VARCHAR(100) NOT NULL,
|
||||
|
||||
-- Token 使用量
|
||||
input_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
output_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
total_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- 费用
|
||||
cost NUMERIC(12, 6) NOT NULL DEFAULT 0,
|
||||
eu_consumed NUMERIC(10, 4) DEFAULT 0,
|
||||
|
||||
-- 时间
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- 索引
|
||||
INDEX idx_model_billing_tenant (tenant_id),
|
||||
INDEX idx_model_billing_channel (channel_id),
|
||||
INDEX idx_model_billing_model (model_name),
|
||||
INDEX idx_model_billing_timestamp (timestamp)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 方案对比总结
|
||||
|
||||
| 方案 | 实时性 | 可靠性 | 实现复杂度 | 维护成本 | 推荐度 |
|
||||
|------|--------|--------|------------|----------|--------|
|
||||
| 方案1: 日志监听 | ⭐⭐⭐⭐ | ⭐⭐ | 高 | 高 | ⭐⭐ |
|
||||
| 方案2: 读数据库 | ⭐⭐⭐ | ⭐⭐⭐ | 中 | 高 | ⭐⭐ |
|
||||
| **方案3: Spend API** | ⭐⭐⭐ | ⭐⭐⭐⭐ | **低** | 低 | ⭐⭐⭐⭐ |
|
||||
| **方案4: Callback** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 中 | 中 | ⭐⭐⭐⭐⭐ |
|
||||
| 方案5: Agent上报 | ⭐⭐⭐⭐ | ⭐⭐ | 高 | 高 | ⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施建议
|
||||
|
||||
### 5.1 第一阶段:快速上线(1-2天)
|
||||
|
||||
使用 **方案 3(Spend API)**:
|
||||
|
||||
1. 创建定时任务,每分钟调用 `get_spend_logs()`
|
||||
2. 将数据写入 `model_billing_records` 表
|
||||
3. 修改 Dashboard 查询,合并 Agent 计费和模型计费
|
||||
|
||||
### 5.2 第二阶段:优化升级(1周)
|
||||
|
||||
升级到 **方案 4(Callback)**:
|
||||
|
||||
1. 配置 LiteLLM custom callback
|
||||
2. 实现 webhook 端点
|
||||
3. 添加重试和幂等处理
|
||||
4. 保留 Spend API 作为数据校验
|
||||
|
||||
### 5.3 数据模型设计
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
users ||--o{ model_billing_records : has
|
||||
channels ||--o{ model_billing_records : has
|
||||
tenant_model_keys ||--o{ model_billing_records : generates
|
||||
|
||||
model_billing_records {
|
||||
uuid id PK
|
||||
uuid tenant_id FK
|
||||
uuid channel_id FK
|
||||
string model_name
|
||||
int input_tokens
|
||||
int output_tokens
|
||||
int total_tokens
|
||||
decimal cost
|
||||
decimal eu_consumed
|
||||
timestamp timestamp
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| [`services/mcp-server/app/litellm_client.py`](services/mcp-server/app/litellm_client.py) | LiteLLM 客户端(已有 get_spend_logs) |
|
||||
| [`services/model-gateway/config/litellm.yaml`](services/model-gateway/config/litellm.yaml) | LiteLLM 配置(需添加 callback) |
|
||||
| [`services/mcp-server/monitoring.py`](services/mcp-server/monitoring.py) | Dashboard 数据查询(需修改) |
|
||||
| [`services/mcp-server/models.py`](services/mcp-server/models.py) | 数据模型(需添加新表) |
|
||||
@@ -0,0 +1,420 @@
|
||||
# 计费系统问题清单
|
||||
|
||||
> **版本**: v1.0.0
|
||||
> **创建时间**: 2026-01-08
|
||||
> **分析目标**: 列出当前计费系统存在的问题和修复建议
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题概述
|
||||
|
||||
| 问题编号 | 严重程度 | 问题描述 | 影响范围 |
|
||||
|----------|----------|----------|----------|
|
||||
| BUG-001 | 🔴 严重 | Dashboard EU 消耗显示为 0 | 所有租户 |
|
||||
| BUG-002 | 🔴 严重 | 平台 Agent 分配时未创建计费记录 | 平台 Agent 用户 |
|
||||
| BUG-003 | 🟡 中等 | 两个计费表数据不同步 | 数据统计 |
|
||||
| BUG-004 | 🟡 中等 | 计费记录缺少 model_name 字段 | 模型使用统计 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 问题详细分析
|
||||
|
||||
### 2.1 BUG-001: Dashboard EU 消耗显示为 0
|
||||
|
||||
**问题描述**:
|
||||
`/api/v1/monitoring/dashboard` 接口返回的 `euConsumption24h` 数据始终为 0。
|
||||
|
||||
**根本原因**:
|
||||
- Dashboard 查询的是 `billing_records` 表
|
||||
- Agent 运行计费写入的是 `agent_billing_records` 表
|
||||
- 两个表是**完全独立的**,数据不互通
|
||||
|
||||
**问题代码位置**:
|
||||
[`monitoring.py:541-552`](services/mcp-server/monitoring.py:541)
|
||||
|
||||
```python
|
||||
# 当前代码:查询 billing_records 表
|
||||
total_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(SUM(eu), 0) as total_eu,
|
||||
COALESCE(SUM(cost), 0) as total_cost,
|
||||
COUNT(*) as total_calls
|
||||
FROM billing_records -- ❌ 错误的表
|
||||
WHERE tenant_id = :tenant_id
|
||||
AND timestamp > NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
修改查询逻辑,同时查询两个表或改为查询 `agent_billing_records` 表:
|
||||
|
||||
```python
|
||||
# 方案1:查询 agent_billing_records 表
|
||||
total_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(SUM(eu_consumed), 0) as total_eu,
|
||||
COALESCE(SUM(cost), 0) as total_cost,
|
||||
COUNT(*) as total_calls
|
||||
FROM agent_billing_records
|
||||
WHERE user_id = :tenant_id
|
||||
AND start_time > NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
|
||||
# 方案2:合并查询两个表
|
||||
total_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(SUM(eu), 0) as total_eu,
|
||||
COALESCE(SUM(cost), 0) as total_cost,
|
||||
COUNT(*) as total_calls
|
||||
FROM (
|
||||
SELECT eu, cost FROM billing_records
|
||||
WHERE tenant_id = :tenant_id AND timestamp > NOW() - INTERVAL '24 hours'
|
||||
UNION ALL
|
||||
SELECT eu_consumed as eu, cost FROM agent_billing_records
|
||||
WHERE user_id = :tenant_id AND start_time > NOW() - INTERVAL '24 hours'
|
||||
) combined
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 BUG-002: 平台 Agent 分配时未创建计费记录
|
||||
|
||||
**问题描述**:
|
||||
当渠道管理员通过 `POST /api/channel/tenants/{tenant_id}/platform-agents` 分配平台 Agent 给租户时,Pod 已启动但**没有创建计费记录**。
|
||||
|
||||
**根本原因**:
|
||||
[`platform_agent_quota.py:404-579`](services/mcp-server/app/routes/platform_agent_quota.py:404) 中的 `allocate_platform_agent_to_tenant` 函数缺少计费记录创建逻辑。
|
||||
|
||||
**问题代码位置**:
|
||||
```python
|
||||
# platform_agent_quota.py:540-568
|
||||
# 创建 Agent 记录
|
||||
agent = Agent(
|
||||
name=pod_name,
|
||||
type="platform",
|
||||
template=request.templateName,
|
||||
pod_name=result.name,
|
||||
k8s_namespace=result.namespace,
|
||||
k8s_status=result.status,
|
||||
service_port=result.service_port,
|
||||
owner_id=tenant_uuid,
|
||||
status="active"
|
||||
)
|
||||
db.add(agent)
|
||||
|
||||
# ❌ 缺少:创建 AgentBillingRecord
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
在创建 Agent 记录后,添加计费记录创建逻辑:
|
||||
|
||||
```python
|
||||
# 在 agent = Agent(...) 之后添加:
|
||||
|
||||
# 创建计费记录
|
||||
from models import AgentBillingRecord
|
||||
|
||||
billing_record = AgentBillingRecord(
|
||||
user_id=tenant_uuid,
|
||||
channel_id=channel_uuid,
|
||||
agent_type=request.templateName,
|
||||
agent_name=pod_name,
|
||||
is_platform_agent=True,
|
||||
start_time=datetime.utcnow(),
|
||||
cpu_used=agent_config.cpu_request,
|
||||
memory_used=agent_config.memory_request,
|
||||
)
|
||||
db.add(billing_record)
|
||||
```
|
||||
|
||||
**同时需要修改**:
|
||||
在 [`platform_agent_quota.py:987-1068`](services/mcp-server/app/routes/platform_agent_quota.py:987) 的 `stop_platform_agent` 函数中,添加计费记录结束逻辑:
|
||||
|
||||
```python
|
||||
# 在删除 Agent 记录之前,更新计费记录
|
||||
billing_result = await db.execute(
|
||||
select(AgentBillingRecord).where(
|
||||
and_(
|
||||
AgentBillingRecord.agent_name == agent_name,
|
||||
AgentBillingRecord.user_id == uuid.UUID(user_id),
|
||||
AgentBillingRecord.is_platform_agent == True,
|
||||
AgentBillingRecord.end_time == None
|
||||
)
|
||||
)
|
||||
)
|
||||
billing_record = billing_result.scalar_one_or_none()
|
||||
|
||||
if billing_record:
|
||||
billing_record.end_time = datetime.utcnow()
|
||||
duration = (billing_record.end_time - billing_record.start_time).total_seconds()
|
||||
billing_record.duration_seconds = int(duration)
|
||||
billing_record.eu_consumed = _calculate_eu(int(duration))
|
||||
# 计算成本
|
||||
from app.billing import calculate_platform_agent_cost
|
||||
billing_record.cost = calculate_platform_agent_cost(billing_record.agent_type, int(duration))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 BUG-003: 两个计费表数据不同步
|
||||
|
||||
**问题描述**:
|
||||
系统存在两个计费表 `billing_records` 和 `agent_billing_records`,但数据写入逻辑不一致:
|
||||
- `billing_records`:用于按调用计费(如 API 调用)
|
||||
- `agent_billing_records`:用于按运行时长计费(Agent 运行)
|
||||
|
||||
**影响**:
|
||||
- Dashboard 统计数据不完整
|
||||
- 计费报表数据不准确
|
||||
- 难以进行统一的计费分析
|
||||
|
||||
**修复建议**:
|
||||
|
||||
**方案1:统一使用 `agent_billing_records` 表**
|
||||
- 修改所有计费相关查询,使用 `agent_billing_records` 表
|
||||
- 将 `billing_records` 表标记为废弃
|
||||
|
||||
**方案2:创建计费视图**
|
||||
```sql
|
||||
CREATE VIEW unified_billing AS
|
||||
SELECT
|
||||
id,
|
||||
tenant_id as user_id,
|
||||
channel_id,
|
||||
agent_name,
|
||||
NULL as agent_type,
|
||||
duration as duration_seconds,
|
||||
eu as eu_consumed,
|
||||
cost,
|
||||
timestamp as start_time,
|
||||
NULL as end_time,
|
||||
'api_call' as billing_type
|
||||
FROM billing_records
|
||||
UNION ALL
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
channel_id,
|
||||
agent_name,
|
||||
agent_type,
|
||||
duration_seconds,
|
||||
eu_consumed,
|
||||
cost,
|
||||
start_time,
|
||||
end_time,
|
||||
CASE WHEN is_platform_agent THEN 'platform_agent' ELSE 'custom_agent' END as billing_type
|
||||
FROM agent_billing_records;
|
||||
```
|
||||
|
||||
**方案3:双写机制**
|
||||
- Agent 启动时同时写入两个表
|
||||
- 保持数据一致性
|
||||
|
||||
---
|
||||
|
||||
### 2.4 BUG-004: 计费记录缺少 model_name 字段
|
||||
|
||||
**问题描述**:
|
||||
Dashboard 的模型使用统计查询 `billing_records.model_name` 字段,但该字段在表中不存在。
|
||||
|
||||
**问题代码位置**:
|
||||
[`monitoring.py:605-619`](services/mcp-server/monitoring.py:605)
|
||||
|
||||
```python
|
||||
# 查询模型使用统计
|
||||
result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(model_name, 'unknown') as model, -- ❌ 字段不存在
|
||||
COUNT(*) as calls,
|
||||
COALESCE(SUM(eu), 0) as eu_consumed,
|
||||
COALESCE(SUM(cost), 0) as cost
|
||||
FROM billing_records
|
||||
WHERE tenant_id = :tenant_id
|
||||
AND timestamp > NOW() - INTERVAL '30 days'
|
||||
GROUP BY model_name
|
||||
ORDER BY calls DESC
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
|
||||
**方案1:添加 model_name 字段到 billing_records 表**
|
||||
```sql
|
||||
ALTER TABLE billing_records ADD COLUMN model_name VARCHAR(100);
|
||||
```
|
||||
|
||||
**方案2:从 agent_billing_records 查询**
|
||||
修改查询逻辑,使用 `agent_billing_records.agent_type` 作为模型/模板名称。
|
||||
|
||||
---
|
||||
|
||||
## 3. 修复优先级
|
||||
|
||||
| 优先级 | 问题编号 | 修复工作量 | 建议时间 |
|
||||
|--------|----------|------------|----------|
|
||||
| P0 | BUG-001 | 小 | 立即修复 |
|
||||
| P0 | BUG-002 | 中 | 立即修复 |
|
||||
| P1 | BUG-003 | 大 | 下一迭代 |
|
||||
| P2 | BUG-004 | 小 | 下一迭代 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 修复代码示例
|
||||
|
||||
### 4.1 修复 BUG-001:Dashboard EU 消耗查询
|
||||
|
||||
**文件**: [`services/mcp-server/monitoring.py`](services/mcp-server/monitoring.py)
|
||||
|
||||
**修改位置**: `_get_tenant_eu_consumption_24h` 方法(约第 527-589 行)
|
||||
|
||||
```python
|
||||
async def _get_tenant_eu_consumption_24h(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
tenant_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取租户过去24小时的EU消耗情况
|
||||
|
||||
修复:改为查询 agent_billing_records 表
|
||||
"""
|
||||
try:
|
||||
# 总消耗 - 修改为查询 agent_billing_records
|
||||
total_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(SUM(eu_consumed), 0) as total_eu,
|
||||
COALESCE(SUM(cost), 0) as total_cost,
|
||||
COUNT(*) as total_calls
|
||||
FROM agent_billing_records
|
||||
WHERE user_id = :tenant_id
|
||||
AND start_time > NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
total_row = total_result.fetchone()
|
||||
|
||||
# 每小时消耗 - 修改为查询 agent_billing_records
|
||||
hourly_result = await session.execute(
|
||||
text("""
|
||||
SELECT
|
||||
DATE_TRUNC('hour', start_time) as hour,
|
||||
COALESCE(SUM(eu_consumed), 0) as eu,
|
||||
COALESCE(SUM(cost), 0) as cost,
|
||||
COUNT(*) as calls
|
||||
FROM agent_billing_records
|
||||
WHERE user_id = :tenant_id
|
||||
AND start_time > NOW() - INTERVAL '24 hours'
|
||||
GROUP BY DATE_TRUNC('hour', start_time)
|
||||
ORDER BY hour
|
||||
"""),
|
||||
{"tenant_id": tenant_id}
|
||||
)
|
||||
hourly_rows = hourly_result.fetchall()
|
||||
|
||||
return {
|
||||
"total": float(total_row[0]) if total_row else 0,
|
||||
"totalCost": float(total_row[1]) if total_row else 0,
|
||||
"totalCalls": int(total_row[2]) if total_row else 0,
|
||||
"hourlyData": [
|
||||
{
|
||||
"timestamp": row[0].isoformat() if row[0] else None,
|
||||
"value": float(row[1]),
|
||||
"cost": float(row[2]),
|
||||
"calls": int(row[3])
|
||||
}
|
||||
for row in hourly_rows
|
||||
]
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"获取租户EU消耗失败: {e}")
|
||||
return {"total": 0, "totalCost": 0, "totalCalls": 0, "hourlyData": []}
|
||||
```
|
||||
|
||||
### 4.2 修复 BUG-002:平台 Agent 分配时创建计费记录
|
||||
|
||||
**文件**: [`services/mcp-server/app/routes/platform_agent_quota.py`](services/mcp-server/app/routes/platform_agent_quota.py)
|
||||
|
||||
**修改位置**: `allocate_platform_agent_to_tenant` 函数(约第 540-551 行之后)
|
||||
|
||||
```python
|
||||
# 在创建 Agent 记录后添加:
|
||||
|
||||
# 创建计费记录
|
||||
billing_record = AgentBillingRecord(
|
||||
user_id=tenant_uuid,
|
||||
channel_id=channel_uuid,
|
||||
agent_type=request.templateName,
|
||||
agent_name=pod_name,
|
||||
is_platform_agent=True,
|
||||
start_time=datetime.utcnow(),
|
||||
cpu_used=agent_config.cpu_request,
|
||||
memory_used=agent_config.memory_request,
|
||||
duration_seconds=0, # 初始为 0,停止时更新
|
||||
eu_consumed=0, # 初始为 0,停止时计算
|
||||
cost=0, # 初始为 0,停止时计算
|
||||
)
|
||||
db.add(billing_record)
|
||||
|
||||
logger.info(
|
||||
"创建平台 Agent 计费记录",
|
||||
tenant_id=tenant_id,
|
||||
agent_name=pod_name,
|
||||
template=request.templateName
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试验证
|
||||
|
||||
修复后需要验证以下场景:
|
||||
|
||||
### 5.1 平台 Agent 计费测试
|
||||
|
||||
1. 渠道分配平台 Agent 给租户
|
||||
2. 验证 `agent_billing_records` 表有新记录
|
||||
3. 调用 `/api/v1/monitoring/dashboard` 验证 EU 消耗不为 0
|
||||
4. 停止平台 Agent
|
||||
5. 验证计费记录的 `end_time`、`duration_seconds`、`cost` 已更新
|
||||
|
||||
### 5.2 自定义 Agent 计费测试
|
||||
|
||||
1. 租户创建自定义 Agent
|
||||
2. 验证 `agent_billing_records` 表有新记录
|
||||
3. 调用 `/api/v1/monitoring/dashboard` 验证 EU 消耗不为 0
|
||||
4. 删除自定义 Agent
|
||||
5. 验证计费记录已正确结束
|
||||
|
||||
### 5.3 Dashboard 数据测试
|
||||
|
||||
1. 登录租户账号
|
||||
2. 调用 `/api/v1/monitoring/dashboard`
|
||||
3. 验证返回数据:
|
||||
- `euConsumption24h.total` > 0(如果有运行中的 Agent)
|
||||
- `euConsumption24h.hourlyData` 有数据
|
||||
- `modelUsage.models` 有数据
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文件
|
||||
|
||||
| 文件 | 需要修改 | 说明 |
|
||||
|------|----------|------|
|
||||
| [`services/mcp-server/monitoring.py`](services/mcp-server/monitoring.py) | ✅ 是 | 修改 EU 消耗查询逻辑 |
|
||||
| [`services/mcp-server/app/routes/platform_agent_quota.py`](services/mcp-server/app/routes/platform_agent_quota.py) | ✅ 是 | 添加计费记录创建 |
|
||||
| [`services/mcp-server/models.py`](services/mcp-server/models.py) | ❌ 否 | 模型定义无需修改 |
|
||||
| [`services/mcp-server/app/billing.py`](services/mcp-server/app/billing.py) | ❌ 否 | 计费逻辑无需修改 |
|
||||
Reference in New Issue
Block a user