dsh-layered-memory 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +37 -4
- package/README.md +35 -4
- package/dist/client.js +3016 -2976
- package/dist/config.d.ts +64 -4
- package/dist/config.js +12 -0
- package/dist/contract.d.ts +615 -0
- package/dist/contract.js +1 -0
- package/dist/hooks/recall.d.ts +2 -18
- package/dist/index.d.ts +50 -4
- package/dist/index.js +9 -3
- package/dist/llm-usage.d.ts +2 -1
- package/dist/llm.d.ts +30 -4
- package/dist/llm.js +79 -7
- package/dist/pipeline/rebuild.d.ts +2 -21
- package/dist/pipeline/runner.d.ts +3 -3
- package/dist/pipeline/runner.js +38 -10
- package/dist/settings.d.ts +18 -29
- package/dist/settings.js +81 -1
- package/dist/stats.d.ts +2 -26
- package/dist/stats.js +91 -21
- package/dist/store/cost-ledger.d.ts +78 -0
- package/dist/store/cost-ledger.js +173 -0
- package/dist/store/download-queue.d.ts +2 -20
- package/dist/store/embedding-source.d.ts +2 -52
- package/dist/store/runtime-installer.d.ts +2 -13
- package/dist/store/sqlite.d.ts +18 -0
- package/dist/store/sqlite.js +27 -0
- package/dist/token-cost.d.ts +21 -0
- package/dist/token-cost.js +178 -0
- package/package.json +8 -4
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
import type { MemoryLogger } from '../types.js';
|
|
2
2
|
import { type CatalogEntry } from './model-catalog.js';
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
modelId: string;
|
|
6
|
-
phase: DownloadPhase;
|
|
7
|
-
/** 当前文件序号(1-based)。 */
|
|
8
|
-
fileIndex: number;
|
|
9
|
-
fileCount: number;
|
|
10
|
-
/** 当前文件已收字节(含续传基线)。 */
|
|
11
|
-
fileReceived: number;
|
|
12
|
-
fileTotal: number;
|
|
13
|
-
/** 整模型累计字节(含已完成文件;分母 = 目录总大小)。 */
|
|
14
|
-
overallReceived: number;
|
|
15
|
-
overallTotal: number;
|
|
16
|
-
/** EMA 平滑速度(字节/秒)。 */
|
|
17
|
-
speedBps: number;
|
|
18
|
-
startedAt: number;
|
|
19
|
-
/** phase=error 时的原因。 */
|
|
20
|
-
error?: string;
|
|
21
|
-
}
|
|
3
|
+
import type { DownloadProgress } from '../contract.js';
|
|
4
|
+
export type { DownloadPhase, DownloadProgress } from '../contract.js';
|
|
22
5
|
export interface ModelStatus {
|
|
23
6
|
id: string;
|
|
24
7
|
/** none=未下载;partial=有断点/不完整;downloaded=全部文件就位且尺寸吻合。 */
|
|
@@ -104,4 +87,3 @@ export declare function resolveProxyUrl(setting: string | undefined, host: strin
|
|
|
104
87
|
/** 代理 URL 日志脱敏:剥掉 userinfo(内网代理常带 user:pass 凭据),只留 scheme//host;
|
|
105
88
|
* 解析失败的串原样也可能是凭据形态,返回占位符。 */
|
|
106
89
|
export declare function maskProxyUrl(proxy: string): string;
|
|
107
|
-
export {};
|
|
@@ -7,7 +7,8 @@ import { LocalEmbeddingService } from './local-embedding.js';
|
|
|
7
7
|
import { ModelDownloadQueue } from './download-queue.js';
|
|
8
8
|
import { RuntimeInstaller } from './runtime-installer.js';
|
|
9
9
|
import type { MemoryDb } from './sqlite.js';
|
|
10
|
-
|
|
10
|
+
import type { EmbeddingSourceKind, EmbeddingStateView } from '../contract.js';
|
|
11
|
+
export type { ApplyPhase, EmbeddingSourceKind, EmbeddingStateView, ReindexProgressState } from '../contract.js';
|
|
11
12
|
export interface EmbeddingSourceState {
|
|
12
13
|
source: EmbeddingSourceKind;
|
|
13
14
|
/** source=local 时启用的目录模型 id。 */
|
|
@@ -38,17 +39,6 @@ export declare function resolveInitialEmbedding(cfg: MemoryConfig, sourceStore:
|
|
|
38
39
|
/** 本地服务构造工厂(index.ts 的初始解析与 Manager 共用一份实现,防漂移)。
|
|
39
40
|
* 推理在 worker 线程(见 local-embedding.ts);此处只传 runtime 目录与模型目录。 */
|
|
40
41
|
export declare function makeLocalServiceFactory(installer: RuntimeInstaller, downloader: ModelDownloadQueue, logger?: MemoryLogger, maxInputChars?: number): (modelId: string) => LocalEmbeddingService | null;
|
|
41
|
-
export type ApplyPhase = 'idle' | 'installing-runtime' | 'warming' | 'switching' | 'reindexing' | 'done' | 'error';
|
|
42
|
-
export interface ReindexProgressState {
|
|
43
|
-
running: boolean;
|
|
44
|
-
l1Done: number;
|
|
45
|
-
l1Total: number;
|
|
46
|
-
l0Done: number;
|
|
47
|
-
l0Total: number;
|
|
48
|
-
startedAt: number;
|
|
49
|
-
cancelled: boolean;
|
|
50
|
-
error?: string;
|
|
51
|
-
}
|
|
52
42
|
export interface EmbeddingManagerDeps {
|
|
53
43
|
dataDir: string;
|
|
54
44
|
cfg: MemoryConfig;
|
|
@@ -119,43 +109,3 @@ export declare class EmbeddingManager {
|
|
|
119
109
|
/** RPC 快照(设置页嵌入区块数据源;client 忙时 1s 轮询)。 */
|
|
120
110
|
snapshot(): Promise<EmbeddingStateView>;
|
|
121
111
|
}
|
|
122
|
-
export interface EmbeddingStateView {
|
|
123
|
-
source: EmbeddingSourceKind;
|
|
124
|
-
activeModel: string | null;
|
|
125
|
-
ceilings: {
|
|
126
|
-
remote: boolean;
|
|
127
|
-
local: boolean;
|
|
128
|
-
};
|
|
129
|
-
runtime: {
|
|
130
|
-
targetVersion: string;
|
|
131
|
-
phase: 'idle' | 'installing' | 'ready' | 'error' | 'cancelled';
|
|
132
|
-
installedVersion: string | null;
|
|
133
|
-
elapsedMs: number;
|
|
134
|
-
lastLines: string[];
|
|
135
|
-
error?: string;
|
|
136
|
-
};
|
|
137
|
-
models: Array<{
|
|
138
|
-
id: string;
|
|
139
|
-
name: string;
|
|
140
|
-
dims: number;
|
|
141
|
-
contextTokens: number;
|
|
142
|
-
tags: string[];
|
|
143
|
-
description: string;
|
|
144
|
-
totalBytes: number;
|
|
145
|
-
bytesOnDisk: number;
|
|
146
|
-
state: 'none' | 'partial' | 'downloaded';
|
|
147
|
-
}>;
|
|
148
|
-
download: ReturnType<ModelDownloadQueue['getProgress']>;
|
|
149
|
-
apply: {
|
|
150
|
-
phase: ApplyPhase;
|
|
151
|
-
message: string;
|
|
152
|
-
startedAt: number;
|
|
153
|
-
busy: boolean;
|
|
154
|
-
};
|
|
155
|
-
local: {
|
|
156
|
-
state: 'idle' | 'loading' | 'ready' | 'failed' | 'terminated';
|
|
157
|
-
error: string | null;
|
|
158
|
-
} | null;
|
|
159
|
-
reindex: ReindexProgressState;
|
|
160
|
-
activeNote?: string;
|
|
161
|
-
}
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
import type { MemoryLogger } from '../types.js';
|
|
2
2
|
/** 钉死的 transformers.js 版本(D8:精确版本,升级插件时在此变更并重装运行时)。 */
|
|
3
3
|
export declare const PINNED_TRANSFORMERS_VERSION = "4.2.0";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** 插件钉死的目标版本。 */
|
|
7
|
-
targetVersion: string;
|
|
8
|
-
/** runtime 里实际就位的版本(未安装为 null)。 */
|
|
9
|
-
installedVersion: string | null;
|
|
10
|
-
startedAt: number;
|
|
11
|
-
/** 已运行毫秒(读时计算)。 */
|
|
12
|
-
elapsedMs: number;
|
|
13
|
-
/** npm 输出尾行(最近 5 行,让用户看到 npm 在干什么)。 */
|
|
14
|
-
lastLines: string[];
|
|
15
|
-
error?: string;
|
|
16
|
-
}
|
|
4
|
+
import type { RuntimeProgress } from '../contract.js';
|
|
5
|
+
export type { RuntimeProgress } from '../contract.js';
|
|
17
6
|
/** 子进程抽象(测试注入缝):注册输出行回调 + 终止 + 终态。 */
|
|
18
7
|
export interface SpawnedProcess {
|
|
19
8
|
onStdout(cb: (line: string) => void): void;
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface StoreCapabilities {
|
|
|
9
9
|
ftsSearch: boolean;
|
|
10
10
|
vectorSearch: boolean;
|
|
11
11
|
}
|
|
12
|
+
import { CostLedger } from './cost-ledger.js';
|
|
13
|
+
import type { BucketRow, CostAggregate, CostByLayer } from './cost-ledger.js';
|
|
14
|
+
export type { BucketRow, CostAggregate, CostByLayer } from './cost-ledger.js';
|
|
15
|
+
import type { CostByModel } from '../contract.js';
|
|
12
16
|
/** L1 检索命中(含 BM25/余弦归一分数)。 */
|
|
13
17
|
export interface L1SearchHit {
|
|
14
18
|
id: string;
|
|
@@ -23,6 +27,7 @@ export interface L1SearchHit {
|
|
|
23
27
|
export interface L0SearchHit extends L0MessageRecord {
|
|
24
28
|
score: number;
|
|
25
29
|
}
|
|
30
|
+
/** 已排序序列的中位数(偶数个取中间两者平均;空返回 0)。 */
|
|
26
31
|
export declare class MemoryDb {
|
|
27
32
|
private db;
|
|
28
33
|
private degraded;
|
|
@@ -44,6 +49,8 @@ export declare class MemoryDb {
|
|
|
44
49
|
private stmtL1FtsDelete;
|
|
45
50
|
private stmtL1FtsSearch;
|
|
46
51
|
private stmtL1FtsSearchFamily;
|
|
52
|
+
/** 成本账本(token_cost 表族;init 内初始化,未就绪时方法返回零值)。 */
|
|
53
|
+
readonly costLedger: CostLedger;
|
|
47
54
|
private stmtUpsertL0;
|
|
48
55
|
private stmtGetL0;
|
|
49
56
|
/** 主表存在性点查(同 L1:防御性 FTS 删除的前置判断)。 */
|
|
@@ -145,6 +152,17 @@ export declare class MemoryDb {
|
|
|
145
152
|
searchL1Vector(embedding: Float32Array, topK: number, family?: string): L1SearchHit[];
|
|
146
153
|
/** 批量 upsert L0 消息(元数据 + FTS;embeddings 与 records 等长,可省略)。 */
|
|
147
154
|
upsertL0Batch(records: L0MessageRecord[], embeddings?: Array<Float32Array | undefined>): boolean;
|
|
155
|
+
/** 记录一次蒸馏调用成本(委托 cost-ledger;语义见 CostLedger.insertCostCall)。 */
|
|
156
|
+
insertCostCall(provider: string, model: string, layer: string, inputChars: number, outputTokens: number, reasoningTokens: number, retentionDays: number): void;
|
|
157
|
+
/** 查询 token_cost 单窗口聚合(委托 cost-ledger;降级/异常返回零值)。 */
|
|
158
|
+
aggregateCost(since: number): {
|
|
159
|
+
total: CostAggregate;
|
|
160
|
+
byModel: CostByModel[];
|
|
161
|
+
};
|
|
162
|
+
/** 按层级归并聚合(委托 cost-ledger;降级/异常返回空数组)。 */
|
|
163
|
+
aggregateCostByLayer(since: number): CostByLayer[];
|
|
164
|
+
/** 按时间桶 + model 聚合(委托 cost-ledger;趋势图与日均/周均/月均共用)。 */
|
|
165
|
+
aggregateByBucket(bucketMs: number, offsetMs: number, since: number, layer: string): BucketRow[];
|
|
148
166
|
countL0(): number;
|
|
149
167
|
/** 统计 recorded_at >= iso 的消息数(状态面板"今日捕获"用)。 */
|
|
150
168
|
countL0Since(iso: string): number;
|
package/dist/store/sqlite.js
CHANGED
|
@@ -22,6 +22,9 @@ import { bm25RankToScore, buildFtsQuery, tokenizeForFts } from './search-utils.j
|
|
|
22
22
|
import { describeTokenizer, ensureTokenizer, tokenizerStamp } from '../util/tokenizer.js';
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const TAG = '[memory][sqlite]';
|
|
25
|
+
// 成本账本(token_cost 表族:类型 + 实现)已整体迁出到 ./cost-ledger.ts——与检索引擎
|
|
26
|
+
// 零关系的独立职责;此处 re-export 不断裂既有引用,MemoryDb 四个公开方法改为一行委托。
|
|
27
|
+
import { CostLedger } from './cost-ledger.js';
|
|
25
28
|
/** vec0 KNN 对遗留零向量的补偿缓冲(官方同款)。 */
|
|
26
29
|
const ZERO_VEC_BUFFER = 10;
|
|
27
30
|
/** IN 查询/删除的分块大小(保守避开 SQLite 变量数上限:现代构建 32766,老版 999)。 */
|
|
@@ -35,6 +38,10 @@ function chunkIds(ids) {
|
|
|
35
38
|
out.push(ids.slice(i, i + IN_CHUNK));
|
|
36
39
|
return out;
|
|
37
40
|
}
|
|
41
|
+
function emptyCostAggregate() {
|
|
42
|
+
return { calls: 0, inputChars: 0, outputTokens: 0, reasoningTokens: 0, avgOutputTokens: 0, medianOutputTokens: 0 };
|
|
43
|
+
}
|
|
44
|
+
/** 已排序序列的中位数(偶数个取中间两者平均;空返回 0)。 */
|
|
38
45
|
export class MemoryDb {
|
|
39
46
|
db;
|
|
40
47
|
degraded = false;
|
|
@@ -56,6 +63,8 @@ export class MemoryDb {
|
|
|
56
63
|
stmtL1FtsDelete;
|
|
57
64
|
stmtL1FtsSearch;
|
|
58
65
|
stmtL1FtsSearchFamily;
|
|
66
|
+
/** 成本账本(token_cost 表族;init 内初始化,未就绪时方法返回零值)。 */
|
|
67
|
+
costLedger = new CostLedger();
|
|
59
68
|
stmtUpsertL0;
|
|
60
69
|
stmtGetL0;
|
|
61
70
|
/** 主表存在性点查(同 L1:防御性 FTS 删除的前置判断)。 */
|
|
@@ -325,6 +334,8 @@ export class MemoryDb {
|
|
|
325
334
|
this.stmtGetL0 = this.db.prepare('SELECT session_id, role, message_text, recorded_at, timestamp FROM l0_conversations WHERE record_id = ?');
|
|
326
335
|
this.stmtL0Exists = this.db.prepare('SELECT 1 FROM l0_conversations WHERE record_id = ?');
|
|
327
336
|
this.prepareL0VecStatements();
|
|
337
|
+
// ── token_cost:蒸馏成本明细表(成本账本自治;见 cost-ledger.ts) ──
|
|
338
|
+
this.costLedger.init(this.db, this.logger);
|
|
328
339
|
// ── FTS5 全文索引(建表失败仅停用 FTS,不降级整个库) ──
|
|
329
340
|
try {
|
|
330
341
|
// 索引重建判据(FTS5 无法 ALTER,只能 drop 后从源表全量回灌):
|
|
@@ -978,6 +989,22 @@ export class MemoryDb {
|
|
|
978
989
|
return false;
|
|
979
990
|
}
|
|
980
991
|
}
|
|
992
|
+
/** 记录一次蒸馏调用成本(委托 cost-ledger;语义见 CostLedger.insertCostCall)。 */
|
|
993
|
+
insertCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens, retentionDays) {
|
|
994
|
+
this.costLedger.insertCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens, retentionDays);
|
|
995
|
+
}
|
|
996
|
+
/** 查询 token_cost 单窗口聚合(委托 cost-ledger;降级/异常返回零值)。 */
|
|
997
|
+
aggregateCost(since) {
|
|
998
|
+
return this.costLedger.aggregateCost(since);
|
|
999
|
+
}
|
|
1000
|
+
/** 按层级归并聚合(委托 cost-ledger;降级/异常返回空数组)。 */
|
|
1001
|
+
aggregateCostByLayer(since) {
|
|
1002
|
+
return this.costLedger.aggregateCostByLayer(since);
|
|
1003
|
+
}
|
|
1004
|
+
/** 按时间桶 + model 聚合(委托 cost-ledger;趋势图与日均/周均/月均共用)。 */
|
|
1005
|
+
aggregateByBucket(bucketMs, offsetMs, since, layer) {
|
|
1006
|
+
return this.costLedger.aggregateByBucket(bucketMs, offsetMs, since, layer);
|
|
1007
|
+
}
|
|
981
1008
|
countL0() {
|
|
982
1009
|
if (this.degraded)
|
|
983
1010
|
return 0;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 蒸馏成本看板账本:把每次蒸馏调用(model × layer)的 token 成本写入 SQLite 明细表。
|
|
3
|
+
*
|
|
4
|
+
* 模块级单例 + init 注入 db:callLLM 拿不到 db(db 在 index.ts 运行时创建),
|
|
5
|
+
* 故通过 initTokenCost(db, retentionDays) 在插件启动时注入;recordCostCall 每次调用写一行明细。
|
|
6
|
+
*
|
|
7
|
+
* 与作者 llm-usage.ts 的关系:作者是"按 layer 累计的纯内存计数器"(给 bench 用);
|
|
8
|
+
* 本模块补上三个缺口——按 model 分组、持久化(保留期可配置,默认 365 天)、面向 UI 成本看板。
|
|
9
|
+
*/
|
|
10
|
+
import type { CostSnapshot, Granularity } from './contract.js';
|
|
11
|
+
import type { DistillLayer } from './llm-usage.js';
|
|
12
|
+
import type { MemoryDb } from './store/sqlite.js';
|
|
13
|
+
export type { CostWindow, Granularity, ModelMetrics, LayerMetrics, LayerWindow, LayerCost, TrendBucket, TrendSnapshot, CostSnapshot, } from './contract.js';
|
|
14
|
+
/** 插件启动时注入 db 与明细保留期(index.ts 调用;retentionDays 0 = 永久保留)。 */
|
|
15
|
+
export declare function initTokenCost(d: MemoryDb, retention: number): void;
|
|
16
|
+
/** 插件卸载时清空 db 引用(index.ts 的 ctx.effect 清理里调用,防悬空引用)。 */
|
|
17
|
+
export declare function resetTokenCost(): void;
|
|
18
|
+
/** 记录一次蒸馏调用成本(callLLM 出口调用;provider/model 由调用方传入)。 */
|
|
19
|
+
export declare function recordCostCall(provider: string, model: string, layer: DistillLayer, inputChars: number, outputTokens: number, reasoningTokens: number): void;
|
|
20
|
+
/** 读成本看板快照(db 未注入/降级时返回全零结构,不抛错;rangeDays>0 = 趋势展示近 N 天)。 */
|
|
21
|
+
export declare function snapshotTokenCost(granularity: Granularity, rangeDays: number): CostSnapshot;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
let db = null;
|
|
2
|
+
let retentionDays = 365;
|
|
3
|
+
/** 插件启动时注入 db 与明细保留期(index.ts 调用;retentionDays 0 = 永久保留)。 */
|
|
4
|
+
export function initTokenCost(d, retention) {
|
|
5
|
+
db = d;
|
|
6
|
+
retentionDays = Math.max(0, Math.round(retention));
|
|
7
|
+
}
|
|
8
|
+
/** 插件卸载时清空 db 引用(index.ts 的 ctx.effect 清理里调用,防悬空引用)。 */
|
|
9
|
+
export function resetTokenCost() {
|
|
10
|
+
db = null;
|
|
11
|
+
}
|
|
12
|
+
/** 记录一次蒸馏调用成本(callLLM 出口调用;provider/model 由调用方传入)。 */
|
|
13
|
+
export function recordCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens) {
|
|
14
|
+
if (!db)
|
|
15
|
+
return;
|
|
16
|
+
db.insertCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens, retentionDays);
|
|
17
|
+
}
|
|
18
|
+
/** 四窗口定义:range + 回看毫秒数(all 的 ms 恒 0)。 */
|
|
19
|
+
const WINDOW_DEFS = [
|
|
20
|
+
{ range: 'day', ms: 24 * 3600_000 },
|
|
21
|
+
{ range: 'week', ms: 7 * 24 * 3600_000 },
|
|
22
|
+
{ range: 'month', ms: 30 * 24 * 3600_000 },
|
|
23
|
+
{ range: 'all', ms: 0 },
|
|
24
|
+
];
|
|
25
|
+
/** 趋势桶宽(毫秒)与桶数。 */
|
|
26
|
+
const TREND_MS = { day: 24 * 3600_000, week: 7 * 24 * 3600_000, month: 30 * 24 * 3600_000 };
|
|
27
|
+
const TREND_COUNT = { day: 30, week: 12, month: 12 };
|
|
28
|
+
/** 三个归并层级。 */
|
|
29
|
+
const LAYERS = ['l1', 'l2', 'l3'];
|
|
30
|
+
/** 本地时区偏移(东正西负):把 SQL 端 UTC epoch 桶边界对齐到本地午夜。 */
|
|
31
|
+
function localOffsetMs() {
|
|
32
|
+
return -new Date().getTimezoneOffset() * 60_000;
|
|
33
|
+
}
|
|
34
|
+
/** 已排序序列的中位数(偶数取中间两者平均;空返回 0)。 */
|
|
35
|
+
function medianOf(sorted) {
|
|
36
|
+
const n = sorted.length;
|
|
37
|
+
if (n === 0)
|
|
38
|
+
return 0;
|
|
39
|
+
const mid = Math.floor(n / 2);
|
|
40
|
+
return n % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
41
|
+
}
|
|
42
|
+
/** provider/model 复合键(避免跨 provider 同名 model 在聚合里混淆)。 */
|
|
43
|
+
function modelKey(provider, model) {
|
|
44
|
+
return provider + '/' + model;
|
|
45
|
+
}
|
|
46
|
+
/** 从某模型某粒度的 bucket 序列算均值/中位数(活跃桶口径:只除有调用的桶数)。 */
|
|
47
|
+
function statOf(rows) {
|
|
48
|
+
const active = rows.filter((r) => r.calls > 0);
|
|
49
|
+
if (active.length === 0)
|
|
50
|
+
return { avgCalls: 0, avgOutput: 0, medianOutput: 0 };
|
|
51
|
+
const calls = active.reduce((s, r) => s + r.calls, 0);
|
|
52
|
+
const output = active.reduce((s, r) => s + r.outputTokens, 0);
|
|
53
|
+
return {
|
|
54
|
+
avgCalls: calls / active.length,
|
|
55
|
+
avgOutput: output / active.length,
|
|
56
|
+
medianOutput: medianOf(active.map((r) => r.outputTokens).sort((a, b) => a - b)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** 从 day/week/month 三组 bucket 行组装每个模型的统计指标(provider/model 复合键)。 */
|
|
60
|
+
function buildModelMetrics(dayRows, weekRows, monthRows) {
|
|
61
|
+
const keys = new Set();
|
|
62
|
+
for (const r of dayRows)
|
|
63
|
+
keys.add(modelKey(r.provider, r.model));
|
|
64
|
+
for (const r of weekRows)
|
|
65
|
+
keys.add(modelKey(r.provider, r.model));
|
|
66
|
+
for (const r of monthRows)
|
|
67
|
+
keys.add(modelKey(r.provider, r.model));
|
|
68
|
+
return Array.from(keys)
|
|
69
|
+
.sort()
|
|
70
|
+
.map((key) => {
|
|
71
|
+
const d = statOf(dayRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
72
|
+
const w = statOf(weekRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
73
|
+
const m = statOf(monthRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
74
|
+
return {
|
|
75
|
+
model: key,
|
|
76
|
+
dayCalls: d.avgCalls,
|
|
77
|
+
weekCalls: w.avgCalls,
|
|
78
|
+
monthCalls: m.avgCalls,
|
|
79
|
+
dayOutput: d.avgOutput,
|
|
80
|
+
dayMedian: d.medianOutput,
|
|
81
|
+
weekOutput: w.avgOutput,
|
|
82
|
+
weekMedian: w.medianOutput,
|
|
83
|
+
monthOutput: m.avgOutput,
|
|
84
|
+
monthMedian: m.medianOutput,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** 从某层级某粒度的 bucket 行生成连续趋势桶(空桶补 0;count 为桶数,由调用方按展示范围算)。 */
|
|
89
|
+
function buildTrend(rows, granularity, now, count) {
|
|
90
|
+
const bucketMs = TREND_MS[granularity];
|
|
91
|
+
const offset = localOffsetMs();
|
|
92
|
+
const cur = Math.floor((now + offset) / bucketMs);
|
|
93
|
+
const buckets = [];
|
|
94
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
95
|
+
const b = cur - i;
|
|
96
|
+
buckets.push({ ts: b * bucketMs - offset, total: 0, byModel: {} });
|
|
97
|
+
}
|
|
98
|
+
for (const r of rows) {
|
|
99
|
+
const idx = count - 1 - (cur - r.bucket);
|
|
100
|
+
if (idx < 0 || idx >= count)
|
|
101
|
+
continue;
|
|
102
|
+
const tb = buckets[idx];
|
|
103
|
+
tb.total += r.outputTokens;
|
|
104
|
+
const key = modelKey(r.provider, r.model);
|
|
105
|
+
tb.byModel[key] = (tb.byModel[key] ?? 0) + r.outputTokens;
|
|
106
|
+
}
|
|
107
|
+
return buckets;
|
|
108
|
+
}
|
|
109
|
+
/** 读成本看板快照(db 未注入/降级时返回全零结构,不抛错;rangeDays>0 = 趋势展示近 N 天)。 */
|
|
110
|
+
export function snapshotTokenCost(granularity, rangeDays) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const emptyWindow = (range, ms) => ({
|
|
113
|
+
range,
|
|
114
|
+
since: ms === 0 ? 0 : now - ms,
|
|
115
|
+
calls: 0,
|
|
116
|
+
inputChars: 0,
|
|
117
|
+
outputTokens: 0,
|
|
118
|
+
reasoningTokens: 0,
|
|
119
|
+
avgOutputTokens: 0,
|
|
120
|
+
medianOutputTokens: 0,
|
|
121
|
+
});
|
|
122
|
+
if (!db) {
|
|
123
|
+
return {
|
|
124
|
+
windows: WINDOW_DEFS.map((w) => emptyWindow(w.range, w.ms)),
|
|
125
|
+
byModel: [],
|
|
126
|
+
byLayer: [],
|
|
127
|
+
byLayerStats: [],
|
|
128
|
+
trend: { granularity, byLayer: { l1: [], l2: [], l3: [] } },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// 四窗口总聚合(累计窗口顺带取 byModel)
|
|
132
|
+
const d = db;
|
|
133
|
+
const windows = [];
|
|
134
|
+
let byModel = [];
|
|
135
|
+
for (const w of WINDOW_DEFS) {
|
|
136
|
+
const since = w.ms === 0 ? 0 : now - w.ms;
|
|
137
|
+
const agg = d.aggregateCost(since);
|
|
138
|
+
windows.push({ range: w.range, since, ...agg.total });
|
|
139
|
+
if (w.range === 'all')
|
|
140
|
+
byModel = agg.byModel;
|
|
141
|
+
}
|
|
142
|
+
// 按层级 × 窗口聚合(层级×窗口表格):每个窗口调一次 aggregateCostByLayer,再按层组装
|
|
143
|
+
const layerByWindow = WINDOW_DEFS.map((w) => {
|
|
144
|
+
const since = w.ms === 0 ? 0 : now - w.ms;
|
|
145
|
+
return { range: w.range, rows: d.aggregateCostByLayer(since) };
|
|
146
|
+
});
|
|
147
|
+
const byLayer = LAYERS.map((layer) => ({
|
|
148
|
+
layer,
|
|
149
|
+
windows: layerByWindow.map(({ range, rows }) => {
|
|
150
|
+
const row = rows.find((r) => r.layer === layer);
|
|
151
|
+
return {
|
|
152
|
+
range,
|
|
153
|
+
calls: row?.calls ?? 0,
|
|
154
|
+
inputChars: row?.inputChars ?? 0,
|
|
155
|
+
outputTokens: row?.outputTokens ?? 0,
|
|
156
|
+
reasoningTokens: row?.reasoningTokens ?? 0,
|
|
157
|
+
avgOutputTokens: row?.avgOutputTokens ?? 0,
|
|
158
|
+
medianOutputTokens: row?.medianOutputTokens ?? 0,
|
|
159
|
+
};
|
|
160
|
+
}),
|
|
161
|
+
}));
|
|
162
|
+
// 每层级模型统计(全量历史口径)+ 趋势(按展示范围)
|
|
163
|
+
const offset = localOffsetMs();
|
|
164
|
+
const byLayerStats = [];
|
|
165
|
+
const trendByLayer = { l1: [], l2: [], l3: [] };
|
|
166
|
+
// 趋势展示范围:rangeDays > 0 = 近 N 天(强制按「日」粒度出 N 个日桶,不受周/月聚合影响);否则用默认窗口
|
|
167
|
+
const trendGranularity = rangeDays > 0 ? 'day' : granularity;
|
|
168
|
+
const trendSince = rangeDays > 0 ? now - rangeDays * 24 * 3600_000 : 0;
|
|
169
|
+
const trendCount = rangeDays > 0 ? rangeDays : TREND_COUNT[granularity];
|
|
170
|
+
for (const layer of LAYERS) {
|
|
171
|
+
const dayRows = d.aggregateByBucket(TREND_MS.day, offset, 0, layer);
|
|
172
|
+
const weekRows = d.aggregateByBucket(TREND_MS.week, offset, 0, layer);
|
|
173
|
+
const monthRows = d.aggregateByBucket(TREND_MS.month, offset, 0, layer);
|
|
174
|
+
byLayerStats.push({ layer, models: buildModelMetrics(dayRows, weekRows, monthRows) });
|
|
175
|
+
trendByLayer[layer] = buildTrend(d.aggregateByBucket(TREND_MS[trendGranularity], offset, trendSince, layer), trendGranularity, now, trendCount);
|
|
176
|
+
}
|
|
177
|
+
return { windows, byModel, byLayer, byLayerStats, trend: { granularity: trendGranularity, byLayer: trendByLayer } };
|
|
178
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-layered-memory",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,9 +31,11 @@
|
|
|
31
31
|
"cordis.patch.yml"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "tsc -p tsconfig.json && node scripts/copy-client.mjs",
|
|
35
|
-
"smoke": "node dist-smoke/smoke.js",
|
|
36
|
-
"verify-catalog": "npm run build && node scripts/verify-catalog.mjs"
|
|
34
|
+
"build": "tsc -p tsconfig.json && node scripts/build-client.mjs && node scripts/copy-client.mjs",
|
|
35
|
+
"smoke": "npm run build:smoke && node dist-smoke/smoke.js",
|
|
36
|
+
"verify-catalog": "npm run build && node scripts/verify-catalog.mjs",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
38
|
+
"build:smoke": "tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution nodenext --target es2022 --strict --skipLibCheck --esModuleInterop"
|
|
37
39
|
},
|
|
38
40
|
"engines": {
|
|
39
41
|
"node": ">=22.16.0"
|
|
@@ -78,6 +80,8 @@
|
|
|
78
80
|
"@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
|
|
79
81
|
"@deepseek-ai/dsh-tools": "0.1.1-rc.2",
|
|
80
82
|
"@types/node": "^22.0.0",
|
|
83
|
+
"@types/react": "^19.2.18",
|
|
84
|
+
"esbuild": "^0.28.2",
|
|
81
85
|
"typescript": "^5.6.0"
|
|
82
86
|
}
|
|
83
87
|
}
|