dsh-prime-memory 0.22.2 → 0.24.1
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/CHANGELOG.en.md +421 -0
- package/CHANGELOG.ja.md +416 -0
- package/CHANGELOG.ko.md +416 -0
- package/CHANGELOG.md +120 -0
- package/dist/client.js +1266 -845
- package/dist/config.d.ts +131 -1341
- package/dist/config.js +89 -4
- package/dist/conflict-service.d.ts +7 -1
- package/dist/conflict-service.js +7 -1
- package/dist/contract.d.ts +211 -0
- package/dist/domain-gate.d.ts +11 -0
- package/dist/domain-gate.js +15 -0
- package/dist/hooks/obsolete-notice.d.ts +26 -0
- package/dist/hooks/obsolete-notice.js +64 -0
- package/dist/hooks/recall.d.ts +39 -0
- package/dist/hooks/recall.js +110 -30
- package/dist/index.d.ts +1 -1127
- package/dist/index.js +52 -2
- package/dist/memory-types.d.ts +69 -0
- package/dist/memory-types.js +165 -0
- package/dist/memport/format.d.ts +6 -1
- package/dist/memport/format.js +42 -2
- package/dist/pipeline/l1.d.ts +44 -2
- package/dist/pipeline/l1.js +163 -9
- package/dist/pipeline/md-sync.d.ts +65 -0
- package/dist/pipeline/md-sync.js +456 -0
- package/dist/pipeline/ruminate.d.ts +46 -1
- package/dist/pipeline/ruminate.js +109 -0
- package/dist/pipeline/runner.d.ts +10 -0
- package/dist/pipeline/runner.js +13 -2
- package/dist/pipeline/workspace-relink.d.ts +60 -0
- package/dist/pipeline/workspace-relink.js +73 -0
- package/dist/projection/recall.d.ts +51 -13
- package/dist/projection/recall.js +47 -12
- package/dist/prompts/examples.d.ts +73 -0
- package/dist/prompts/examples.js +178 -0
- package/dist/prompts/l1-dedup.d.ts +9 -3
- package/dist/prompts/l1-dedup.js +34 -13
- package/dist/prompts/l1-extraction.d.ts +5 -3
- package/dist/prompts/l1-extraction.js +49 -4
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +16 -1
- package/dist/stats.d.ts +12 -0
- package/dist/stats.js +352 -17
- package/dist/store/embedding-source.d.ts +8 -0
- package/dist/store/embedding-source.js +39 -2
- package/dist/store/file-versions.d.ts +2 -0
- package/dist/store/file-versions.js +2 -0
- package/dist/store/graph-store.d.ts +14 -0
- package/dist/store/graph-store.js +35 -0
- package/dist/store/l1-snapshot.d.ts +26 -1
- package/dist/store/l1-snapshot.js +53 -8
- package/dist/store/l1.d.ts +94 -3
- package/dist/store/l1.js +169 -4
- package/dist/store/memory-md-archive.d.ts +84 -0
- package/dist/store/memory-md-archive.js +155 -0
- package/dist/store/memory-md-disable.d.ts +74 -0
- package/dist/store/memory-md-disable.js +302 -0
- package/dist/store/memory-md-export.d.ts +50 -0
- package/dist/store/memory-md-export.js +227 -0
- package/dist/store/memory-md-ledger.d.ts +94 -0
- package/dist/store/memory-md-ledger.js +243 -0
- package/dist/store/memory-md-orphan.d.ts +27 -0
- package/dist/store/memory-md-orphan.js +153 -0
- package/dist/store/memory-md-parse.d.ts +35 -0
- package/dist/store/memory-md-parse.js +85 -0
- package/dist/store/memory-md-run.d.ts +48 -0
- package/dist/store/memory-md-run.js +151 -0
- package/dist/store/memory-md-skeleton.d.ts +18 -0
- package/dist/store/memory-md-skeleton.js +61 -0
- package/dist/store/memory-md-sync.d.ts +59 -0
- package/dist/store/memory-md-sync.js +152 -0
- package/dist/store/memory-md-types.d.ts +243 -0
- package/dist/store/memory-md-types.js +10 -0
- package/dist/store/memory-md.d.ts +117 -0
- package/dist/store/memory-md.js +230 -0
- package/dist/store/obsolete-notices.d.ts +39 -0
- package/dist/store/obsolete-notices.js +153 -0
- package/dist/store/recall-feedback.d.ts +75 -0
- package/dist/store/recall-feedback.js +213 -0
- package/dist/store/recall-usage.d.ts +11 -0
- package/dist/store/recall-usage.js +29 -0
- package/dist/store/session-recalled.d.ts +38 -3
- package/dist/store/session-recalled.js +156 -9
- package/dist/store/sqlite.d.ts +66 -0
- package/dist/store/sqlite.js +233 -15
- package/dist/store/supersede.d.ts +1 -1
- package/dist/store/supersede.js +1 -1
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +26 -7
- package/dist/tools/slots.d.ts +2 -1
- package/dist/tools/slots.js +2 -1
- package/dist/types.d.ts +24 -7
- package/dist/types.js +10 -2
- package/dist/util/maturity.d.ts +20 -3
- package/dist/util/maturity.js +18 -1
- package/dist/util/md-name.d.ts +129 -0
- package/dist/util/md-name.js +366 -0
- package/dist/util/md-normalize.d.ts +23 -0
- package/dist/util/md-normalize.js +131 -0
- package/dist/workspace-route.d.ts +22 -0
- package/dist/workspace-route.js +66 -0
- package/dsh.plugin.json +1 -1
- package/package.json +6 -5
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { resolveDataDir } from '../config.js';
|
|
1
2
|
import { InProcMemoryBackend } from '../store/memory-backend.js';
|
|
3
|
+
import { syncFiles } from '../store/memory-md-sync.js';
|
|
2
4
|
import { groupPendingBySession, loadPending, } from '../store/pending.js';
|
|
3
5
|
import { errDetail } from '../util/filelog.js';
|
|
4
6
|
import { runSceneConsolidation } from './l2.js';
|
|
@@ -49,6 +51,13 @@ export class RuminateController {
|
|
|
49
51
|
pendingFile;
|
|
50
52
|
/** Room 注册表(可选;孤儿候选预标记的合并词表来源)。 */
|
|
51
53
|
roomRegistry;
|
|
54
|
+
/**
|
|
55
|
+
* 本轮起始时的库快照(id → updatedAt),供收尾算「本轮新增/变更」增量集(task_2.8)。
|
|
56
|
+
* `undefined` = 本轮没拍(投射门未开或拍失败)→ 收尾一律不做全库重导。
|
|
57
|
+
*/
|
|
58
|
+
mdBaseline;
|
|
59
|
+
/** 投射旁路失败累计次数(只 warn 不抛,计数供诊断/测试读取)。 */
|
|
60
|
+
mdFailures = 0;
|
|
52
61
|
/** 记忆后端:未注入时包 l1(进程内,行为与改造前等价)。 */
|
|
53
62
|
backend() {
|
|
54
63
|
return this.stores.backend ?? new InProcMemoryBackend(this.stores.l1);
|
|
@@ -90,6 +99,9 @@ export class RuminateController {
|
|
|
90
99
|
this.sessions = groupSessions(buckets);
|
|
91
100
|
this.cancelRequested = false;
|
|
92
101
|
this.totalL1 = 0;
|
|
102
|
+
// md 投射基线(task_2.8):两条收尾路径(finalize / doLightRefresh)共用同一份起点,
|
|
103
|
+
// 所以必须在这里拍——放在 `sessions.length === 0` 分支之前,轻量刷新才覆盖得到。
|
|
104
|
+
this.snapshotMdBaseline();
|
|
93
105
|
if (this.sessions.length === 0) {
|
|
94
106
|
// 没有 pending 切片 → 做 L2/L3 轻量刷新
|
|
95
107
|
return await this.doLightRefresh();
|
|
@@ -275,6 +287,8 @@ export class RuminateController {
|
|
|
275
287
|
catch (err) {
|
|
276
288
|
this.logger.warn(`[memory] 孤儿候选预标记失败(忽略): ${errDetail(err)}`);
|
|
277
289
|
}
|
|
290
|
+
// md 投射收尾(task_2.8):只导本轮新增/变更的记录;旁路,失败不影响本次产物。
|
|
291
|
+
await this.projectMd('反刍收尾');
|
|
278
292
|
this.status.phase = 'done';
|
|
279
293
|
this.finish(null);
|
|
280
294
|
}
|
|
@@ -410,6 +424,9 @@ export class RuminateController {
|
|
|
410
424
|
if (this.stores.slots !== undefined) {
|
|
411
425
|
await migrateLegacySlots(this.stores.slots, this.stores.l1, this.cfg.slots.migrateLegacy === true, this.logger);
|
|
412
426
|
}
|
|
427
|
+
// md 投射收尾(task_2.8):与 finalize 同一动作的两个挂载点之一——历史教训是
|
|
428
|
+
// `sessions.length === 0` 走本路径,只挂 finalize 等于没挂。
|
|
429
|
+
await this.projectMd('轻量刷新');
|
|
413
430
|
this.status.running = false;
|
|
414
431
|
this.status.detail = null;
|
|
415
432
|
this.status.phase = this.cancelRequested ? 'cancelled' : 'done';
|
|
@@ -418,6 +435,98 @@ export class RuminateController {
|
|
|
418
435
|
this.logger.info(`[memory] 反刍轻量刷新结束(${this.status.phase}):${this.status.done}/${this.status.total} 步,耗时 ${Math.round(cost / 1000)}s`);
|
|
419
436
|
return this.getStatus();
|
|
420
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* 投射侧诊断计数(只增不减;不进任何端点,避开契约五处齐改的面)。
|
|
440
|
+
*/
|
|
441
|
+
getMdFailures() {
|
|
442
|
+
return this.mdFailures;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* 拍本轮起始基线(id → updatedAt)。三道门任一不满足就**一个动作都不做**:
|
|
446
|
+
* 连 `l1.all()` 都不调,这样“投射关着”时的产物与改动前逐字一致(本仓红线)。
|
|
447
|
+
*
|
|
448
|
+
* 为什么用基线 diff 而不是 `updatedAt >= startedAt`:relabel / 退场等改写走的是
|
|
449
|
+
* `patchMetadata` 那类最小写回,**不碰 `updated_time`**(它在快照哈希字段表里),
|
|
450
|
+
* 按时间戳筛会把这批真变了的记录漏掉。
|
|
451
|
+
*/
|
|
452
|
+
snapshotMdBaseline() {
|
|
453
|
+
this.mdBaseline = undefined;
|
|
454
|
+
const exp = this.cfg.memExport;
|
|
455
|
+
if (exp === undefined || exp.enabled !== true)
|
|
456
|
+
return;
|
|
457
|
+
if (this.stores.mdLedger === undefined)
|
|
458
|
+
return;
|
|
459
|
+
try {
|
|
460
|
+
const map = new Map();
|
|
461
|
+
for (const r of this.stores.l1.all())
|
|
462
|
+
map.set(r.id, r.updatedAt);
|
|
463
|
+
this.mdBaseline = map;
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
// 拍不上基线 = 本轮不投射(而不是退回全库重导);旁路失败不拖垮反刍。
|
|
467
|
+
this.mdFailures++;
|
|
468
|
+
this.logger.warn(`[memory] 反刍 md 投射基线失败(本次不做投射): ${errDetail(err)}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* 反刍收尾的增量 md 投射(task_2.8):库 → 文件 + 账本 + 后缀对齐,**只碰本轮新增/变更的那批**。
|
|
473
|
+
*
|
|
474
|
+
* 与手动的 `dsh-memory/mem-export` 端点的区别:
|
|
475
|
+
* - 不跑命名回填(`assignMissingNames` 会写库,反刍是只读旁路,不能在那里改库);
|
|
476
|
+
* 本轮新记录的 `name` 已在写入前由管线单一真源分配,未命名的按 `fileStemFor` 回落。
|
|
477
|
+
* - 不建类型目录骨架(`ensureTypeSkeleton`),那是面板显式触发的动作。
|
|
478
|
+
* 护栏全部复用 `cfg.memExport` 既有值(`maxRecords`/`timeBudgetMs`),**不新增配置项**。
|
|
479
|
+
*
|
|
480
|
+
* 一轮投射走 `syncFiles`(与 `L1Store` 钩子同一函数):导出之后紧跟后缀收敛,
|
|
481
|
+
* 所以本轮被合并取代/冲突判负的增量记录不会停在未改名的 `.memory.md` 上。
|
|
482
|
+
*
|
|
483
|
+
* 失败面:整个方法自吞异常,只 warn + 计数,绝不抛给调用方、绝不改变反刍结果。
|
|
484
|
+
* @param stage - 日志里的路径名(区分 finalize 与轻量刷新两条挂载点)。
|
|
485
|
+
*/
|
|
486
|
+
async projectMd(stage) {
|
|
487
|
+
const baseline = this.mdBaseline;
|
|
488
|
+
if (baseline === undefined)
|
|
489
|
+
return;
|
|
490
|
+
const exp = this.cfg.memExport;
|
|
491
|
+
const ledger = this.stores.mdLedger;
|
|
492
|
+
if (exp === undefined || exp.enabled !== true || ledger === undefined)
|
|
493
|
+
return;
|
|
494
|
+
try {
|
|
495
|
+
const changed = [];
|
|
496
|
+
for (const r of this.stores.l1.all()) {
|
|
497
|
+
const prev = baseline.get(r.id);
|
|
498
|
+
// 新 id(本轮蒸馏产出)或 updatedAt 变了(合并/改写)才算变更;其余不重导。
|
|
499
|
+
if (prev === undefined || prev !== r.updatedAt)
|
|
500
|
+
changed.push(r);
|
|
501
|
+
}
|
|
502
|
+
if (changed.length === 0)
|
|
503
|
+
return;
|
|
504
|
+
const deps = {
|
|
505
|
+
exportCfg: () => ({
|
|
506
|
+
enabled: exp.enabled === true,
|
|
507
|
+
root: exp.root,
|
|
508
|
+
maxRecords: exp.maxRecords,
|
|
509
|
+
timeBudgetMs: exp.timeBudgetMs,
|
|
510
|
+
}),
|
|
511
|
+
dataDir: resolveDataDir(this.cfg),
|
|
512
|
+
ledger: () => this.stores.mdLedger,
|
|
513
|
+
logger: this.logger,
|
|
514
|
+
};
|
|
515
|
+
const out = await syncFiles(deps, changed);
|
|
516
|
+
if (out === undefined)
|
|
517
|
+
return;
|
|
518
|
+
const { exported: rep, suffix } = out;
|
|
519
|
+
this.logger.info(`[memory] 反刍 md 投射(${stage}):增量 ${changed.length} 条,写 ${rep.written},未变 ${rep.unchanged},跳过 ${rep.skipped},改名 ${suffix.renamed},落点 ${rep.root}${rep.capped ? `,预算用尽剩 ${rep.remaining} 个文件(下轮反刍继续)` : ''}`);
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
this.mdFailures++;
|
|
523
|
+
this.logger.warn(`[memory] 反刍 md 投射失败(旁路忽略,不影响本次产物,累计 ${this.mdFailures} 次): ${errDetail(err)}`);
|
|
524
|
+
}
|
|
525
|
+
finally {
|
|
526
|
+
// 基线一次性消费:同一次运行不重投射同一批(两条挂载点互斥,这里只防异常复用)。
|
|
527
|
+
this.mdBaseline = undefined;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
421
530
|
finish(error) {
|
|
422
531
|
this.status.running = false;
|
|
423
532
|
this.status.detail = null;
|
|
@@ -30,6 +30,16 @@ export interface MemoryStores {
|
|
|
30
30
|
state: StateStore;
|
|
31
31
|
/** 图谱存储(可选:测试缝/未装配时图谱泵整体停用;GraphStore 自带降级 no-op)。 */
|
|
32
32
|
graph?: GraphStore;
|
|
33
|
+
/**
|
|
34
|
+
* 派生数据搬运面(P0-d):merge 后把使用史与人工标注搬到新 id。
|
|
35
|
+
* 只声明用到的那个方法(不拉整个类入参)——未装配时不搬,行为与改动前一致。
|
|
36
|
+
*/
|
|
37
|
+
usage?: {
|
|
38
|
+
rekey(oldId: string, newId: string): boolean;
|
|
39
|
+
};
|
|
40
|
+
feedback?: {
|
|
41
|
+
rekey(oldId: string, newId: string): boolean;
|
|
42
|
+
};
|
|
33
43
|
}
|
|
34
44
|
/** 管线任务(优先级调度:live > graph > rebuild)。 */
|
|
35
45
|
export interface PipelineTask {
|
package/dist/pipeline/runner.js
CHANGED
|
@@ -121,12 +121,17 @@ export function effectiveCfg(cfg, live) {
|
|
|
121
121
|
const cfOverride = s?.conflictFreeze !== undefined
|
|
122
122
|
? { conflictFreeze: { ...(cfg.conflictFreeze ?? {}), enabled: s.conflictFreeze } }
|
|
123
123
|
: null;
|
|
124
|
-
|
|
124
|
+
// md 投射/后缀收敛的单次动作护栏运行时覆盖(0 = 跟随部署 cfg.memExport.maxRecords)。
|
|
125
|
+
// 只覆盖条数护栏,不动 enabled/root——开关与根路径仍属部署配置。
|
|
126
|
+
const mdx = s?.mdExportMaxRecords ?? 0;
|
|
127
|
+
const memExportOverride = mdx > 0 ? { memExport: { ...(cfg.memExport ?? {}), maxRecords: mdx } } : null;
|
|
128
|
+
if (!override && !budgets && !maxInput && !fallbacksTakeover && !chainEffort && !chainFallbacks && !effortInject && !layerChains && !hasChannel && !embedOverride && !cfOverride && !memExportOverride)
|
|
125
129
|
return cfg;
|
|
126
130
|
return {
|
|
127
131
|
...cfg,
|
|
128
132
|
...(embedOverride ?? {}),
|
|
129
133
|
...(cfOverride ?? {}),
|
|
134
|
+
...(memExportOverride ?? {}),
|
|
130
135
|
llm: {
|
|
131
136
|
...cfg.llm,
|
|
132
137
|
...(effortInject ?? {}),
|
|
@@ -662,7 +667,13 @@ export class MemoryRunner {
|
|
|
662
667
|
// §E 写入侧工作区:后台蒸馏手上只有 sessionId(没有 exec),经
|
|
663
668
|
// `ctx.get('agents')` 宽容解析——与 §A 的多级父链解析同一招。
|
|
664
669
|
// 拿不到 → undefined → 归属回落 global(pipeline 侧 `resolveRecordScope` 兜底)。
|
|
665
|
-
sessionWorkspaceIdOf(this.ctx, sessionId), anchorMap
|
|
670
|
+
sessionWorkspaceIdOf(this.ctx, sessionId), anchorMap,
|
|
671
|
+
// 派生数据跟着搬(P0-d):不搬等于每次合并把这条记忆的使用史与人工标注清零。
|
|
672
|
+
// 两个 sidecar 都是后装上的(`stores.usage` 在装配段才填),故在调用时点现取。
|
|
673
|
+
(oldId, newId) => {
|
|
674
|
+
this.stores.usage?.rekey(oldId, newId);
|
|
675
|
+
this.stores.feedback?.rekey(oldId, newId);
|
|
676
|
+
});
|
|
666
677
|
if (!result.skipped) {
|
|
667
678
|
this.pending[mode] = rest;
|
|
668
679
|
// 重建轮(force)不是有机对话,不推进爬坡
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 存量记忆的 workspace re-link(内容路由回填)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:新写入的路由只覆盖此后产生的记忆,而库里已有的记录归属列全空
|
|
5
|
+
* (实测 1279 条活跃记录里 `workspace_id` 非空为 0)。不回填,「按项目看记忆」这件事
|
|
6
|
+
* 就永远只有增量、没有存量。
|
|
7
|
+
*
|
|
8
|
+
* 三条设计约束:
|
|
9
|
+
* 1. **只写 `workspace_id`,不动 `scope`、不动 `updated_time`**。前者保可见性零变化
|
|
10
|
+
* (`cfg.scope='global'` 下 `isScopeVisible` 直接放行),后者保快照哈希零失配
|
|
11
|
+
* (见 `store/sqlite.ts` 的 `setL1WorkspaceIds` 注释,三条硬约束都在那儿)。
|
|
12
|
+
* 2. **宁缺勿猜**:路由不命中的记录保持原样,绝不给一个"默认归属"。归错项目会让记忆
|
|
13
|
+
* 在错误的工作区可见/不可见,比不归难查得多。
|
|
14
|
+
* 3. **有界可重入**:照 `wing-backfill` 的双护栏 + 扫描阶段也查墙钟 + 剩余留待再次触发;
|
|
15
|
+
* 且因不写 `updated_time`,按更新时间倒序分页**不会边扫边漂**。
|
|
16
|
+
*/
|
|
17
|
+
import type { MemoryRecord } from '../types.js';
|
|
18
|
+
import type { WorkspaceRoute } from '../workspace-route.js';
|
|
19
|
+
/** re-link 需要的存储面(结构化声明,便于单测注入)。 */
|
|
20
|
+
export interface RelinkStoreFace {
|
|
21
|
+
list(opts: {
|
|
22
|
+
limit: number;
|
|
23
|
+
offset: number;
|
|
24
|
+
}): {
|
|
25
|
+
items: MemoryRecord[];
|
|
26
|
+
total: number;
|
|
27
|
+
};
|
|
28
|
+
setWorkspaceIds(items: ReadonlyArray<{
|
|
29
|
+
id: string;
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
}>): number;
|
|
32
|
+
}
|
|
33
|
+
export interface RelinkReport {
|
|
34
|
+
dryRun: boolean;
|
|
35
|
+
/** 扫过的记录数(含未命中)。 */
|
|
36
|
+
scanned: number;
|
|
37
|
+
/** 路由命中的记录数。 */
|
|
38
|
+
matched: number;
|
|
39
|
+
/** 归属已是目标值、无需写的记录数(幂等重跑时等于 matched)。 */
|
|
40
|
+
unchanged: number;
|
|
41
|
+
/** 干跑时“若真跑会写多少条”;真写时恒为 0(那一步看 written)。 */
|
|
42
|
+
toWrite: number;
|
|
43
|
+
/** 实际写入条数(dryRun 时恒为 0)。 */
|
|
44
|
+
written: number;
|
|
45
|
+
/** 因预算未处理、留待下次触发的条数。 */
|
|
46
|
+
remaining: number;
|
|
47
|
+
/** 命中记录按归属的分布(先看这个数就知道路由表合不合口味)。 */
|
|
48
|
+
byWorkspace: Record<string, number>;
|
|
49
|
+
}
|
|
50
|
+
export declare function relinkWorkspaces(deps: {
|
|
51
|
+
l1: RelinkStoreFace;
|
|
52
|
+
routes: readonly WorkspaceRoute[];
|
|
53
|
+
dryRun?: boolean;
|
|
54
|
+
maxRecords?: number;
|
|
55
|
+
timeBudgetMs?: number;
|
|
56
|
+
logger?: {
|
|
57
|
+
info: (m: string) => void;
|
|
58
|
+
warn: (m: string) => void;
|
|
59
|
+
};
|
|
60
|
+
}): Promise<RelinkReport>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { routeWorkspace } from '../workspace-route.js';
|
|
2
|
+
import { yieldLoop } from '../util/yield.js';
|
|
3
|
+
const PAGE = 200;
|
|
4
|
+
/** 攒够一批再写:一批一个事务,避免逐条开事务的 fsync 开销。 */
|
|
5
|
+
const FLUSH = 500;
|
|
6
|
+
export async function relinkWorkspaces(deps) {
|
|
7
|
+
const { l1, routes, logger } = deps;
|
|
8
|
+
const dryRun = deps.dryRun !== false; // 缺省干跑:先看报告,再决定真写
|
|
9
|
+
const maxRecords = Math.max(1, Math.floor(deps.maxRecords ?? 300));
|
|
10
|
+
const budgetMs = Math.max(1000, Math.floor(deps.timeBudgetMs ?? 120_000));
|
|
11
|
+
const started = Date.now();
|
|
12
|
+
const overBudget = () => Date.now() - started > budgetMs;
|
|
13
|
+
const report = {
|
|
14
|
+
dryRun, scanned: 0, matched: 0, unchanged: 0, toWrite: 0, written: 0, remaining: 0, byWorkspace: {},
|
|
15
|
+
};
|
|
16
|
+
if (routes.length === 0)
|
|
17
|
+
return report;
|
|
18
|
+
let offset = 0;
|
|
19
|
+
let batch = [];
|
|
20
|
+
// 计数在此结算:`flush()` 会清空 batch,调用点再拿 `batch.length` 必然归零。
|
|
21
|
+
const flush = () => {
|
|
22
|
+
if (batch.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
if (dryRun)
|
|
25
|
+
report.toWrite += batch.length;
|
|
26
|
+
else
|
|
27
|
+
report.written += l1.setWorkspaceIds(batch);
|
|
28
|
+
batch = [];
|
|
29
|
+
};
|
|
30
|
+
for (;;) {
|
|
31
|
+
if (report.scanned >= maxRecords)
|
|
32
|
+
break;
|
|
33
|
+
// 扫描阶段本身也查墙钟:1279 条不慢,但同一个护栏要能扛住更大的库。
|
|
34
|
+
if (overBudget())
|
|
35
|
+
break;
|
|
36
|
+
const page = l1.list({ limit: PAGE, offset });
|
|
37
|
+
if (page.items.length === 0)
|
|
38
|
+
break;
|
|
39
|
+
for (const rec of page.items) {
|
|
40
|
+
// 上限检查必须在**循环顶部**:放在末尾时,未命中的记录会先 `continue` 掉,
|
|
41
|
+
// 于是 maxRecords=1 实际能扫 2 条(实测翻出)——有界性得对所有分支都成立。
|
|
42
|
+
if (report.scanned >= maxRecords)
|
|
43
|
+
break;
|
|
44
|
+
report.scanned++;
|
|
45
|
+
const routed = routeWorkspace(`${rec.content}\n${rec.scene_name}`, routes);
|
|
46
|
+
if (routed === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
report.matched++;
|
|
49
|
+
report.byWorkspace[routed] = (report.byWorkspace[routed] ?? 0) + 1;
|
|
50
|
+
if (rec.workspaceId === routed) {
|
|
51
|
+
report.unchanged++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
batch.push({ id: rec.id, workspaceId: routed });
|
|
55
|
+
if (batch.length >= FLUSH)
|
|
56
|
+
flush();
|
|
57
|
+
}
|
|
58
|
+
offset += page.items.length;
|
|
59
|
+
if (offset >= page.total)
|
|
60
|
+
break;
|
|
61
|
+
await yieldLoop(); // 让出事件循环:面板 RPC / 状态查询优先
|
|
62
|
+
}
|
|
63
|
+
flush();
|
|
64
|
+
// 剩余 = 总数 - 实扫条数。**不能用 offset 算**:中途按 maxRecords/预算 break 时,
|
|
65
|
+
// offset 已推进整个页(一页 PAGE 条),会把“还剩 2 条没扫”算成 0,
|
|
66
|
+
// 于是报告谎称已扫完——下次也就不会再触发续扫了。
|
|
67
|
+
const totalRows = l1.list({ limit: 1, offset: 0 }).total;
|
|
68
|
+
report.remaining = Math.max(0, totalRows - report.scanned);
|
|
69
|
+
logger?.info(`[memory] workspace re-link${dryRun ? '(干跑)' : ''}:扫 ${report.scanned} 条,命中 ${report.matched} 条,`
|
|
70
|
+
+ `待写 ${report.toWrite} 条,实写 ${report.written} 条,`
|
|
71
|
+
+ `剩 ${report.remaining} 条未扫(下次触发续);分布 ${JSON.stringify(report.byWorkspace)}`);
|
|
72
|
+
return report;
|
|
73
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 会话「活召回集」服务端投影(memoryRecall)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 语义:本会话**已注入**的记忆,以及其中「此刻仍在上下文里」的那部分。
|
|
5
|
+
* 数据源是 `SessionRecalledStore`(sessionKey 键、写穿持久化),所以**旧会话重开也读得到**。
|
|
6
|
+
*
|
|
7
|
+
* 压缩/清空对这张表的作用不同(2026-10-13 用户口径):
|
|
8
|
+
* - `clear`(主动清空)整表清,view 随之空;
|
|
9
|
+
* - `compact`(摘要替换)**不删条目**,只把当时在列表里的那批标成 `superseded`(正文已被
|
|
10
|
+
* 摘要取代、已不在上下文里)。读侧据此把旧召回与当前召回区分开,而不是继续谎报“还在”。
|
|
11
|
+
* 已退场(`retired`)的条目必须留在列表里(人的裁决凭证),取代标记与它正交。
|
|
7
12
|
*
|
|
8
13
|
* 与 memorySlots 的分工(两条轴,互不替代):
|
|
9
14
|
* - `memorySlots` = 用户手动 pin 的**常驻约定**(每轮硬注入,注册表语义);
|
|
@@ -19,6 +24,7 @@
|
|
|
19
24
|
*/
|
|
20
25
|
import type { Context } from '@deepseek-ai/cordis';
|
|
21
26
|
import type { SessionRecalledStore } from '../store/session-recalled.js';
|
|
27
|
+
import type { MemoryRecord } from '../types.js';
|
|
22
28
|
export declare const MEMORY_RECALL_KEY = "memoryRecall";
|
|
23
29
|
/** view 最多透出条数(最近在上;超出部分只计入 count,防 wire 体量失控)。 */
|
|
24
30
|
export declare const RECALL_VIEW_CAP = 20;
|
|
@@ -31,13 +37,44 @@ export interface RecallItemView {
|
|
|
31
37
|
priority: number;
|
|
32
38
|
/** 使用场景(metadata.dsh_usage_scenario);无则缺席(附加字段,旧帧没有也整条通过)。 */
|
|
33
39
|
scenario?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* 人给的最近一次打分(-1..5);未标注则缺席。
|
|
42
|
+
* 卡片靠它把已打的那一笔显示成选中态;均值等细粒度数据走 `session-recalled` 端点。
|
|
43
|
+
*/
|
|
44
|
+
rating?: number;
|
|
45
|
+
/**
|
|
46
|
+
* 正文全文(截断至 BODY_MAX);消费方悬停泡泡直接读它,不再回查端点。
|
|
47
|
+
* 缺席 = 旧帧(读侧退化成只显示 title)。
|
|
48
|
+
*/
|
|
49
|
+
body?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 该记录已被判定过时(`isRetired`)。只写 true:未退场时字段缺席,
|
|
52
|
+
* 读侧按「缺席即活跃」处理,旧帧自然兼容。
|
|
53
|
+
*/
|
|
54
|
+
retired?: true;
|
|
55
|
+
/**
|
|
56
|
+
* 该条目的正文**已被摘要取代**(压缩发生时时它就在列表里),即不在模型上下文里了。
|
|
57
|
+
* 只写 true(同 `retired` 的兼容手法);与 `retired` 正交,两者可共存。
|
|
58
|
+
* 读侧据此把「已被摘要取代的旧召回」与「此刻仍在上下文里的召回」区分开。
|
|
59
|
+
*/
|
|
60
|
+
superseded?: true;
|
|
34
61
|
}
|
|
35
62
|
/** 持久态:本会话的活召回 id 集 + 判脏序号。 */
|
|
36
63
|
export interface RecallProjectionState {
|
|
37
64
|
rev: number;
|
|
65
|
+
/**
|
|
66
|
+
* 反馈 sidecar 的序号(打分也会让 view 变:条目要显示已打的那一笔)。
|
|
67
|
+
* 可选:旧 checkpoint 帧里没这个字段,读侧必须容忍。
|
|
68
|
+
*/
|
|
69
|
+
fb?: number;
|
|
38
70
|
sessionId: string;
|
|
39
71
|
/** 注入序(旧→新),由 store 保证。 */
|
|
40
72
|
ids: string[];
|
|
73
|
+
/**
|
|
74
|
+
* 已被摘要取代的 id(压缩水位之上的那批)。可选:旧 checkpoint 没有这个字段,
|
|
75
|
+
* 读侧必须容忍(缺席 = 无取代,与改动前逐字一致)。
|
|
76
|
+
*/
|
|
77
|
+
superseded?: string[];
|
|
41
78
|
}
|
|
42
79
|
/** wire view:count 是总数,items 是**最近在上**的截断视图。 */
|
|
43
80
|
export interface RecallView {
|
|
@@ -54,21 +91,22 @@ interface SchemaLike<T> {
|
|
|
54
91
|
export declare const stateSchema: SchemaLike<RecallProjectionState>;
|
|
55
92
|
/** view 校验(出网前调用;未知键容忍,同 slots 投影的语义边界)。 */
|
|
56
93
|
export declare const viewSchema: SchemaLike<RecallView>;
|
|
57
|
-
/** 投影需要的 L1 只读面(结构化声明,避免跨模块类型耦合)。
|
|
94
|
+
/** 投影需要的 L1 只读面(结构化声明,避免跨模块类型耦合)。
|
|
95
|
+
* 判退场要 `validTo`/`metadata`,故直接 Pick 自 MemoryRecord,不在投影里自写时间比较。 */
|
|
58
96
|
export interface RecallL1Face {
|
|
59
|
-
getByIds(ids: string[]): Array<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}>;
|
|
97
|
+
getByIds(ids: string[]): Array<Pick<MemoryRecord, 'id' | 'type' | 'content' | 'priority' | 'metadata' | 'validTo'>>;
|
|
98
|
+
}
|
|
99
|
+
/** 投影需要的反馈只读面(缺席 = view 不带 rating,旧行为)。 */
|
|
100
|
+
export interface RecallFeedbackFace {
|
|
101
|
+
revision(): number;
|
|
102
|
+
lastOf(id: string): number | undefined;
|
|
66
103
|
}
|
|
67
104
|
/**
|
|
68
105
|
* 注册 memoryRecall 投影。
|
|
69
106
|
*
|
|
70
107
|
* 缺席降级同 slots 投影:`ctx.inject` 在服务未提供时不回调;形状不符静默不注册。
|
|
71
|
-
* apply 是 O(1) 判脏(store.revision()
|
|
108
|
+
* apply 是 O(1) 判脏(store.revision() 与反馈序号比对),任意已提交事件都会带进来看一眼。
|
|
109
|
+
* @param feedback - 相关度反馈只读面;缺席时 view 不带 rating(旧行为)。
|
|
72
110
|
*/
|
|
73
|
-
export declare function registerRecallProjection(ctx: Context, store: SessionRecalledStore, l1?: RecallL1Face): void;
|
|
111
|
+
export declare function registerRecallProjection(ctx: Context, store: SessionRecalledStore, l1?: RecallL1Face, feedback?: RecallFeedbackFace): void;
|
|
74
112
|
export {};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { isRetired } from '../store/supersede.js';
|
|
1
2
|
export const MEMORY_RECALL_KEY = 'memoryRecall';
|
|
2
3
|
/** view 最多透出条数(最近在上;超出部分只计入 count,防 wire 体量失控)。 */
|
|
3
4
|
export const RECALL_VIEW_CAP = 20;
|
|
4
5
|
/** 单条标题(正文首行)最大长度。 */
|
|
5
6
|
const TITLE_MAX = 160;
|
|
7
|
+
/** 单条全文最大长度(泡泡里显示的正文;超出截断并补省略号,防 wire 体量失控)。 */
|
|
8
|
+
const BODY_MAX = 700;
|
|
6
9
|
function invalid(what, detail) {
|
|
7
10
|
throw new Error(`${what}: ${detail}`);
|
|
8
11
|
}
|
|
@@ -31,6 +34,11 @@ export const stateSchema = {
|
|
|
31
34
|
if (!Array.isArray(root.ids))
|
|
32
35
|
invalid(what, 'ids 必须是字符串数组');
|
|
33
36
|
root.ids.forEach((id, i) => requireString(id, `${what}.ids[${i}]`, 'id'));
|
|
37
|
+
if (root.superseded !== undefined) {
|
|
38
|
+
if (!Array.isArray(root.superseded))
|
|
39
|
+
invalid(what, 'superseded 必须是字符串数组');
|
|
40
|
+
root.superseded.forEach((id, i) => requireString(id, `${what}.superseded[${i}]`, 'id'));
|
|
41
|
+
}
|
|
34
42
|
return value;
|
|
35
43
|
},
|
|
36
44
|
};
|
|
@@ -56,6 +64,15 @@ export const viewSchema = {
|
|
|
56
64
|
invalid(at, 'scenario 必须是字符串数组');
|
|
57
65
|
item.scenario.forEach((s, j) => requireString(s, `${at}.scenario[${j}]`, 'scenario'));
|
|
58
66
|
}
|
|
67
|
+
if (item.rating !== undefined && typeof item.rating !== 'number')
|
|
68
|
+
invalid(at, 'rating 必须是数字');
|
|
69
|
+
// 附加字段:旧帧没有 → 容忍;有 → 必须成形状。retired 只认 true,false 视为脏帧。
|
|
70
|
+
if (item.body !== undefined)
|
|
71
|
+
requireString(item.body, at, 'body');
|
|
72
|
+
if (item.retired !== undefined && item.retired !== true)
|
|
73
|
+
invalid(at, 'retired 只能是 true 或缺席');
|
|
74
|
+
if (item.superseded !== undefined && item.superseded !== true)
|
|
75
|
+
invalid(at, 'superseded 只能是 true 或缺席');
|
|
59
76
|
});
|
|
60
77
|
return value;
|
|
61
78
|
},
|
|
@@ -66,14 +83,22 @@ function firstLine(content) {
|
|
|
66
83
|
const trimmed = line.trim();
|
|
67
84
|
return trimmed.length > TITLE_MAX ? trimmed.slice(0, TITLE_MAX) : trimmed;
|
|
68
85
|
}
|
|
86
|
+
/** 正文全文 → 泡泡用(去首尾空白,超长截断补省略号)。 */
|
|
87
|
+
function clampBody(content) {
|
|
88
|
+
const s = content.trim();
|
|
89
|
+
return s.length > BODY_MAX ? `${s.slice(0, BODY_MAX)}…` : s;
|
|
90
|
+
}
|
|
69
91
|
/** 读 store 建态;ids 与旧态一致时返回旧引用(避免无谓 republish)。 */
|
|
70
|
-
function buildState(sessionId, store, prev) {
|
|
92
|
+
function buildState(sessionId, store, feedbackRev, prev) {
|
|
71
93
|
const ids = store.list(sessionId);
|
|
72
|
-
|
|
94
|
+
const superseded = [...store.supersededOf(sessionId)];
|
|
95
|
+
const sameCounters = prev !== undefined && prev.rev === store.revision() && (prev.fb ?? 0) === feedbackRev;
|
|
96
|
+
if (sameCounters && sameIds(prev.ids, ids) && sameIds(prev.superseded ?? [], superseded))
|
|
73
97
|
return prev;
|
|
74
|
-
const next = { rev: store.revision(), sessionId, ids };
|
|
75
|
-
// rev
|
|
76
|
-
if (prev !== undefined && prev.sessionId === sessionId && sameIds(prev.ids, ids)
|
|
98
|
+
const next = { rev: store.revision(), fb: feedbackRev, sessionId, ids, superseded };
|
|
99
|
+
// rev 变了但本会话集合、取代集与反馈都没变(别的会话写了)→ 仍复用旧引用,防跨会话 thrash。
|
|
100
|
+
if (prev !== undefined && prev.sessionId === sessionId && sameIds(prev.ids, ids)
|
|
101
|
+
&& sameIds(prev.superseded ?? [], superseded) && (prev.fb ?? 0) === feedbackRev)
|
|
77
102
|
return prev;
|
|
78
103
|
return next;
|
|
79
104
|
}
|
|
@@ -87,13 +112,14 @@ function sameIds(a, b) {
|
|
|
87
112
|
}
|
|
88
113
|
/** view 引用稳定:同一 state 恒返回同一对象(registry 按 Object.is 判发布)。 */
|
|
89
114
|
const viewMemo = new WeakMap();
|
|
90
|
-
function makeView(state, l1) {
|
|
115
|
+
function makeView(state, l1, feedback) {
|
|
91
116
|
const memoized = viewMemo.get(state);
|
|
92
117
|
if (memoized !== undefined)
|
|
93
118
|
return memoized;
|
|
94
119
|
const recentFirst = [...state.ids].reverse();
|
|
95
120
|
const picked = recentFirst.slice(0, RECALL_VIEW_CAP);
|
|
96
121
|
const byId = new Map((l1?.getByIds(picked) ?? []).map((r) => [r.id, r]));
|
|
122
|
+
const superseded = new Set(state.superseded ?? []);
|
|
97
123
|
const items = [];
|
|
98
124
|
for (const id of picked) {
|
|
99
125
|
const r = byId.get(id);
|
|
@@ -102,12 +128,19 @@ function makeView(state, l1) {
|
|
|
102
128
|
const scenario = Array.isArray(r.metadata?.dsh_usage_scenario)
|
|
103
129
|
? r.metadata?.dsh_usage_scenario
|
|
104
130
|
: undefined;
|
|
131
|
+
const rating = feedback?.lastOf(id);
|
|
105
132
|
items.push({
|
|
106
133
|
id: r.id,
|
|
107
134
|
type: r.type,
|
|
108
135
|
title: firstLine(r.content),
|
|
109
136
|
priority: r.priority,
|
|
137
|
+
body: clampBody(r.content),
|
|
110
138
|
...(scenario !== undefined && scenario.length > 0 ? { scenario } : {}),
|
|
139
|
+
...(typeof rating === 'number' ? { rating } : {}),
|
|
140
|
+
// 退场记录仍留在列表里(文本曾是上下文的一部分,且判过时是人的裁决凭证),只是该打上删除线。
|
|
141
|
+
...(isRetired(r) ? { retired: true } : {}),
|
|
142
|
+
// 已被摘要取代:正文已不在上下文里。与 retired 正交,两者可同时在。
|
|
143
|
+
...(superseded.has(id) ? { superseded: true } : {}),
|
|
111
144
|
});
|
|
112
145
|
}
|
|
113
146
|
const next = { rev: state.rev, sessionId: state.sessionId, count: state.ids.length, items };
|
|
@@ -125,22 +158,24 @@ function sessionIdOf(header) {
|
|
|
125
158
|
* 注册 memoryRecall 投影。
|
|
126
159
|
*
|
|
127
160
|
* 缺席降级同 slots 投影:`ctx.inject` 在服务未提供时不回调;形状不符静默不注册。
|
|
128
|
-
* apply 是 O(1) 判脏(store.revision()
|
|
161
|
+
* apply 是 O(1) 判脏(store.revision() 与反馈序号比对),任意已提交事件都会带进来看一眼。
|
|
162
|
+
* @param feedback - 相关度反馈只读面;缺席时 view 不带 rating(旧行为)。
|
|
129
163
|
*/
|
|
130
|
-
export function registerRecallProjection(ctx, store, l1) {
|
|
164
|
+
export function registerRecallProjection(ctx, store, l1, feedback) {
|
|
131
165
|
ctx.inject(['sessionProjections'], (injected) => {
|
|
132
166
|
const registry = injected.sessionProjections;
|
|
133
167
|
if (registry === undefined || typeof registry.register !== 'function')
|
|
134
168
|
return;
|
|
169
|
+
const fbRev = () => feedback?.revision() ?? 0;
|
|
135
170
|
// 双代平面(同 slots 投影):0.1.0-rc.8 的 registry 走平面 schema/view,0.1.1+ 走 wire。
|
|
136
171
|
const definition = {
|
|
137
172
|
key: MEMORY_RECALL_KEY,
|
|
138
173
|
stateSchema,
|
|
139
|
-
init: (header) => buildState(sessionIdOf(header), store),
|
|
140
|
-
apply: (state) => buildState(state.sessionId, store, state),
|
|
141
|
-
wire: { viewSchema, view: (state) => makeView(state, l1) },
|
|
174
|
+
init: (header) => buildState(sessionIdOf(header), store, fbRev()),
|
|
175
|
+
apply: (state) => buildState(state.sessionId, store, fbRev(), state),
|
|
176
|
+
wire: { viewSchema, view: (state) => makeView(state, l1, feedback) },
|
|
142
177
|
schema: viewSchema,
|
|
143
|
-
view: (state) => makeView(state, l1),
|
|
178
|
+
view: (state) => makeView(state, l1, feedback),
|
|
144
179
|
stateVersion: 0,
|
|
145
180
|
};
|
|
146
181
|
registry.register(definition);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** 示例集总长预算(字符)。超了就该减条数,而不是悄悄放宽。 */
|
|
2
|
+
export declare const FEW_SHOT_BUDGET = 1400;
|
|
3
|
+
/** 一条止例:会话片段 → 模型该产出的 title/content 对。 */
|
|
4
|
+
interface GoodExample {
|
|
5
|
+
/** 类型(与 G2 骨架同名,方便对照)。 */
|
|
6
|
+
type: string;
|
|
7
|
+
title: string;
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 止例集。每条的 content 都按 G2 骨架写成"前因后果",title 是它的名词短语摘要。
|
|
12
|
+
*
|
|
13
|
+
* 刻意覆盖到 title 的四种难头:含英文标识符(吃字符额度)、含数字(容易被当 id 残片)、
|
|
14
|
+
* 纯中文短语、以及"主语是团队/用户"的正当名词短语(这几个词作**短语**开头是合法的,
|
|
15
|
+
* 只有叙述引子才算话头)。
|
|
16
|
+
*/
|
|
17
|
+
export declare const TITLE_GOOD_EXAMPLES: readonly GoodExample[];
|
|
18
|
+
/** 反例:模型最常犯的三种错,各给一条"不要这样"。 */
|
|
19
|
+
export declare const TITLE_BAD_EXAMPLES: ReadonlyArray<{
|
|
20
|
+
title: string;
|
|
21
|
+
why: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** 供用例与提示词共用:把止例渲染成"输入 → 输出对象"的示范块。 */
|
|
24
|
+
export declare function renderGoodExample(g: GoodExample): string;
|
|
25
|
+
/**
|
|
26
|
+
* 追加到抽取系统提示尾部的示例段。
|
|
27
|
+
*
|
|
28
|
+
* 只在 `cfg.naming.fewShot` 为真时拼上(默认关):它每轮都收固定成本,值不值要靠
|
|
29
|
+
* A/B 的 fallback 率说话,不能靠"看起来更专业"。
|
|
30
|
+
*/
|
|
31
|
+
export declare const EXTRACTION_FEW_SHOT: string;
|
|
32
|
+
/**
|
|
33
|
+
* 各类型的"前因后果"要素表(照 G2 `NARRATIVE_CLAUSE` 写,不另造一套定义)。
|
|
34
|
+
*
|
|
35
|
+
* 为什么需要它:可用性判据是用户定的——"content 没前因后果、写不全就不算可用"。
|
|
36
|
+
* 那句活若只留在提示词里就永远没法判定、也没法拿它定默认值,所以每个类型拆 3 个要素,
|
|
37
|
+
* 每个要素给一组词元(命中任一即算在)。
|
|
38
|
+
*
|
|
39
|
+
* **这是关键词启发式,不是语义判定**:要素换个说法就接不住;G2 自己也允许"信息不足则省略该节"。
|
|
40
|
+
* 因此它只能用于**同一输入下两臂相对比较**,不能当绝对质量分。
|
|
41
|
+
*/
|
|
42
|
+
export declare const SKELETON_ELEMENTS: Readonly<Record<string, ReadonlyArray<{
|
|
43
|
+
name: string;
|
|
44
|
+
anyOf: readonly string[];
|
|
45
|
+
}>>>;
|
|
46
|
+
/** 骨架打分结果。 */
|
|
47
|
+
export interface SkeletonScore {
|
|
48
|
+
类型: string;
|
|
49
|
+
要素数: number;
|
|
50
|
+
命中数: number;
|
|
51
|
+
缺: string[];
|
|
52
|
+
/** 所有要素都命中(且正文非空)。 */
|
|
53
|
+
齐备: boolean;
|
|
54
|
+
/** 该类型不在要素表里(分数仅供参考,不拿它下结论)。 */
|
|
55
|
+
无表可依: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 按类型给 content 打骨架分(关键词启发式,局限见 {@link SKELETON_ELEMENTS})。
|
|
59
|
+
*
|
|
60
|
+
* @param type - 记忆类型。
|
|
61
|
+
* @param content - 模型产出的正文。
|
|
62
|
+
* @returns 命中情况;空正文算 0 命中而不是"齐备"。
|
|
63
|
+
*/
|
|
64
|
+
export declare function scoreSkeleton(type: string, content: string): SkeletonScore;
|
|
65
|
+
/** 自检:示例本身是否合格(用例直接吃这个函数,不在用例里手抄判据)。 */
|
|
66
|
+
export declare function auditFewShot(): {
|
|
67
|
+
超预算: boolean;
|
|
68
|
+
长度: number;
|
|
69
|
+
不合格title: string[];
|
|
70
|
+
抄content开头: string[];
|
|
71
|
+
止例骨架不全: string[];
|
|
72
|
+
};
|
|
73
|
+
export {};
|