feat(llm): 用量落库 + 只读汇总端点 + 面板用量小卡(契约 v0.64)
- 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 侧在请求上打标,留列后接
This commit is contained in:
1 parent
18381f65fd
commit
be38651ba4
32 files changed
+2233
-44
No files matched your search
@@ -75,6 +75,7 @@ export {
|
||||
adminListRag,
|
||||
adminListSessions,
|
||||
adminListUsers,
|
||||
adminLlmUsage,
|
||||
adminMemorySearch,
|
||||
adminPluginInstall,
|
||||
adminPluginUninstall,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
AdminLlmConfig,
|
||||
AdminLlmProvider,
|
||||
AdminLlmResetResult,
|
||||
AdminLlmUsage,
|
||||
AdminPluginRecord,
|
||||
AdminRagHit,
|
||||
AdminRagResponse,
|
||||
@@ -100,6 +101,20 @@ export function adminGetLlmCatalog(opts: { refresh?: boolean } = {}): Promise<Ad
|
||||
const qs = opts.refresh ? "?refresh=1" : "";
|
||||
return request<AdminLlmCatalog>(`/admin/llm/catalog${qs}`);
|
||||
}
|
||||
/** GET /api/v1/admin/llm/usage — LLM 用量汇总(owner|admin 读,契约 v0.64,切片 U1):
|
||||
* 一块时间窗内的调用数 / 输入·输出 token / 缓存两档与命中率,并按用途与模型分解。
|
||||
* `since`/`until` 接受 RFC3339 或 Unix 毫秒;缺省窗口 = 近 24h,跨度上限 90 天
|
||||
* (超限后端回 400 bad_request,**不静默裁剪**)。响应里绝不含凭据与正文。 */
|
||||
export function adminLlmUsage(
|
||||
params: { since?: string; until?: string; purpose?: string } = {}
|
||||
): Promise<AdminLlmUsage> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params.since) qs.set("since", params.since);
|
||||
if (params.until) qs.set("until", params.until);
|
||||
if (params.purpose) qs.set("purpose", params.purpose);
|
||||
const q = qs.toString();
|
||||
return request<AdminLlmUsage>(`/admin/llm/usage${q ? `?${q}` : ""}`);
|
||||
}
|
||||
/** GET /api/v1/admin/automations */
|
||||
export function adminListAutomations(): Promise<AdminAutomationRecord[]> {
|
||||
return request<AdminAutomationRecord[]>("/admin/automations");
|
||||
|
||||
@@ -244,3 +244,43 @@ export interface AdminStorageStatus {
|
||||
documents: AdminStorageDocuments;
|
||||
vector: AdminStorageVector;
|
||||
}
|
||||
|
||||
/* --- LLM 用量汇总(切片 U1,契约 v0.64)。字段名与
|
||||
* internal/services/gateway/admin_llm_usage.go 的 DTO 逐字对齐;
|
||||
* **绝不含凭据、也绝不含消息正文**(那些东西根本没进 `llm_usage` 表)。 --- */
|
||||
|
||||
/** 一个时间窗(或一个分组)内的用量合计。`hitRate` = cacheHit / (cacheHit +
|
||||
* cacheMiss),**分母为 0 时后端下发 0**(不编造其它值)。 */
|
||||
export interface AdminLlmUsageTotals {
|
||||
calls: number;
|
||||
prompt: number;
|
||||
completion: number;
|
||||
cacheHit: number;
|
||||
cacheMiss: number;
|
||||
hitRate: number;
|
||||
}
|
||||
|
||||
/** 本次生效的时间窗与用途过滤(缺省补齐后的真值;`purpose` 为空 = 未过滤)。 */
|
||||
export interface AdminLlmUsageWindow {
|
||||
since: string;
|
||||
until: string;
|
||||
purpose: string;
|
||||
}
|
||||
|
||||
/** 按用途分解的一行。 */
|
||||
export interface AdminLlmUsagePurposeRow extends AdminLlmUsageTotals {
|
||||
purpose: string;
|
||||
}
|
||||
|
||||
/** 按模型分解的一行。 */
|
||||
export interface AdminLlmUsageModelRow extends AdminLlmUsageTotals {
|
||||
model: string;
|
||||
}
|
||||
|
||||
/** GET /api/v1/admin/llm/usage — LLM 用量汇总(owner|admin 读)。 */
|
||||
export interface AdminLlmUsage {
|
||||
window: AdminLlmUsageWindow;
|
||||
totals: AdminLlmUsageTotals;
|
||||
byPurpose: AdminLlmUsagePurposeRow[];
|
||||
byModel: AdminLlmUsageModelRow[];
|
||||
}
|
||||
Reference in new issue
Block a user