- model 层新增可选 UsageSink(nil ⇒ 逐字不变,既有用例零改动) - 组合根注入 UsageWriter:有界队列 1024 + 单写者 + 批量事务(128/批、2s 兜底), 队列满丢弃 + 限流 WARN,绝不阻塞模型调用;sink 挂 RuntimeLLM(热重载不断) - 迁移 0017:表 llm_usage(13 列,session_id 可空)+ idx_created_at + idx_session_id - 新端点 GET /api/v1/admin/llm/usage(owner|admin 读;缺省近 24h、上限 90 天、 超限 400 不静默裁剪;hitRate = cacheHit/(cacheHit+cacheMiss),分母 0 ⇒ 0; 绝不含凭据与正文;未接线/查库失败 ⇒ 500 usage_unavailable) - LLM_USAGE_RETENTION(默认 720h,≤0 = 关闭清理):启动清一次 + 每小时 - 面板 LLM 页「用量(近 24h)」小卡 + Docs 两份同步 v0.64 - 已知缺口(如实登记):session_id 恒 NULL,需 loop 侧在请求上打标,留列后接
47 lines
2.9 KiB
SQL
47 lines
2.9 KiB
SQL
-- 0017_llm_usage: 每次成功模型调用的用量落库(切片 U1,主人裁决「2.4 落」)。
|
||
--
|
||
-- 背景:T1 已在每次模型调用后记一条结构化日志(prompt / completion / total /
|
||
-- reasoning / cacheHit / cacheMiss / hitRate / tookMs),但**只落日志** ——
|
||
-- 看不见趋势、算不出按用途/模型的成本,面板也没有数据源。本表把同一条样本
|
||
-- 落进库里,供只读汇总端点(GET /api/v1/admin/llm/usage)聚合。
|
||
--
|
||
-- 口径:
|
||
-- * **无凭据、无正文**:本表只有 token 计数、模型名、用途标签与耗时 ——
|
||
-- apiKey / token / 消息正文一个字都不入库(写入方 model.UsageWriter 连
|
||
-- 参数都拿不到凭据)。
|
||
-- * cache_hit_tokens / cache_miss_tokens 是**输入侧两档**(提供商按命中/未命中
|
||
-- 两档计价,命中价约为未命中的 1/50),命中率 = hit / (hit + miss),分母为 0
|
||
-- 时按 0 处理(口径与 model.Response.CacheHitRate 同源)。
|
||
-- * session_id 可空。**当前尚无写入方**(恒为 NULL):模型适配器跑在 loop 之下、
|
||
-- 没有会话概念(见 internal/runtime/model/stream.go 的 StreamEvent.SessionID
|
||
-- 注释),把会话标识带到适配器边界需要 loop 侧在请求上打标 —— 那份文件本轮
|
||
-- 不在本切片改动范围内,故先留列、后接线。
|
||
-- * created_at 由 SQLite 以 UTC ISO-8601(毫秒)写入,与 0001 起的口径一致;
|
||
-- 汇总端点的时间窗按同一格式做**字典序**比较(定宽格式下等价于时间序)。
|
||
-- * 表名用复数,援引 AGENTS §10 已登记的例外(既有迁移口径一致)。
|
||
CREATE TABLE IF NOT EXISTS llm_usage (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
session_id TEXT,
|
||
purpose TEXT NOT NULL DEFAULT '',
|
||
adapter TEXT NOT NULL DEFAULT '',
|
||
model TEXT NOT NULL DEFAULT '',
|
||
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
||
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
||
cache_hit_tokens INTEGER NOT NULL DEFAULT 0,
|
||
cache_miss_tokens INTEGER NOT NULL DEFAULT 0,
|
||
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
||
total_tokens INTEGER NOT NULL DEFAULT 0,
|
||
took_ms INTEGER NOT NULL DEFAULT 0,
|
||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||
);
|
||
|
||
-- 汇总端点与保留期清理都按时间窗扫表(`created_at >= ? AND created_at <= ?` /
|
||
-- `created_at < ?`),这条索引是两者的唯一依赖。
|
||
CREATE INDEX IF NOT EXISTS idx_llm_usage_created_at
|
||
ON llm_usage (created_at);
|
||
|
||
-- 按会话下钻(面板将来要「这个会话烧了多少」)。当前 session_id 恒为 NULL,
|
||
-- 索引仍然建好 —— 它不影响写入正确性,接线后立刻可用。
|
||
CREATE INDEX IF NOT EXISTS idx_llm_usage_session_id
|
||
ON llm_usage (session_id);
|