feat: 添加缓存 Token 独立展示(cache_creation / cache_read)

从 logs 表 other JSON 字段提取 cache_creation_tokens 和 cache_tokens,
在排名、日志、聚合、详情页分别展示,total_tokens 包含缓存部分。
This commit is contained in:
2026-04-20 19:55:09 +08:00
parent 004cdd9fc9
commit c5c91cc157
6 changed files with 104 additions and 27 deletions

View File

@@ -6,6 +6,16 @@ const REAL_MODEL = `COALESCE(
THEN other::jsonb->>'upstream_model_name' END,
model_name)`;
const CACHE_CREATION = `COALESCE(
CASE WHEN other IS NOT NULL AND other != '' AND other::jsonb ? 'cache_creation_tokens'
THEN (other::jsonb->>'cache_creation_tokens')::bigint END,
0)`;
const CACHE_READ = `COALESCE(
CASE WHEN other IS NOT NULL AND other != '' AND other::jsonb ? 'cache_tokens'
THEN (other::jsonb->>'cache_tokens')::bigint END,
0)`;
// ── 数据时间边界 ────────────────────────────────────────────────
export async function getDateRange(): Promise<{ minDate: string; maxDate: string }> {
@@ -43,6 +53,8 @@ export interface OverviewData {
total_tokens: number;
total_prompt: number;
total_completion: number;
total_cache_creation: number;
total_cache_read: number;
total_quota: number;
active_users: number;
active_models: number;
@@ -62,6 +74,8 @@ export async function getOverview(
COALESCE(SUM(prompt_tokens + completion_tokens), 0)::bigint as total_tokens,
COALESCE(SUM(prompt_tokens), 0)::bigint as total_prompt,
COALESCE(SUM(completion_tokens), 0)::bigint as total_completion,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as total_cache_creation,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as total_cache_read,
COALESCE(SUM(quota), 0)::bigint as total_quota,
COUNT(DISTINCT user_id)::int as active_users,
COUNT(DISTINCT ${REAL_MODEL})::int as active_models,
@@ -72,9 +86,11 @@ export async function getOverview(
const r = rows[0];
return {
total_calls: Number(r.total_calls),
total_tokens: Number(r.total_tokens),
total_tokens: Number(r.total_tokens) + Number(r.total_cache_creation) + Number(r.total_cache_read),
total_prompt: Number(r.total_prompt),
total_completion: Number(r.total_completion),
total_cache_creation: Number(r.total_cache_creation),
total_cache_read: Number(r.total_cache_read),
total_quota: Number(r.total_quota),
active_users: Number(r.active_users),
active_models: Number(r.active_models),
@@ -89,6 +105,8 @@ export interface TrendPoint {
calls: number;
prompt_tokens: number;
completion_tokens: number;
cache_creation_tokens: number;
cache_read_tokens: number;
total_tokens: number;
quota: number;
}
@@ -112,6 +130,8 @@ export async function getTrends(
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt_tokens,
COALESCE(SUM(completion_tokens), 0)::bigint as completion_tokens,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation_tokens,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read_tokens,
COALESCE(SUM(quota), 0)::bigint as quota
FROM logs WHERE ${where}
GROUP BY date ORDER BY date`,
@@ -123,7 +143,9 @@ export async function getTrends(
calls: Number(r.calls),
prompt_tokens: Number(r.prompt_tokens),
completion_tokens: Number(r.completion_tokens),
total_tokens: Number(r.prompt_tokens) + Number(r.completion_tokens),
cache_creation_tokens: Number(r.cache_creation_tokens),
cache_read_tokens: Number(r.cache_read_tokens),
total_tokens: Number(r.prompt_tokens) + Number(r.completion_tokens) + Number(r.cache_creation_tokens) + Number(r.cache_read_tokens),
quota: Number(r.quota),
}));
}
@@ -137,6 +159,8 @@ export interface RankingItem {
calls: number;
prompt_tokens: number;
completion_tokens: number;
cache_creation_tokens: number;
cache_read_tokens: number;
total_tokens: number;
quota: number;
quota_usd: number;
@@ -172,10 +196,12 @@ export async function getUserRanking(
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
COALESCE(SUM(quota), 0)::bigint as quota
FROM logs WHERE ${where}
GROUP BY user_id, username
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) DESC
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
LIMIT $${params.length}`,
params
);
@@ -188,7 +214,9 @@ export async function getUserRanking(
calls: Number(r.calls),
prompt_tokens: Number(r.prompt),
completion_tokens: Number(r.completion),
total_tokens: Number(r.prompt) + Number(r.completion),
cache_creation_tokens: Number(r.cache_creation),
cache_read_tokens: Number(r.cache_read),
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
quota: Number(r.quota),
quota_usd: Number(r.quota) / 500000,
}));
@@ -208,10 +236,12 @@ export async function getModelRanking(
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
COALESCE(SUM(quota), 0)::bigint as quota
FROM logs WHERE ${where}
GROUP BY model
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) DESC
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
LIMIT $${params.length}`,
params
);
@@ -222,7 +252,9 @@ export async function getModelRanking(
calls: Number(r.calls),
prompt_tokens: Number(r.prompt),
completion_tokens: Number(r.completion),
total_tokens: Number(r.prompt) + Number(r.completion),
cache_creation_tokens: Number(r.cache_creation),
cache_read_tokens: Number(r.cache_read),
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
quota: Number(r.quota),
quota_usd: Number(r.quota) / 500000,
}));
@@ -244,10 +276,12 @@ export async function getChannelRanking(
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
COALESCE(SUM(quota), 0)::bigint as quota
FROM logs WHERE ${where}
GROUP BY channel_id
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) DESC
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
LIMIT $${params.length}`,
params
);
@@ -259,7 +293,9 @@ export async function getChannelRanking(
calls: Number(r.calls),
prompt_tokens: Number(r.prompt),
completion_tokens: Number(r.completion),
total_tokens: Number(r.prompt) + Number(r.completion),
cache_creation_tokens: Number(r.cache_creation),
cache_read_tokens: Number(r.cache_read),
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
quota: Number(r.quota),
quota_usd: Number(r.quota) / 500000,
}));
@@ -288,6 +324,8 @@ export async function getUserDetail(
`SELECT COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens),0)::bigint as prompt,
COALESCE(SUM(completion_tokens),0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where} AND username = $${params.length}`,
params
@@ -302,6 +340,8 @@ export async function getUserDetail(
`SELECT ${REAL_MODEL} as model,
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens + completion_tokens),0)::bigint as tokens,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where2} AND username = $${params2.length}
GROUP BY model
@@ -323,12 +363,14 @@ export async function getUserDetail(
calls: Number(o.calls),
prompt_tokens: Number(o.prompt),
completion_tokens: Number(o.completion),
total_tokens: Number(o.prompt) + Number(o.completion),
cache_creation_tokens: Number(o.cache_creation),
cache_read_tokens: Number(o.cache_read),
total_tokens: Number(o.prompt) + Number(o.completion) + Number(o.cache_creation) + Number(o.cache_read),
quota: Number(o.quota),
models: models.map((m) => ({
name: m.model,
calls: Number(m.calls),
total_tokens: Number(m.tokens),
total_tokens: Number(m.tokens) + Number(m.cache_creation) + Number(m.cache_read),
quota: Number(m.quota),
})),
};
@@ -347,6 +389,8 @@ export async function getModelDetail(
`SELECT COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens),0)::bigint as prompt,
COALESCE(SUM(completion_tokens),0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where} AND ${REAL_MODEL} = $${params.length}`,
params
@@ -361,6 +405,8 @@ export async function getModelDetail(
`SELECT user_id, username,
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens + completion_tokens),0)::bigint as tokens,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where2} AND ${REAL_MODEL} = $${params2.length}
GROUP BY user_id, username
@@ -373,12 +419,14 @@ export async function getModelDetail(
calls: Number(o.calls),
prompt_tokens: Number(o.prompt),
completion_tokens: Number(o.completion),
total_tokens: Number(o.prompt) + Number(o.completion),
cache_creation_tokens: Number(o.cache_creation),
cache_read_tokens: Number(o.cache_read),
total_tokens: Number(o.prompt) + Number(o.completion) + Number(o.cache_creation) + Number(o.cache_read),
quota: Number(o.quota),
users: users.map((u) => ({
name: displayNames[u.user_id] || u.username,
calls: Number(u.calls),
total_tokens: Number(u.tokens),
total_tokens: Number(u.tokens) + Number(u.cache_creation) + Number(u.cache_read),
quota: Number(u.quota),
})),
};
@@ -399,6 +447,8 @@ export async function getChannelDetail(
`SELECT COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens),0)::bigint as prompt,
COALESCE(SUM(completion_tokens),0)::bigint as completion,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where} AND channel_id = $${params.length}`,
params
@@ -412,6 +462,8 @@ export async function getChannelDetail(
`SELECT ${REAL_MODEL} as model,
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens + completion_tokens),0)::bigint as tokens,
COALESCE(SUM(${CACHE_CREATION}),0)::bigint as cache_creation,
COALESCE(SUM(${CACHE_READ}),0)::bigint as cache_read,
COALESCE(SUM(quota),0)::bigint as quota
FROM logs WHERE ${where2} AND channel_id = $${params2.length}
GROUP BY model
@@ -425,12 +477,14 @@ export async function getChannelDetail(
calls: Number(o.calls),
prompt_tokens: Number(o.prompt),
completion_tokens: Number(o.completion),
total_tokens: Number(o.prompt) + Number(o.completion),
cache_creation_tokens: Number(o.cache_creation),
cache_read_tokens: Number(o.cache_read),
total_tokens: Number(o.prompt) + Number(o.completion) + Number(o.cache_creation) + Number(o.cache_read),
quota: Number(o.quota),
models: models.map((m) => ({
name: m.model,
calls: Number(m.calls),
total_tokens: Number(m.tokens),
total_tokens: Number(m.tokens) + Number(m.cache_creation) + Number(m.cache_read),
quota: Number(m.quota),
})),
};
@@ -449,6 +503,8 @@ export interface LogEntry {
channel_id: number;
prompt_tokens: number;
completion_tokens: number;
cache_creation_tokens: number;
cache_read_tokens: number;
total_tokens: number;
quota: number;
quota_usd: number;
@@ -513,6 +569,8 @@ export async function getLogs(options: {
`SELECT id, created_at, user_id, username, model_name,
${REAL_MODEL} as real_model,
channel_id, prompt_tokens, completion_tokens, quota,
${CACHE_CREATION} as cache_creation,
${CACHE_READ} as cache_read,
use_time, is_stream, token_name
FROM logs WHERE ${where}
ORDER BY id DESC
@@ -535,7 +593,9 @@ export async function getLogs(options: {
channel_id: Number(r.channel_id),
prompt_tokens: Number(r.prompt_tokens),
completion_tokens: Number(r.completion_tokens),
total_tokens: Number(r.prompt_tokens) + Number(r.completion_tokens),
cache_creation_tokens: Number(r.cache_creation),
cache_read_tokens: Number(r.cache_read),
total_tokens: Number(r.prompt_tokens) + Number(r.completion_tokens) + Number(r.cache_creation) + Number(r.cache_read),
quota: Number(r.quota),
quota_usd: Number(r.quota) / 500000,
use_time: Number(r.use_time),