Files
taiji-AI-PAD/plans/模型Token计费方案分析.md
T
2026-01-08 15:03:52 +00:00

12 KiB
Raw Blame History

模型 Token 计费方案分析

版本: v1.0.0 创建时间: 2026-01-08 分析目标: 分析模型 Token 使用量计费的实现方案


1. 当前系统架构

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() 已有,但未集成到计费

1.2 LiteLLM 配置

当前配置(litellm.yaml)已启用:

  • database_url: 使用 PostgreSQL 存储用量数据
  • success_callback: ["langfuse"]: 成功回调
  • failure_callback: ["langfuse"]: 失败回调

2. 方案对比分析

方案 1:读取 LiteLLM 网关日志(实时记录)

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 数据库

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(推荐)

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() 实现
  • ✅ 不依赖内部实现细节
  • ✅ 支持按 Key、Team、时间范围查询

缺点:

  • ❌ 实时性取决于查询频率
  • ❌ 需要定时任务或触发机制
  • ❌ 大量 Key 时 API 调用开销大

实现复杂度: 低


方案 4:LiteLLM Callback/Webhook(推荐)

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 侧上报(补充方案)

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() 实现
  2. 实现成本最低
  3. 可以快速上线

实现步骤:

  1. 创建定时任务:每分钟同步用量数据
  2. 修改 Dashboard 查询:从同步后的数据查询
  3. 添加增量同步逻辑:记录上次同步时间
# 伪代码示例
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:

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"
  1. 在 mcp-server 添加 Webhook 端点
# 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"}
  1. 创建新的计费表
-- 模型调用计费记录表
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 数据模型设计

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 LiteLLM 客户端(已有 get_spend_logs)
services/model-gateway/config/litellm.yaml LiteLLM 配置(需添加 callback)
services/mcp-server/monitoring.py Dashboard 数据查询(需修改)
services/mcp-server/models.py 数据模型(需添加新表)