更新模型计费

This commit is contained in:
zhanggangyong
2026-01-08 15:03:52 +00:00
parent c94367994e
commit 625afdf441
12 changed files with 5694 additions and 730 deletions
@@ -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 接口变动需求 |
+255
View File
@@ -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 配额管理 |
+444
View File
@@ -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) | 数据模型(需添加新表) |
+420
View File
@@ -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) | ❌ 否 | 计费逻辑无需修改 |