AI调用记录管理模块
AI 调用成本是产品中一类「隐形支出」——每次用户请求触发的 LLM 调用、Embedding 计算、图像生成,都在静默消耗预算。当产品从原型走向规模化运营,调用量从每天数百次增长到数十万次时,缺乏精细化管理的成本会迅速失控。更关键的是,没有完整的调用记录,你就无法回答这几个核心问题:哪些功能在大量消耗 token?错误率是否异常?某次模型升级是否导致了延迟上升?
AI 调用记录管理模块的职责,就是让每一次 AI 调用都「可追踪、可分析、可优化」。它不是一个锦上添花的后台功能,而是产品进入生产阶段后必须具备的基础设施。
核心功能设计
一个完整的 AI 调用记录管理模块需要覆盖四个核心功能域:调用列表、成本分析、性能监控和错误追踪。它们各自解决不同的管理需求,但在数据层面高度关联。
调用列表
调用列表是管理模块的基础视图,提供所有 AI 调用的结构化记录。每条记录至少应包含以下信息:调用 ID、时间戳、调用方(用户/租户/功能模块)、目标模型、请求参数摘要、响应状态、token 消耗量和耗时。
设计调用列表时需要关注几个要点:
分页与检索。调用记录的量级通常是百万级甚至更高,列表页必须支持高效分页和多维筛选。按时间范围、模型、状态、用户 ID、功能模块进行组合筛选是基本需求。
数据脱敏。请求和响应的原始内容可能包含用户敏感数据,列表中通常只展示摘要信息(如 prompt 前 100 个字符),完整内容需要单独权限才能查看。
关联上下文。每条调用记录应能关联到具体的业务场景——是哪个页面、哪个功能触发的调用。这样当成本异常时,可以快速定位到具体的功能模块。
成本分析
成本分析模块将 token 消耗转化为财务数据,帮助团队理解 AI 调用的经济影响。
核心能力包括:
- 实时成本统计:按小时/天/周/月统计总成本和各模型成本分布
- 分维度归因:按用户、租户、功能模块、模型类型进行成本拆分
- 趋势分析:识别成本增长趋势,发现异常波峰
- 预算对比:设定预算上限后,展示实际消耗与预算的偏差
- 预测模型:基于历史数据预测未来成本走势
不同模型的成本计算逻辑不同。OpenAI 的 GPT 系列按 input/output token 分别计价,Claude 也采用类似模式,而图像生成模型(如 DALL-E、Stable Diffusion)按图片尺寸和数量计价。成本分析模块需要维护一张模型价格表,并在模型提供商调价时及时更新。
性能监控
性能监控关注的是 AI 调用的响应质量,核心指标包括:
| 指标 | 说明 | 告警阈值参考 |
|---|---|---|
| 首 Token 延迟(TTFT) | 从发起请求到收到第一个 token 的时间 | 流式场景 < 500ms |
| 端到端延迟(E2E Latency) | 从发起请求到完整响应的总耗时 | 根据业务场景设定 |
| 吞吐量(TPS) | 每秒处理的 token 数量 | 低于基线 30% 时告警 |
| 成功率 | 成功调用占总调用的比例 | < 99% 时告警 |
| P95/P99 延迟 | 95%/99% 分位的延迟值 | 根据 SLA 设定 |
性能监控的难点在于,AI 调用的耗时波动远大于传统 API。同一个模型在不同时段、不同负载下的响应时间可能相差数倍。因此,监控策略应以分位值(P50/P95/P99)为主,而非简单的平均值。
错误追踪
AI 调用的错误类型比传统 API 更复杂,常见的包括:
- 模型过载(429 Too Many Requests):触发速率限制
- 上下文超长(Context Length Exceeded):输入 token 超过模型限制
- 内容过滤(Content Filter):触发安全策略导致响应被截断
- 模型服务不可用(503 Service Unavailable):模型提供商故障
- 超时(Timeout):模型推理时间过长
- 格式异常(Parse Error):模型返回内容无法解析为预期格式
错误追踪模块需要对每种错误类型进行分类统计,并支持快速定位高频错误的调用特征——是特定 prompt 模式导致的,还是特定模型版本的问题,或者是特定时间段的基础设施故障。
功能域对比
| 维度 | 调用列表 | 成本分析 | 性能监控 | 错误追踪 |
|---|---|---|---|---|
| 核心目标 | 可追溯 | 可量化 | 可度量 | 可定位 |
| 数据粒度 | 单次调用 | 聚合统计 | 时间序列 | 错误分类 |
| 更新频率 | 实时写入 | 准实时聚合 | 实时采集 | 实时写入 |
| 主要消费者 | 开发/运维 | 产品/财务 | 运维/SRE | 开发/运维 |
| 存储压力 | 高(原始数据) | 中(聚合数据) | 中(时序数据) | 低(仅异常数据) |
| 关键挑战 | 海量数据检索 | 多模型价格映射 | 延迟波动处理 | 错误分类准确性 |
数据库设计
AI 调用记录的数据库设计需要平衡写入性能、查询效率和存储成本。推荐采用「主表 + 聚合表」的分层架构:主表记录每次调用的明细,聚合表存储预计算的统计数据。
核心表结构
调用记录主表 ai_call_logs
CREATE TABLE ai_call_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
call_id VARCHAR(64) NOT NULL UNIQUE, -- 全局唯一调用 ID
tenant_id VARCHAR(64) NOT NULL, -- 租户 ID
user_id VARCHAR(64), -- 用户 ID
feature_module VARCHAR(128) NOT NULL, -- 功能模块标识
provider VARCHAR(32) NOT NULL, -- 模型提供商:openai / anthropic / ...
model VARCHAR(64) NOT NULL, -- 模型名称:gpt-4o / claude-sonnet-4-20250514 / ...
request_type VARCHAR(16) NOT NULL DEFAULT 'chat', -- chat / embedding / image / audio
input_tokens INT NOT NULL DEFAULT 0,
output_tokens INT NOT NULL DEFAULT 0,
total_tokens INT GENERATED ALWAYS AS (input_tokens + output_tokens) STORED,
cost_usd DECIMAL(12, 6) NOT NULL DEFAULT 0, -- 单次调用成本(美元)
latency_ms INT, -- 端到端延迟
ttft_ms INT, -- 首 Token 延迟
status VARCHAR(16) NOT NULL, -- success / error / timeout / rate_limited
error_code VARCHAR(64), -- 错误码
error_message TEXT, -- 错误信息
prompt_hash VARCHAR(64), -- prompt 哈希(用于去重和聚合)
metadata JSON, -- 扩展字段:temperature、top_p 等
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tenant_time (tenant_id, created_at DESC),
INDEX idx_model_time (model, created_at DESC),
INDEX idx_feature_time (feature_module, created_at DESC),
INDEX idx_status_time (status, created_at DESC),
INDEX idx_user_time (user_id, created_at DESC)
);模型价格表 ai_model_pricing
CREATE TABLE ai_model_pricing (
id INT PRIMARY KEY AUTO_INCREMENT,
provider VARCHAR(32) NOT NULL,
model VARCHAR(64) NOT NULL,
request_type VARCHAR(16) NOT NULL DEFAULT 'chat',
input_price DECIMAL(12, 6) NOT NULL, -- 每百万 input token 价格(美元)
output_price DECIMAL(12, 6) NOT NULL, -- 每百万 output token 价格(美元)
effective_from DATE NOT NULL, -- 价格生效日期
effective_to DATE, -- 价格失效日期(NULL 表示当前有效)
currency VARCHAR(8) NOT NULL DEFAULT 'USD',
UNIQUE INDEX uk_model_effective (provider, model, request_type, effective_from)
);日聚合表 ai_call_daily_stats
CREATE TABLE ai_call_daily_stats (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
stat_date DATE NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
feature_module VARCHAR(128),
provider VARCHAR(32) NOT NULL,
model VARCHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL,
call_count INT NOT NULL DEFAULT 0,
total_input_tokens BIGINT NOT NULL DEFAULT 0,
total_output_tokens BIGINT NOT NULL DEFAULT 0,
total_cost_usd DECIMAL(14, 6) NOT NULL DEFAULT 0,
avg_latency_ms INT,
p95_latency_ms INT,
p99_latency_ms INT,
error_count INT NOT NULL DEFAULT 0,
UNIQUE INDEX uk_daily_dim (stat_date, tenant_id, feature_module, provider, model, status)
);数据库设计对比
| 表 | 用途 | 写入频率 | 数据保留 | 分区策略 |
|---|---|---|---|---|
ai_call_logs | 调用明细 | 每次调用实时写入 | 90 天热数据 + 冷归档 | 按天/周范围分区 |
ai_model_pricing | 模型价格 | 极低(模型调价时) | 永久保留历史 | 无需分区 |
ai_call_daily_stats | 日聚合统计 | 每日批量计算写入 | 永久保留 | 按年范围分区 |
索引设计要点
ai_call_logs 表的索引设计围绕后台管理系统的典型查询模式展开:
idx_tenant_time:租户维度的调用列表查询,按时间倒序。这是最高频的查询场景,尤其是多租户 SaaS 产品。idx_model_time:按模型维度的成本和性能分析,帮助识别哪个模型的消耗最大。idx_feature_time:按功能模块的归因分析,定位成本异常的具体功能。idx_status_time:错误追踪场景,快速筛选失败的调用记录。idx_user_time:用户维度的用量查询,用于计费限额和用户级分析。
call_id 的 UNIQUE 索引用于保证幂等性——网络重试场景下不会产生重复记录。prompt_hash 字段用于 prompt 去重和相似调用聚合分析,不参与索引以节省存储空间。
聚合查询示例
按功能模块统计月度成本
SELECT
feature_module,
DATE_FORMAT(created_at, '%Y-%m') AS month,
COUNT(*) AS call_count,
SUM(input_tokens) AS total_input_tokens,
SUM(output_tokens) AS total_output_tokens,
SUM(cost_usd) AS total_cost,
AVG(latency_ms) AS avg_latency
FROM ai_call_logs
WHERE tenant_id = ?
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
GROUP BY feature_module, DATE_FORMAT(created_at, '%Y-%m')
ORDER BY total_cost DESC;错误率趋势分析
SELECT
DATE(created_at) AS stat_date,
COUNT(*) AS total_calls,
SUM(CASE WHEN status != 'success' THEN 1 ELSE 0 END) AS error_count,
ROUND(SUM(CASE WHEN status != 'success' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) AS error_rate
FROM ai_call_logs
WHERE tenant_id = ?
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE(created_at)
ORDER BY stat_date;对于大规模数据(日调用量 > 100 万),直接查询主表会面临性能瓶颈。此时应依赖 ai_call_daily_stats 聚合表,将报表查询的时间复杂度从 O(N) 降低到 O(天数量)。聚合任务建议通过定时任务每小时增量计算,而非全量重算。
成本分析与优化建议
成本分析不仅是展示数据,更重要的是驱动优化决策。以下是经过验证的成本优化策略:
成本归因体系
建立「租户 → 功能模块 → 模型 → 调用」四级归因体系,让每一分钱的 AI 支出都有明确的归属。
| 归因层级 | 用途 | 优化动作 |
|---|---|---|
| 租户级 | 计费、套餐设计 | 调整不同套餐的 AI 调用配额 |
| 功能级 | 产品 ROI 分析 | 评估功能价值与成本是否匹配 |
| 模型级 | 模型选型决策 | 在质量满足需求时切换到更便宜的模型 |
| 调用级 | 异常检测 | 识别异常高消耗的单次调用 |
优化策略
1. 模型分级与路由
不是所有任务都需要最强的模型。根据任务复杂度建立模型分级策略:
- 简单任务(分类、提取、格式化)→ 使用轻量模型(GPT-4o-mini、Claude Haiku)
- 中等任务(摘要、翻译、常规对话)→ 使用中等模型(GPT-4o、Claude Sonnet)
- 复杂任务(推理、代码生成、创意写作)→ 使用旗舰模型(o3、Claude Opus)
通过智能路由,在满足质量要求的前提下,整体成本通常可以降低 40% ~ 60%。
2. Prompt 缓存与复用
对于结构相似的请求(如相同 system prompt 的多轮对话),利用模型提供商的 Prompt Caching 能力(如 Anthropic 的 Prompt Caching、OpenAI 的 Prompt Caching),相同前缀的 token 可以享受 50% ~ 90% 的价格折扣。
3. 上下文窗口管理
传入过多历史上下文是成本膨胀的常见原因。通过上下文压缩(summarize 历史对话)、滑动窗口(只保留最近 N 轮)或 RAG 替代(用检索替代长上下文),可以有效控制 input token 数量。
4. 批量与异步处理
非实时场景(如内容生成、数据标注)应使用 Batch API(如 OpenAI Batch API),成本通常为实时调用的 50%。
5. 缓存层
对确定性较高的请求(如 FAQ 回答、模板填充),在应用层引入语义缓存(Semantic Cache),相同意图的请求直接返回缓存结果,避免重复调用模型。
性能监控与告警
性能监控的核心是建立分级告警机制,避免告警疲劳的同时确保关键问题不被遗漏。
告警分级
| 级别 | 触发条件 | 响应时间 | 通知方式 |
|---|---|---|---|
| P0 - 严重 | 成功率 < 90% 或 P99 延迟 > 30s,持续 5 分钟 | 5 分钟内响应 | 电话 + 即时消息 |
| P1 - 高 | 成功率 < 95% 或 P95 延迟 > 15s,持续 10 分钟 | 15 分钟内响应 | 即时消息 |
| P2 - 中 | 成本超日预算 150%,或错误率上升但未达 P1 | 1 小时内响应 | 邮件 + 即时消息 |
| P3 - 低 | 成本趋势异常增长、非核心模型性能波动 | 下一工作日处理 | 邮件 |
监控数据采集
性能监控数据的采集应在 AI 调用的中间件层完成,不侵入业务代码:
// AI 调用中间件 —— 自动采集调用指标
interface CallMetrics {
callId: string
tenantId: string
userId?: string
featureModule: string
provider: string
model: string
startTime: number
firstTokenTime?: number // 流式响应专用
endTime: number
inputTokens: number
outputTokens: number
status: 'success' | 'error' | 'timeout' | 'rate_limited'
errorCode?: string
errorMessage?: string
metadata?: Record<string, unknown>
}
class AIMetricsCollector {
private buffer: CallMetrics[] = []
private flushInterval = 5000 // 5 秒批量写入
async record(metrics: CallMetrics): Promise<void> {
// 计算衍生指标
const latency = metrics.endTime - metrics.startTime
const ttft = metrics.firstTokenTime
? metrics.firstTokenTime - metrics.startTime
: null
// 计算成本
const cost = await this.calculateCost(
metrics.provider,
metrics.model,
metrics.inputTokens,
metrics.outputTokens
)
// 写入缓冲区,批量落库
this.buffer.push({
...metrics,
latencyMs: latency,
ttftMs: ttft ?? undefined,
costUsd: cost,
})
}
private async flush(): Promise<void> {
if (this.buffer.length === 0) return
const batch = this.buffer.splice(0, this.buffer.length)
// 批量写入主表
await db.insert(aiCallLogs).values(batch)
// 异步更新聚合表
await this.updateAggregates(batch)
}
}成本计算逻辑
async function calculateCost(
provider: string,
model: string,
inputTokens: number,
outputTokens: number
): Promise<number> {
// 查询当前有效的模型价格
const pricing = await db.query.aiModelPricing.findFirst({
where: and(
eq(aiModelPricing.provider, provider),
eq(aiModelPricing.model, model),
lte(aiModelPricing.effectiveFrom, new Date()),
or(
isNull(aiModelPricing.effectiveTo),
gte(aiModelPricing.effectiveTo, new Date())
)
),
})
if (!pricing) return 0
// 价格单位:每百万 token
const inputCost = (inputTokens / 1_000_000) * pricing.inputPrice
const outputCost = (outputTokens / 1_000_000) * pricing.outputPrice
return Number((inputCost + outputCost).toFixed(6))
}实现方法
调用列表 API
// Hono 路由 —— 调用列表查询
import { Hono } from 'hono'
import { eq, and, gte, lte, desc, sql } from 'drizzle-orm'
const callLogRouter = new Hono()
callLogRouter.get('/api/admin/ai-call-logs', async (c) => {
const query = c.req.query()
const page = Number(query.page) || 1
const pageSize = Math.min(Number(query.pageSize) || 50, 200)
const offset = (page - 1) * pageSize
// 构建动态筛选条件
const conditions = []
if (query.tenantId) {
conditions.push(eq(aiCallLogs.tenantId, query.tenantId))
}
if (query.model) {
conditions.push(eq(aiCallLogs.model, query.model))
}
if (query.status) {
conditions.push(eq(aiCallLogs.status, query.status))
}
if (query.featureModule) {
conditions.push(eq(aiCallLogs.featureModule, query.featureModule))
}
if (query.startTime) {
conditions.push(gte(aiCallLogs.createdAt, new Date(query.startTime)))
}
if (query.endTime) {
conditions.push(lte(aiCallLogs.createdAt, new Date(query.endTime)))
}
const where = conditions.length > 0 ? and(...conditions) : undefined
// 并行查询数据和总数
const [data, totalResult] = await Promise.all([
db.query.aiCallLogs.findMany({
where,
orderBy: [desc(aiCallLogs.createdAt)],
limit: pageSize,
offset,
// 不返回完整的 error_message 和 metadata,减少传输量
columns: {
errorMessage: false,
metadata: false,
},
}),
db
.select({ count: sql<number>`count(*)` })
.from(aiCallLogs)
.where(where),
])
return c.json({
data,
pagination: {
page,
pageSize,
total: totalResult[0].count,
totalPages: Math.ceil(totalResult[0].count / pageSize),
},
})
})成本统计 API
// 成本统计 —— 优先查聚合表,回退到主表
callLogRouter.get('/api/admin/ai-call-logs/cost-stats', async (c) => {
const query = c.req.query()
const tenantId = query.tenantId
const granularity = query.granularity || 'day' // day / week / month
if (!tenantId) {
return c.json({ error: 'tenantId is required' }, 400)
}
// 使用聚合表查询,性能可控
const stats = await db.query.aiCallDailyStats.findMany({
where: and(
eq(aiCallDailyStats.tenantId, tenantId),
gte(aiCallDailyStats.statDate, new Date(query.startDate)),
lte(aiCallDailyStats.statDate, new Date(query.endDate))
),
orderBy: [aiCallDailyStats.statDate],
})
// 按时间粒度二次聚合
const grouped = new Map<string, {
callCount: number
totalCost: number
inputTokens: number
outputTokens: number
errorCount: number
}>()
for (const row of stats) {
const key = formatDate(row.statDate, granularity)
const existing = grouped.get(key) || {
callCount: 0,
totalCost: 0,
inputTokens: 0,
outputTokens: 0,
errorCount: 0,
}
existing.callCount += row.callCount
existing.totalCost += Number(row.totalCostUsd)
existing.inputTokens += Number(row.totalInputTokens)
existing.outputTokens += Number(row.totalOutputTokens)
existing.errorCount += row.errorCount
grouped.set(key, existing)
}
return c.json({
data: Array.from(grouped.entries()).map(([date, stats]) => ({
date,
...stats,
avgCostPerCall: stats.callCount > 0
? stats.totalCost / stats.callCount
: 0,
})),
})
})聚合任务
// 每小时增量聚合任务
async function runHourlyAggregation(): Promise<void> {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000)
// 获取需要聚合的时间范围内的原始数据
const rawData = await db
.select({
statDate: sql<Date>`DATE(${aiCallLogs.createdAt})`,
tenantId: aiCallLogs.tenantId,
featureModule: aiCallLogs.featureModule,
provider: aiCallLogs.provider,
model: aiCallLogs.model,
status: aiCallLogs.status,
callCount: sql<number>`count(*)`,
totalInputTokens: sql<number>`sum(${aiCallLogs.inputTokens})`,
totalOutputTokens: sql<number>`sum(${aiCallLogs.outputTokens})`,
totalCost: sql<string>`sum(${aiCallLogs.costUsd})`,
avgLatency: sql<number>`avg(${aiCallLogs.latencyMs})`,
p95Latency: sql<number>`percentile_cont(0.95) within group (order by ${aiCallLogs.latencyMs})`,
p99Latency: sql<number>`percentile_cont(0.99) within group (order by ${aiCallLogs.latencyMs})`,
errorCount: sql<number>`sum(case when ${aiCallLogs.status} != 'success' then 1 else 0 end)`,
})
.from(aiCallLogs)
.where(gte(aiCallLogs.createdAt, oneHourAgo))
.groupBy(
sql`DATE(${aiCallLogs.createdAt})`,
aiCallLogs.tenantId,
aiCallLogs.featureModule,
aiCallLogs.provider,
aiCallLogs.model,
aiCallLogs.status
)
// Upsert 到聚合表
for (const row of rawData) {
await db
.insert(aiCallDailyStats)
.values({
statDate: row.statDate,
tenantId: row.tenantId,
featureModule: row.featureModule,
provider: row.provider,
model: row.model,
status: row.status,
callCount: row.callCount,
totalInputTokens: row.totalInputTokens,
totalOutputTokens: row.totalOutputTokens,
totalCostUsd: row.totalCost,
avgLatencyMs: row.avgLatency,
p95LatencyMs: row.p95Latency,
p99LatencyMs: row.p99Latency,
errorCount: row.errorCount,
})
.onConflictDoUpdate({
target: [
aiCallDailyStats.statDate,
aiCallDailyStats.tenantId,
aiCallDailyStats.featureModule,
aiCallDailyStats.provider,
aiCallDailyStats.model,
aiCallDailyStats.status,
],
set: {
callCount: sql`${aiCallDailyStats.callCount} + ${row.callCount}`,
totalInputTokens: sql`${aiCallDailyStats.totalInputTokens} + ${row.totalInputTokens}`,
totalOutputTokens: sql`${aiCallDailyStats.totalOutputTokens} + ${row.totalOutputTokens}`,
totalCostUsd: sql`${aiCallDailyStats.totalCostUsd} + ${row.totalCost}`,
},
})
}
}AI 调用记录管理流程
案例分析
案例一:SaaS 写作助手的成本失控与治理
某 SaaS 写作助手产品在上线 6 个月后,月度 AI 调用成本从 $2,000 增长到 $18,000,但用户增长只有 3 倍。通过引入调用记录管理模块,团队发现了几个问题:
- 翻译功能使用了 GPT-4o 做简单文本翻译,每次调用约 2,000 tokens,但翻译任务的输入输出高度结构化,完全可以切换到 GPT-4o-mini,成本降低 90%。
- 某用户的自动化脚本在周末大量调用,占周未总调用量的 60%,但产生的内容从未被使用。
- Prompt 中包含了完整的用户历史文档(平均 8,000 tokens),但实际上只需要最近 3 段对话上下文。
治理措施:引入模型分级路由,翻译和格式化任务使用轻量模型;对单用户设置日调用上限;优化 Prompt 模板,将上下文窗口从全量历史缩减到最近 3 轮。三个月后,月度成本降至 $5,500,降幅约 70%。
案例二:多租户平台的成本归因实践
某面向企业的 AI 客服平台,服务 200+ 租户。平台按「座席数」收费,但 AI 调用成本在不同租户间差异极大——部分租户的客服大量使用 AI 辅助回复,部分租户几乎不用。
在没有调用记录管理时,平台无法区分不同租户的 AI 消耗,导致高消耗租户的成本被均摊到所有租户身上。引入管理模块后:
- 建立了租户级的成本归因,准确度量每个租户的 AI 调用消耗
- 设计了阶梯式定价:基础套餐包含每月 5,000 次 AI 调用,超出部分按实际消耗计费
- 为每个租户提供用量仪表盘,租户可以自助查看 AI 调用统计
- 识别出 3 个异常高消耗的租户,排查发现是其内部系统循环调用导致的
上线成本归因后,平台的 AI 调用收入覆盖了 95% 的模型成本,而此前这一比例只有 45%。
成本分析维度对比
| 分析维度 | 数据源 | 聚合方式 | 典型应用场景 |
|---|---|---|---|
| 按时间 | ai_call_logs.created_at | 小时/天/周/月 | 成本趋势、预算对比 |
| 按租户 | ai_call_logs.tenant_id | SUM 聚合 | 计费、套餐设计 |
| 按功能模块 | ai_call_logs.feature_module | SUM 聚合 | 产品 ROI 分析 |
| 按模型 | ai_call_logs.model | SUM 聚合 | 模型选型、成本优化 |
| 按用户 | ai_call_logs.user_id | SUM + 限额 | 用户配额管理 |
| 按错误类型 | ai_call_logs.error_code | COUNT 分组 | 质量分析、SLA 监控 |
实施检查清单
在上线 AI 调用记录管理模块前,逐项确认以下事项:
- 所有 AI 调用路径都经过统一的中间件层,确保无遗漏记录
-
call_id生成策略保证全局唯一,支持幂等重试 - 模型价格表已配置所有在用模型,且包含历史价格变更记录
- 成本计算公式经过财务验证,与模型提供商账单误差 < 1%
- 调用记录包含完整的维度信息:租户、用户、功能模块、模型、状态
- 敏感数据脱敏策略已定义,prompt/response 内容的存储和展示符合隐私要求
- 索引覆盖所有高频查询场景,慢查询 < 1%
- 聚合任务正常运行,聚合数据与主表数据一致性经过校验
- 告警规则已配置,告警通知渠道畅通(电话/即时消息/邮件)
- 数据保留策略已定义:热数据保留周期、冷数据归档方案、过期数据清理机制
- 批量写入机制已实现,避免逐条 INSERT 导致数据库压力过大
- 管理后台的列表查询接口有分页限制,防止全表扫描
- 成本异常检测逻辑已覆盖:单用户突增、总体成本偏离预算、单模型错误率上升
- 灰度上线方案已准备,新旧系统并行运行期间数据双写校验
参考资料
- OpenAI Production Best Practices - OpenAI 官方生产环境最佳实践,涵盖扩展、安全和成本管理
- Vantage - Best AI Cost Management Tools - 2026 年 AI 成本管理工具对比,覆盖 OpenAI、Anthropic 等主流模型
- Holori - Top 10 AI Cost Visibility Tools in 2026 - AI 成本可视化工具评测,关注 token 使用追踪和支出分配
- Moesif - Tools for Logging and Monitoring API Calls - API 调用日志和监控工具全景,适用于 AI API 场景
- Merge.dev - API Logging Best Practices - API 日志最佳实践,包含结构化日志和安全脱敏策略
- Larridin - AI Dashboard: Build Usage Tracking That Drives Decisions - AI 用量仪表盘设计方法论,从数据驱动决策角度阐述 Dashboard 构建
- Newline - How to Build Dashboards for LLM Monitoring - LLM 监控 Dashboard 构建指南,覆盖数据流映射和字段设计