如何设计AI调用记录表
AI 产品的成本结构和传统 SaaS 有一个本质差异:传统 SaaS 的边际成本趋近于零,而 AI 产品每多服务一个用户,就多一笔真实的 API 账单。GPT-4o 的输入 Token 价格为 $2.50 / 1M tokens,Claude Sonnet 的输入 Token 价格为 $3.00 / 1M tokens,输出一侧通常更贵。当你的产品日活过万、每个用户每天产生几十轮对话,月度 API 账单轻松到达四位数甚至五位数美元。
没有精确的调用记录,成本就是一笔糊涂账。你不知道哪个功能在烧钱,不知道哪个用户的消耗异常,不知道缓存优化到底省了多少。本章系统讲解 AI 调用记录表的设计方法,覆盖日志表结构、Token 消耗记录、成本追踪、性能监控和数据归档五个层面。
AI 调用日志表设计
AI 调用日志表(ai_call_log)是整个成本追踪体系的原始数据源。每一次对 LLM API 的请求——无论成功还是失败——都应该在这里留下一条记录。
核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | UUID / BIGSERIAL | 主键,建议 UUID 避免暴露业务量 |
request_id | VARCHAR(64) | 请求唯一标识,用于幂等校验和日志关联 |
user_id | UUID | 关联用户表,标识调用发起者 |
session_id | VARCHAR(64) | 会话 ID,将同一轮对话的多轮调用关联起来 |
provider | VARCHAR(50) | 模型供应商:openai、anthropic、google、dashscope 等 |
model | VARCHAR(100) | 具体模型名称,如 gpt-4o、claude-sonnet-4-20250514 |
api_endpoint | VARCHAR(255) | 调用的 API 端点路径 |
request_params | JSONB | 请求参数快照:temperature、max_tokens、top_p 等 |
status | ENUM | 调用状态:success、failed、timeout、rate_limited |
http_status_code | SMALLINT | HTTP 响应状态码 |
error_code | VARCHAR(50) | 错误码(如有),如 rate_limit_exceeded、context_length_exceeded |
error_message | TEXT | 错误详情 |
retry_count | SMALLINT | 重试次数 |
latency_ms | INTEGER | 端到端响应耗时(毫秒) |
ttft_ms | INTEGER | Time To First Token,首 Token 响应时间(流式场景) |
created_at | TIMESTAMPTZ | 记录创建时间 |
设计要点
请求参数用 JSONB 而非独立列。AI API 的参数组合繁多,不同模型的参数也不一致。将 temperature、max_tokens、stop_sequences 等参数统一放在 request_params 中,查询时按需提取,避免表结构随参数变化频繁迁移。
同时记录 provider 和 model。供应商和模型是分开的两个维度。同一个 gpt-4o 模型可能通过 OpenAI 官方 API 调用,也可能通过 Azure OpenAI 调用,两者的价格和限速策略不同。分开存储方便按供应商对账,也方便按模型做成本分析。
status 要区分失败原因。failed 是一个笼统的状态,实际需要区分:timeout(超时)、rate_limited(限流)、context_length_exceeded(上下文超限)、invalid_request(参数错误)、server_error(供应商服务端错误)。不同失败类型对应不同的优化策略。
记录 retry_count。重试机制会成倍放大 Token 消耗。一个请求重试 3 次,意味着消耗了 4 倍的输入 Token(原始请求 + 3 次重试)。这个字段帮助识别重试带来的隐性成本。
Token 消耗记录
Token 是 AI API 的计费单位。大部分 LLM 供应商在 API 响应中返回 usage 字段,包含本次调用的 Token 消耗明细。OpenAI 兼容格式通常包含 prompt_tokens、completion_tokens、total_tokens 三个值;Anthropic 的格式使用 input_tokens、output_tokens,并额外区分 cache_read_input_tokens 和 cache_creation_input_tokens。
Token 记录表字段
| 字段 | 类型 | 说明 |
|---|---|---|
call_log_id | UUID | 关联 ai_call_log 表的主键 |
input_tokens | INTEGER | 输入 Token 数(含 system prompt、历史对话、用户输入) |
output_tokens | INTEGER | 输出 Token 数(模型生成的内容) |
total_tokens | INTEGER | 总 Token 数 = input_tokens + output_tokens |
cached_tokens | INTEGER | 命中缓存的 Token 数(如有),这部分通常按折扣价计费 |
cache_creation_tokens | INTEGER | 写入缓存的 Token 数(如有) |
reasoning_tokens | INTEGER | 推理 Token 数(o1/o3 等推理模型的内部思考 Token) |
input_cost | DECIMAL(12,8) | 输入侧费用(美元) |
output_cost | DECIMAL(12,8) | 输出侧费用(美元) |
total_cost | DECIMAL(12,8) | 本次调用总费用(美元) |
cost_currency | CHAR(3) | 费用币种,默认 USD |
设计要点
优先使用供应商返回的值,不要自行估算。不同模型的 Tokenizer 不同,同一个词在不同模型中的 Token 数量可能不一样。自行用 tiktoken 或其他方式估算容易产生误差,特别是遇到中文、日文等多字节字符时。供应商返回的 usage 字段是实际计费依据,应该直接入库。
分开记录输入和输出的 Token 及费用。输入 Token 和输出 Token 的单价通常不同,输出侧往往是输入侧的 3-4 倍。分开记录才能做精细的成本分析:如果一个功能的输入 Token 占比极高,优化方向是压缩 system prompt 或缩短上下文窗口;如果输出 Token 占比高,则需要关注输出内容的冗余度。
缓存 Token 单独记录。OpenAI 的 Prompt Caching 和 Anthropic 的 Prompt Caching 对缓存命中的 Token 按折扣价计费(通常是原价的 10%-50%)。将 cached_tokens 从 input_tokens 中单独拆出来,才能在报表中区分「全价 Token」和「折扣 Token」,准确计算实际成本。
推理 Token 是新兴类别。OpenAI 的 o1、o3 系列模型会产生内部推理 Token(reasoning tokens),这些 Token 不会出现在最终输出中,但会计费。如果不单独记录,你会发现实际费用比根据可见输出估算的值高出一截。
两种表结构选择
Token 消耗记录有两种存储方式:
方案 A:和调用日志合并在同一张表。将 Token 字段直接放在 ai_call_log 表中。优点是一次 JOIN 都不用,查询简单。缺点是表变宽,且失败请求的 Token 字段全部为空。
方案 B:独立的 ai_token_usage 表。通过 call_log_id 关联。优点是职责清晰,只有成功的调用才有 Token 记录。缺点是查询时需要 JOIN。
对于中小规模产品(日调用量 10 万以下),方案 A 足够。当日调用量到达百万级别、需要做高频聚合查询时,方案 B 更合适,因为可以对 Token 记录表单独做分区和索引优化。
成本追踪和统计
有了每次调用的 Token 消耗和费用数据,接下来要解决「这些钱花在了哪里」的问题。成本追踪的核心是多维度的费用归属(cost attribution)。
成本归属维度
| 维度 | 说明 | 典型用途 |
|---|---|---|
| 用户级 | 按 user_id 聚合 | 识别高消耗用户,设置用量配额或提醒 |
| 功能级 | 按功能标识(如 feature_tag)聚合 | 判断哪个功能的 AI 成本最高 |
| 团队/租户级 | 按 team_id 或 tenant_id 聚合 | 多租户 SaaS 的成本分摊和 showback |
| 模型级 | 按 model 聚合 | 评估模型切换的成本影响 |
| 环境级 | 按 environment 聚合 | 区分生产环境和开发/测试环境的消耗 |
| 会话级 | 按 session_id 聚合 | 计算单次对话的平均成本 |
价格表设计
为了实现自动化的成本计算,需要维护一张模型价格表:
| 字段 | 类型 | 说明 |
|---|---|---|
provider | VARCHAR(50) | 供应商 |
model | VARCHAR(100) | 模型名称 |
input_price_per_1m | DECIMAL(10,4) | 每 1M 输入 Token 的价格(美元) |
output_price_per_1m | DECIMAL(10,4) | 每 1M 输出 Token 的价格(美元) |
cached_input_price_per_1m | DECIMAL(10,4) | 缓存命中的输入 Token 价格(如有折扣) |
cache_write_price_per_1m | DECIMAL(10,4) | 缓存写入的 Token 价格(如有额外费用) |
effective_from | DATE | 价格生效日期 |
effective_to | DATE | 价格失效日期(NULL 表示当前有效) |
成本计算公式:
input_cost = input_tokens / 1_000_000 * input_price_per_1m
output_cost = output_tokens / 1_000_000 * output_price_per_1m
total_cost = input_cost + output_cost
如果有缓存命中的 Token,需要从标准输入 Token 中扣除后按折扣价计算:
standard_input = input_tokens - cached_tokens
cached_cost = cached_tokens / 1_000_000 * cached_input_price_per_1m
input_cost = standard_input / 1_000_000 * input_price_per_1m + cached_cost
统计查询示例
按天统计各模型的总消耗和费用:
SELECT
DATE(created_at) AS call_date,
model,
COUNT(*) AS call_count,
SUM(input_tokens) AS total_input_tokens,
SUM(output_tokens) AS total_output_tokens,
SUM(total_cost) AS total_cost_usd
FROM ai_call_log
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
AND status = 'success'
GROUP BY DATE(created_at), model
ORDER BY call_date DESC, total_cost_usd DESC;按功能维度统计过去 7 天的费用分布:
SELECT
feature_tag,
COUNT(*) AS call_count,
SUM(total_cost) AS total_cost_usd,
AVG(total_cost) AS avg_cost_per_call,
SUM(input_tokens + output_tokens) AS total_tokens
FROM ai_call_log
WHERE created_at >= NOW() - INTERVAL '7 days'
AND status = 'success'
GROUP BY feature_tag
ORDER BY total_cost_usd DESC;性能监控
AI API 的响应时间是用户体验的关键瓶颈。一次 Chat Completion 调用可能需要 500ms 到 30s 不等,取决于模型、Token 数量和供应商负载。性能监控需要从调用日志中提取关键指标,建立基线和告警。
核心监控指标
| 指标 | 计算方式 | 告警阈值建议 |
|---|---|---|
| 平均响应时间(P50) | latency_ms 的中位数 | 因模型而异,GPT-4o 通常 < 2s |
| P95 响应时间 | latency_ms 的 95 分位 | 通常设为 P50 的 3-5 倍 |
| P99 响应时间 | latency_ms 的 99 分位 | 超过 30s 需排查 |
| 首 Token 时间(TTFT) | ttft_ms 的中位数 | 流式场景 < 1s |
| 成功率 | status = 'success' 的占比 | < 99% 需关注 |
| 错误率(按类型) | 各 error_code 的占比 | rate_limited > 5% 需扩容 |
| 超时率 | status = 'timeout' 的占比 | > 1% 需检查网络和超时设置 |
| 重试率 | retry_count > 0 的占比 | > 10% 需排查供应商稳定性 |
| 每分钟调用量 | 按分钟聚合的 COUNT | 接近限速阈值时告警 |
| Token 吞吐率 | total_tokens / (latency_ms / 1000) | 显著下降时可能供应商负载高 |
监控视图设计
性能监控通常分为三个层级:
实时仪表盘:展示最近 5-15 分钟的指标快照,包括成功率、P50 延迟、每分钟调用量。用于发现突发故障。
趋势分析:展示过去 24 小时到 30 天的指标趋势,包括日均成本变化、Token 消耗增长曲线、各模型的延迟分布。用于识别慢速退化和成本增长趋势。
异常检测:基于历史基线自动识别异常模式。例如某模型的平均延迟突然从 1.5s 跳到 5s、某个用户的 Token 消耗量比昨天增长了 10 倍、特定错误码的频率突然升高。
关键告警规则
# 示例告警规则(Prometheus/Grafana 风格)
alerts:
- name: ai_api_high_error_rate
condition: error_rate_5m > 0.01
severity: warning
message: "AI API 错误率超过 1%,当前 {{ $value | humanizePercentage }}"
- name: ai_api_latency_spike
condition: p95_latency_5m > 15000
severity: warning
message: "AI API P95 延迟超过 15s,当前 {{ $value }}ms"
- name: ai_api_cost_spike
condition: hourly_cost > daily_avg_hourly_cost * 3
severity: critical
message: "过去 1 小时 AI 调用费用超过日均小时费用的 3 倍"
- name: ai_api_rate_limit_approaching
condition: rpm_1m > rate_limit * 0.8
severity: warning
message: "每分钟调用量接近限速的 80%"数据归档策略
AI 调用日志是典型的「写入量大、查询频率随时间递减」的数据。一条调用记录在产生后的前 7 天被频繁查询(用于实时监控和异常排查),30 天后查询频率大幅下降,90 天后几乎只在审计或回溯场景才会访问。如果所有数据都留在主表中,表的体积会快速膨胀,影响查询性能。
分层存储方案
| 层级 | 存储位置 | 数据范围 | 查询性能 | 存储成本 |
|---|---|---|---|---|
| 热数据 | PostgreSQL 主表(分区表) | 最近 7-30 天 | 毫秒级 | 高 |
| 温数据 | PostgreSQL 归档分区 / Citus | 30-180 天 | 百毫秒级 | 中 |
| 冷数据 | 对象存储(S3)+ Parquet 格式 | 180 天以上 | 秒级 | 低 |
分区策略
PostgreSQL 的表分区(Table Partitioning)是实现归档的基础。按月对 ai_call_log 做范围分区:
CREATE TABLE ai_call_log (
id UUID NOT NULL DEFAULT gen_random_uuid(),
request_id VARCHAR(64) NOT NULL,
user_id UUID NOT NULL,
-- ... 其他字段
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 按月创建分区
CREATE TABLE ai_call_log_2026_06 PARTITION OF ai_call_log
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
CREATE TABLE ai_call_log_2026_07 PARTITION OF ai_call_log
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');按月分区的好处是:归档操作只需要 DETACH PARTITION,不会影响主表的在线查询。分离后的分区可以迁移到低成本存储,或者直接 DROP 掉(如果数据已经导出到 S3)。
索引策略
不同时间范围的数据使用模式不同,索引策略也应该不同:
热数据(最近 30 天)索引:
-- 按用户查询最近的调用记录
CREATE INDEX idx_call_log_user_recent ON ai_call_log (user_id, created_at DESC)
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
-- 按会话查询多轮对话
CREATE INDEX idx_call_log_session ON ai_call_log (session_id, created_at)
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
-- 按供应商和状态查询异常
CREATE INDEX idx_call_log_provider_status ON ai_call_log (provider, status, created_at DESC)
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days';温数据和冷数据不需要这么多索引,甚至不需要索引——它们通常只在批量分析任务中被全表扫描。
实战案例
案例一:AI 写作助手的成本失控
一个面向海外用户的 AI 写作助手,使用 GPT-4o 做内容生成。上线初期功能迭代快,没有建立调用记录体系。两个月后收到 OpenAI 账单,月费用 $8,000,远超预期。
排查后发现三个问题:
- system prompt 膨胀。经过多轮迭代,system prompt 从最初的 500 tokens 增长到 12,000 tokens,每一轮对话都会携带完整的 prompt,但团队对此毫无感知。
- 没有缓存。同一个用户在一篇文章中反复修改同一段落,每次修改都重新发送完整的上下文,没有利用 Prompt Caching。
- 失败请求也在消耗 Token。部分请求因为
max_tokens设置过大导致超时,但 Token 已经计费。
解决方案:上线调用记录表后,团队建立了按功能维度的成本看板。发现「段落重写」功能占了总成本的 45%,但用户只占 12%。进一步优化了 system prompt(压缩到 3,000 tokens)、启用了 Prompt Caching、对重试次数做了上限控制(最多 2 次)。三个月后,月费用降至 $3,200,降幅 60%。
案例二:多租户 AI 平台的成本分摊
一个面向企业客户的 AI 对话平台,支持多个租户(企业)接入。每个租户有自己的 API Key 和用量配额。平台需要回答两个问题:每个租户花了多少钱?每个租户的哪个功能花了多少钱?
调用记录表的设计中增加了 tenant_id 和 feature_tag 两个字段。每次 API 调用时,中间件从请求上下文中提取租户标识,从路由中提取功能标识,写入调用记录。
成本分摊报表基于以下查询:
SELECT
t.tenant_name,
acl.feature_tag,
DATE(acl.created_at) AS usage_date,
SUM(atu.total_tokens) AS total_tokens,
SUM(atu.total_cost) AS total_cost_usd
FROM ai_call_log acl
JOIN ai_token_usage atu ON atu.call_log_id = acl.id
JOIN tenants t ON t.id = acl.user_id -- 简化示意
WHERE acl.created_at >= '2026-06-01'
AND acl.created_at < '2026-07-01'
GROUP BY t.tenant_name, acl.feature_tag, DATE(acl.created_at)
ORDER BY total_cost_usd DESC;基于这些数据,平台实现了 showback 报表——每个租户登录后可以看到自己的 Token 消耗分布和费用明细。对于超出配额的高消耗租户,系统会自动发送用量提醒邮件。这套机制将租户的用量投诉降低了 70%。
AI 调用记录的数据流
设计检查清单
在设计 AI 调用记录表之前,逐项确认以下要点:
- 每次 API 调用都有唯一
request_id,用于幂等校验和日志关联 -
provider和model分开存储,支持同一模型多渠道对账 - 输入 Token 和输出 Token 分开记录,支持差异化的成本分析
- 缓存 Token(cached_tokens)和推理 Token(reasoning_tokens)单独记录
- 费用通过供应商返回的实际值计算,而非本地 Tokenizer 估算
- 模型价格表有版本控制,支持历史价格回溯
- 调用状态细分:
success、failed、timeout、rate_limited等 - 重试次数
retry_count有记录,防止隐性成本膨胀 - 性能指标完整:
latency_ms(端到端延迟)和ttft_ms(首 Token 时间) - 有功能维度的归属标识(
feature_tag),支持按功能做成本分析 - 有用户 / 租户维度的归属,支持 showback 和配额管理
- 表结构支持分区(如按月分区),为数据归档做准备
- 有对应的告警规则:错误率、延迟、费用异常、接近限速
- 定期归档策略已定义:热数据 → 温数据 → 冷数据的流转规则
- 敏感信息(如用户输入的完整内容)不直接存储在调用日志中,或做了脱敏处理
小结
AI 调用记录表不是一张简单的日志表,它是成本管控、性能优化和运营决策的数据基础。设计的核心原则是:记录完整的调用上下文(谁在什么场景下调用了什么模型),精确追踪 Token 消耗(区分输入、输出、缓存和推理 Token),建立多维度的费用归属(用户、功能、团队、环境),以及为未来的数据归档预留结构。
从工程实践的角度,建议从一张合并了 Token 字段的 ai_call_log 表开始,配合一张模型价格表做自动化的成本计算。当数据量增长到需要优化查询性能时,再拆分 Token 记录和做表分区。不要一开始就过度设计——但调用记录这件事,从第一天就应该有。
参考资料
- LiteLLM - What is stored in the DB — LiteLLM 的数据库 Schema 文档,包含
LiteLLM_SpendLogs表的结构设计 - Langfuse - Token & Cost Tracking — Langfuse 的 Token 和成本追踪机制,支持多种模型的自动费用计算
- Braintrust - How to track LLM token usage (2026) — 三层 Token 可见性模型:per-call、context window、per-step
- Portkey - Tracking LLM token usage across providers, teams and workloads — 跨供应商、团队和工作负载的 Token 追踪框架
- AWS 中国博客 - 让每一分 Token 消耗都可量化的全栈实践 — Agentic AI 场景下的 Token 可观测性方案
- Braintrust - How to track LLM costs (2026) — per-request 级别的成本追踪和归属方法
- LiteLLM - Spend Tracking — 跨 100+ 模型的 Token 消耗和费用自动追踪
- MLflow - Token Usage and Cost Tracking — MLflow 的 LLM Token 使用量和成本追踪文档