dsh-prime-memory 0.25.0 → 0.26.0
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 +17 -0
- package/CHANGELOG.ja.md +18 -0
- package/CHANGELOG.ko.md +18 -0
- package/CHANGELOG.md +19 -0
- package/dist/client.js +933 -664
- package/dist/config.d.ts +35 -2
- package/dist/config.js +26 -2
- package/dist/contract.d.ts +94 -2
- package/dist/keyword-backfill.d.ts +36 -7
- package/dist/keyword-backfill.js +137 -62
- package/dist/pipeline/relabel.d.ts +11 -12
- package/dist/pipeline/relabel.js +54 -66
- package/dist/pipeline/runner.js +24 -1
- package/dist/projection/recall.d.ts +3 -2
- package/dist/projection/recall.js +26 -5
- package/dist/projection/slots.d.ts +11 -0
- package/dist/projection/slots.js +13 -3
- package/dist/redistill-backfill.d.ts +133 -0
- package/dist/redistill-backfill.js +352 -0
- package/dist/room-review.d.ts +12 -13
- package/dist/room-review.js +37 -29
- package/dist/settings.js +13 -0
- package/dist/stats.js +116 -19
- package/dist/store/l1.d.ts +12 -0
- package/dist/store/l1.js +9 -0
- package/dist/store/memory-md.d.ts +8 -0
- package/dist/store/memory-md.js +13 -2
- package/dist/store/sqlite.d.ts +17 -0
- package/dist/store/sqlite.js +33 -0
- package/dist/wing-backfill.d.ts +16 -14
- package/dist/wing-backfill.js +76 -96
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -123,12 +123,31 @@ export interface MemoryConfig {
|
|
|
123
123
|
/** active 技能闲置多少天后自动归档(0 = 不做闲置归档)。 */
|
|
124
124
|
idleArchiveDays: number;
|
|
125
125
|
};
|
|
126
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* 存量回填护栏(wing / keyword / redistill 共用,默认 300 条 / 30min 总预算 / 串行 / 单条请求 240s)。
|
|
128
|
+
*
|
|
129
|
+
* 形状约定(用户定):**按单元、串行、不合并**——一个请求只装一条记忆,跑完一条再发下一条。
|
|
130
|
+
* 实测单条 35B 只要 ~2.6s,而 20 条合并成一批要 200s+ 且一次空输出/截断就带走整批,
|
|
131
|
+
* 所以“批量”在这里不省时间只堆风险。`concurrency` 默认 1;拖高概览页滑块可临时多路在飞。
|
|
132
|
+
*
|
|
133
|
+
* 三个旋钮分工(别混淆):
|
|
134
|
+
* - `maxRecords` = **一轮最多处理多少条**(防一次点击拖过久;真实队列没跑完的计入 deferred,
|
|
135
|
+
* 再点一次继续)。注意它**不是**请求批大小:逐条形状下每个请求只装 1 条。
|
|
136
|
+
* - `timeBudgetMs` = **本轮总墙钟预算**(防跑飞)。与 maxRecords 谁先到谁生效:
|
|
137
|
+
* 默认 300 条 × ~2.6s ≈ 13min < 30min,正常由 maxRecords 先结算。
|
|
138
|
+
* - `callTimeoutMs` = **一次 `callLLM`(含整条回退链)**的超时。不能沿用 `llm.timeoutMs`(120s):
|
|
139
|
+
* 它是**整次调用**的预算而非每条路由各享一份——真机踩到主路由(9B)空输出吃 95s,
|
|
140
|
+
* 降到 35B 后只剩余量在线上被掐死(看似“35B 也跑不完”)。默认 240s 按「路由数 × 单路由耗时」留余量。
|
|
141
|
+
*/
|
|
127
142
|
backfill: {
|
|
128
143
|
/** 单次回填处理条数上限。 */
|
|
129
144
|
maxRecords: number;
|
|
130
|
-
/**
|
|
145
|
+
/** 单次回填总墙钟预算(ms),超时剩留待再次触发。 */
|
|
131
146
|
timeBudgetMs: number;
|
|
147
|
+
/** LLM 批调用的并行度(1 = 串行)。 */
|
|
148
|
+
concurrency: number;
|
|
149
|
+
/** 一次 LLM 调用(含整条回退链)的超时(ms),比对话蒸馏的 llm.timeoutMs 宽;按路由数留余量。 */
|
|
150
|
+
callTimeoutMs: number;
|
|
132
151
|
};
|
|
133
152
|
/**
|
|
134
153
|
* 内容路由(workspace 归属):按「这条记忆**谈到**哪个项目」给记录补 `workspace_id`。
|
|
@@ -205,6 +224,20 @@ export interface MemoryConfig {
|
|
|
205
224
|
keyword: {
|
|
206
225
|
enabled: boolean;
|
|
207
226
|
};
|
|
227
|
+
/**
|
|
228
|
+
* 存量记忆重蒸馏整形(redistill):**独立回填批扫**,把活跃且 `scoreSkeleton` 判「骨架不齐备」
|
|
229
|
+
* 的 L1 正文按 G2 分类型骨架重排,并顺带产 `dsh_keywords` / `dsh_usage_scenario`。由
|
|
230
|
+
* `dsh-memory/redistill-backfill` 端点触发,镜像 keyword/wing 回填(单飞/后台/有界)。
|
|
231
|
+
*
|
|
232
|
+
* **默认关**:关时不整形、content/FTS/投射逐字不变(零漂移)。护栏复用 `backfill`。
|
|
233
|
+
* 与 keyword 回填的关键差异:本项**改 content**,写回走 `L1Store.appendNew`(追加 JSONL 事实源
|
|
234
|
+
* + 重嵌 + FTS + 投射点火),且入口要求 `vectorsReady()`(embed 未就绪时改正文会静默丢向量)。
|
|
235
|
+
* 只有整形后骨架分严格更优才写回,并盖整形版本戳(`dsh_reshape_ver`);未过闸的留原文**不打标记**,
|
|
236
|
+
* 下轮仍可再试——即只有「成功替换的」不再进入。
|
|
237
|
+
*/
|
|
238
|
+
redistill: {
|
|
239
|
+
enabled: boolean;
|
|
240
|
+
};
|
|
208
241
|
/**
|
|
209
242
|
* 类型叙述模板(G2):抽取时按 7 类型各自的“前因后果”骨架写 content。
|
|
210
243
|
*
|
package/dist/config.js
CHANGED
|
@@ -80,6 +80,22 @@ export function liveSettingsSchema() {
|
|
|
80
80
|
conflictFreeze: Schema.boolean().default(false),
|
|
81
81
|
// 轨2 技能结晶总开关(看板可拨):默认 false。开=注册 memory_skill_* 工具 + 注入 active 技能 + skills-* 端点可用。
|
|
82
82
|
skillsEnabled: Schema.boolean().default(false),
|
|
83
|
+
// 存量回填/进阶功能的启用开关(概览「存量回填与整形」区可拨):默认 false,
|
|
84
|
+
// 与部署 cfg.<x>.enabled 取或(经 effectiveCfg)——部署可强制开,live 只能追加开。
|
|
85
|
+
// 存在的理由:这些面都有对应按钮/端点,若开关只住 YAML,用户点了只会报“未开启”却没处翻。
|
|
86
|
+
keywordEnabled: Schema.boolean().default(false),
|
|
87
|
+
redistillEnabled: Schema.boolean().default(false),
|
|
88
|
+
mdSyncEnabled: Schema.boolean().default(false),
|
|
89
|
+
memportEnabled: Schema.boolean().default(false),
|
|
90
|
+
/**
|
|
91
|
+
* 存量回填(keyword / redistill)LLM 批调用的**运行时并行度**覆盖:
|
|
92
|
+
* `0` = 跟随部署 `cfg.backfill.concurrency`;1–8 = 直接覆盖。
|
|
93
|
+
*
|
|
94
|
+
* 为什么要是 live 而不是只住 YAML:真机出过“3 并发把每请求拖过超时线、13 批全 abort”,
|
|
95
|
+
* 排查时需要的恰好是“立刻降到 1 串行再看一遍”——为此改 YAML + 重启不现实。
|
|
96
|
+
* 概览页 0–8 滑块写的就是它(0 = 跟随部署,默认部署值是 1 串行)。
|
|
97
|
+
*/
|
|
98
|
+
backfillConcurrency: Schema.number().min(0).max(8).default(0),
|
|
83
99
|
// 工具分组封印(v0.21.0):false = 该组工具不注册,从模型工具列表消失。
|
|
84
100
|
// 核心读(memory_search/read_scene/receipts/conversation_search)与槽位三件不受控。
|
|
85
101
|
toolRoom: Schema.boolean().default(true),
|
|
@@ -164,10 +180,14 @@ export const memorySchema = Schema.object({
|
|
|
164
180
|
excerptChars: Schema.number().min(0).max(4000).default(240),
|
|
165
181
|
idleArchiveDays: Schema.number().min(0).max(3650).default(90),
|
|
166
182
|
}),
|
|
167
|
-
// wing
|
|
183
|
+
// wing/keyword/redistill 回填护栏。maxRecords 是“一轮处理多少条”,timeBudgetMs 是“本轮不跑飞”,
|
|
184
|
+
// callTimeoutMs 是“单次请求(含回退链)不撞线”;逐条形状下每请求只装 1 条(不是 20 条一批)。
|
|
185
|
+
// maxRecords 不得低于真实队列量级:真机活跃集里待整形真目标 251 条,砍到 40 就等于“点一次只动 40”。
|
|
168
186
|
backfill: Schema.object({
|
|
169
187
|
maxRecords: Schema.number().min(1).max(10_000).default(300),
|
|
170
|
-
timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(
|
|
188
|
+
timeBudgetMs: Schema.number().min(1000).max(1_800_000).default(1_800_000),
|
|
189
|
+
concurrency: Schema.number().min(1).max(8).default(1),
|
|
190
|
+
callTimeoutMs: Schema.number().min(1000).max(1_800_000).default(240_000),
|
|
171
191
|
}),
|
|
172
192
|
/**
|
|
173
193
|
* 内容路由:默认**开**(用户口径:谈到本仓/dsh 生态的记忆要归到本仓 workspace,
|
|
@@ -230,6 +250,10 @@ export const memorySchema = Schema.object({
|
|
|
230
250
|
keyword: Schema.object({
|
|
231
251
|
enabled: Schema.boolean().default(false),
|
|
232
252
|
}),
|
|
253
|
+
// 存量记忆重蒸馏整形:默认关(关时不整形、content/FTS/投射逐字不变);护栏复用 backfill。
|
|
254
|
+
redistill: Schema.object({
|
|
255
|
+
enabled: Schema.boolean().default(false),
|
|
256
|
+
}),
|
|
233
257
|
// 类型叙述模板(G2):默认**开**;开时抽取 prompt 追加分类型 content 骨架(关时仍逐字不变)。
|
|
234
258
|
// 默认值从关改开的理由写在上方接口注释:只给定义实测骨架齐备 0/23,
|
|
235
259
|
// 而"没前因后果就不算可用"是用户定的判据,所以默认值要按可用性定而不是按保守定。
|
package/dist/contract.d.ts
CHANGED
|
@@ -106,9 +106,20 @@ export interface MemoryLiveSettings {
|
|
|
106
106
|
/** 轨2 技能结晶总开关(看板可拨):true = 注册 memory_skill_* 工具、注入 active 技能、skills-* 端点可用。
|
|
107
107
|
* 默认 false(新功能默认关);与部署 cfg.skills.enabled 取或(任一开则生效)。 */
|
|
108
108
|
skillsEnabled: boolean;
|
|
109
|
+
/** 关键词回填启用开关(概览区可拨):true = 放行 dsh-memory/keyword-backfill。与部署 cfg.keyword.enabled 取或。 */
|
|
110
|
+
keywordEnabled: boolean;
|
|
111
|
+
/** 重蒸馏整形启用开关(概览区可拨):true = 放行 dsh-memory/redistill-backfill。与部署 cfg.redistill.enabled 取或。 */
|
|
112
|
+
redistillEnabled: boolean;
|
|
113
|
+
/** md 反向同步启用开关(概览区可拨):true = 放行 md-sync 回灌。与部署 cfg.mdSync.enabled 取或。 */
|
|
114
|
+
mdSyncEnabled: boolean;
|
|
115
|
+
/** 外部记忆来源 Connect 启用开关(概览区可拨):true = 放行 memport-* 端点。与部署 cfg.memport.enabled 取或。 */
|
|
116
|
+
memportEnabled: boolean;
|
|
109
117
|
/** md 投射/后缀收敛的单次动作护栏运行时覆盖:0 = 跟随部署 `cfg.memExport.maxRecords`(默认 2000)。
|
|
110
118
|
* 存在的理由:一次点完多少条属于“回填历史时才改一下”的旋钮,不该要求改 YAML + 重启。 */
|
|
111
119
|
mdExportMaxRecords: number;
|
|
120
|
+
/** 存量回填(keyword/redistill)LLM 批调用的运行时并行度覆盖:0 = 跟随部署
|
|
121
|
+
* `cfg.backfill.concurrency`(默认 2);1–8 = 直接覆盖。概览页 1–3 滑块写的就是它。 */
|
|
122
|
+
backfillConcurrency: number;
|
|
112
123
|
}
|
|
113
124
|
/** 召回停用原因(session-stats recall.enabled=false 时带出;短路序第一个为假的因子)。 */
|
|
114
125
|
export type RecallDisabledReason = 'deploy' | 'global' | 'session' | 'mode';
|
|
@@ -641,12 +652,39 @@ export interface RoomAdminResponse {
|
|
|
641
652
|
/** retire withRecords 实际退场的记录条数(软删,可恢复)。 */
|
|
642
653
|
retiredRecords?: number;
|
|
643
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* 回填端点(wing/keyword/redistill)的请求体。三者都同时是「触发器 + 状态查询」:
|
|
657
|
+
* - `probe` 缺省/false:空闲时**启动**一轮;已在跑则只回状态(单飞)。
|
|
658
|
+
* - `probe: true`:**只读快照**,绝不启动——供 UI 离开区域再回来时恢复在途任务的进度
|
|
659
|
+
* (否则计时器随组件卸载而清,回到页面只能看到卡住的最后一帧)。
|
|
660
|
+
*/
|
|
661
|
+
export interface BackfillTriggerRequest {
|
|
662
|
+
probe?: boolean;
|
|
663
|
+
}
|
|
644
664
|
/** dsh-memory/wing-backfill(一键回填,后台任务;端点立即返回,进度以 wing-overview 轮询)。 */
|
|
645
665
|
export interface WingBackfillResponse {
|
|
646
666
|
/** true = 本次触发启动了新任务;false = 已有任务在跑(单飞,不叠加)。 */
|
|
647
667
|
started: boolean;
|
|
648
668
|
running: boolean;
|
|
649
669
|
}
|
|
670
|
+
/** 回填批扫的逐条进度(keyword/redistill 共用):后台任务一轮可达几分钟,没有它 UI 只能转圈圈。 */
|
|
671
|
+
export interface BackfillProgress {
|
|
672
|
+
/** 每个请求处理的条数(两个回填都已改成逐条 = 1;保留字段供未来批量形状复用)。 */
|
|
673
|
+
chunk: number;
|
|
674
|
+
/** 扫描阶段确定的**真目标**总数(扫描未完成时为已发现数)。 */
|
|
675
|
+
candidates: number;
|
|
676
|
+
/** 本次已完成(送过 LLM 且已提交结果)条数。 */
|
|
677
|
+
processed: number;
|
|
678
|
+
/** 仍在扫描阶段(此阶段报「扫描中」而不是「第 N 条」)。 */
|
|
679
|
+
scanning: boolean;
|
|
680
|
+
/** 已完成条数 / 本次要处理的总条数。 */
|
|
681
|
+
batchesDone: number;
|
|
682
|
+
batchTotal: number;
|
|
683
|
+
/** LLM 批调用并行度(cfg.backfill.concurrency)。 */
|
|
684
|
+
concurrency: number;
|
|
685
|
+
/** 预算/上限用尽未处理、留下次的条数(跟“跑完了”区分开的关键一个数)。 */
|
|
686
|
+
deferred: number;
|
|
687
|
+
}
|
|
650
688
|
/** dsh-memory/keyword-backfill(关键词蒸馏回填,后台任务;门控关时 enabled=false 不启动)。 */
|
|
651
689
|
export interface KeywordBackfillResponse {
|
|
652
690
|
/** true = 本次触发启动了新任务;false = 已有任务在跑或门控关。 */
|
|
@@ -658,6 +696,24 @@ export interface KeywordBackfillResponse {
|
|
|
658
696
|
updated: number;
|
|
659
697
|
/** 失败/跳过数。 */
|
|
660
698
|
failed: number;
|
|
699
|
+
/** 分批进度(运行期轮询显示用)。 */
|
|
700
|
+
progress: BackfillProgress;
|
|
701
|
+
}
|
|
702
|
+
/** dsh-memory/redistill-backfill(存量记忆重蒸馏整形,后台任务;门控关时 enabled=false 不启动)。 */
|
|
703
|
+
export interface RedistillBackfillResponse {
|
|
704
|
+
/** true = 本次触发启动了新任务;false = 已有任务在跑或门控关。 */
|
|
705
|
+
started: boolean;
|
|
706
|
+
running: boolean;
|
|
707
|
+
/** 门控 `cfg.redistill.enabled`;关时不启动。 */
|
|
708
|
+
enabled: boolean;
|
|
709
|
+
/** 本进程累计整形写回条数(重启归零;观测用)。 */
|
|
710
|
+
updated: number;
|
|
711
|
+
/** 质量闸判不写(留原文)条数。 */
|
|
712
|
+
skipped: number;
|
|
713
|
+
/** 失败/跳过数(LLM 失败、id 配不上、embed 掉线)。 */
|
|
714
|
+
failed: number;
|
|
715
|
+
/** 分批进度(运行期轮询显示用)。 */
|
|
716
|
+
progress: BackfillProgress;
|
|
661
717
|
}
|
|
662
718
|
/** dsh-memory/session-stats(悬浮卡信息区;热路径端点)。 */
|
|
663
719
|
export interface SessionStatsRequest {
|
|
@@ -892,6 +948,38 @@ export interface UiRecord {
|
|
|
892
948
|
*/
|
|
893
949
|
userRelevance?: number;
|
|
894
950
|
}
|
|
951
|
+
/** dsh-memory/records-meta(按 ID 批量查元数据,供 .memory.md 查看器拼接 Qoder 风格条目)。 */
|
|
952
|
+
export interface RecordsMetaRequest {
|
|
953
|
+
/** L1 record_id 列表(上限 50)。 */
|
|
954
|
+
ids: string[];
|
|
955
|
+
}
|
|
956
|
+
/** 单条轻量元数据(含正文,供 .memory.md 查看器拼接头部条)。 */
|
|
957
|
+
export interface RecordMeta {
|
|
958
|
+
id: string;
|
|
959
|
+
/** 名称蒸馏产出的短标题;无 name 时为 null。 */
|
|
960
|
+
name: string | null;
|
|
961
|
+
type: string;
|
|
962
|
+
priority: number;
|
|
963
|
+
/** 正文全文(markdown)。 */
|
|
964
|
+
content: string;
|
|
965
|
+
maturity?: 'low' | 'mid' | 'high';
|
|
966
|
+
createdAt: string | null;
|
|
967
|
+
updatedAt: string | null;
|
|
968
|
+
scene: string;
|
|
969
|
+
scope: 'global' | 'workspace';
|
|
970
|
+
workspaceId: string | null;
|
|
971
|
+
/** 关键词(metadata.dsh_keywords)。 */
|
|
972
|
+
keywords?: string[];
|
|
973
|
+
/** 使用场景(metadata.dsh_usage_scenario)。 */
|
|
974
|
+
usageScenario?: string[];
|
|
975
|
+
/** 投射文件相对路径 `<type>/<stem>.memory.md`。 */
|
|
976
|
+
filePath: string;
|
|
977
|
+
/** 是否已退场(软删)。 */
|
|
978
|
+
retired: boolean;
|
|
979
|
+
}
|
|
980
|
+
export interface RecordsMetaResponse {
|
|
981
|
+
items: RecordMeta[];
|
|
982
|
+
}
|
|
895
983
|
export interface ListRecordsResponse {
|
|
896
984
|
items: UiRecord[];
|
|
897
985
|
hasMore: boolean;
|
|
@@ -1661,12 +1749,14 @@ export interface DshMemoryRequestMap {
|
|
|
1661
1749
|
'dsh-memory/rooms-export': RoomsExportRequest;
|
|
1662
1750
|
'dsh-memory/room-register': RoomRegisterRequest;
|
|
1663
1751
|
'dsh-memory/room-admin': RoomAdminRequest;
|
|
1664
|
-
'dsh-memory/wing-backfill':
|
|
1665
|
-
'dsh-memory/keyword-backfill':
|
|
1752
|
+
'dsh-memory/wing-backfill': BackfillTriggerRequest;
|
|
1753
|
+
'dsh-memory/keyword-backfill': BackfillTriggerRequest;
|
|
1754
|
+
'dsh-memory/redistill-backfill': BackfillTriggerRequest;
|
|
1666
1755
|
'dsh-memory/session-stats': SessionStatsRequest;
|
|
1667
1756
|
'dsh-memory/settings-get': Record<string, never>;
|
|
1668
1757
|
'dsh-memory/settings-set': SettingsSetRequest;
|
|
1669
1758
|
'dsh-memory/list-records': ListRecordsRequest;
|
|
1759
|
+
'dsh-memory/records-meta': RecordsMetaRequest;
|
|
1670
1760
|
'dsh-memory/records-delete': RecordsDeleteRequest;
|
|
1671
1761
|
'dsh-memory/receipts': ReceiptsRequest;
|
|
1672
1762
|
'dsh-memory/conflicts': ConflictsRequest;
|
|
@@ -1726,10 +1816,12 @@ export interface DshMemoryResponseMap {
|
|
|
1726
1816
|
'dsh-memory/room-admin': RoomAdminResponse;
|
|
1727
1817
|
'dsh-memory/wing-backfill': WingBackfillResponse;
|
|
1728
1818
|
'dsh-memory/keyword-backfill': KeywordBackfillResponse;
|
|
1819
|
+
'dsh-memory/redistill-backfill': RedistillBackfillResponse;
|
|
1729
1820
|
'dsh-memory/session-stats': SessionStatsResponse;
|
|
1730
1821
|
'dsh-memory/settings-get': SettingsGetResponse;
|
|
1731
1822
|
'dsh-memory/settings-set': SettingsSetResponse;
|
|
1732
1823
|
'dsh-memory/list-records': ListRecordsResponse;
|
|
1824
|
+
'dsh-memory/records-meta': RecordsMetaResponse;
|
|
1733
1825
|
'dsh-memory/records-delete': RecordsDeleteResponse;
|
|
1734
1826
|
'dsh-memory/receipts': ReceiptsResponse;
|
|
1735
1827
|
'dsh-memory/conflicts': ConflictsResponse;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 关键词蒸馏回填(keyword axis 产出方)。
|
|
3
3
|
*
|
|
4
|
-
* 职责:从 L1 记录中找「缺 dsh_keywords
|
|
4
|
+
* 职责:从 L1 记录中找「缺 dsh_keywords」的存量记录,**一条一个请求**送 LLM 提炼关键词
|
|
5
5
|
* 与使用场景,归一后写 `metadata.dsh_keywords` + `metadata.dsh_usage_scenario`,
|
|
6
6
|
* 并用 `patchMetadataAndReindex` 保证新关键词**立即进 FTS BM25 轴**。
|
|
7
7
|
*
|
|
8
8
|
* 安全边界(继承 wing-backfill):
|
|
9
9
|
* - 单飞:同时最多一个回填任务,重复触发直接返回 running(不排队不叠加)
|
|
10
|
-
* -
|
|
10
|
+
* - 后台执行:端点立即返回,**逐条推进、跑完一条再发下一条**,不阻塞 RPC/管线
|
|
11
11
|
* - 失败可退:LLM 失败/输出不合法只跳过当前块,原记录零改动(标签只写成功的)
|
|
12
12
|
* - 有界:单次任务最多处理 cfg.backfill.maxRecords 条,防止一次跑飞;其余可再次触发
|
|
13
13
|
* - 门控关(`cfg.keyword.enabled = false`):run() 入口立即跳过——无回填、FTS 逐字不变
|
|
@@ -21,11 +21,33 @@ export interface KeywordBackfillDeps {
|
|
|
21
21
|
cfg: MemoryConfig;
|
|
22
22
|
backend: MemoryBackend;
|
|
23
23
|
logger: MemoryLogger;
|
|
24
|
+
/**
|
|
25
|
+
* 运行时**生效并发**读取器(可选):给正在跑的任务即时改并发用——池子每完成一条重读一次。
|
|
26
|
+
* 缺省 = 启动时读一次 `cfg.backfill.concurrency`(旧行为)。
|
|
27
|
+
*/
|
|
28
|
+
concurrency?: () => number | undefined;
|
|
24
29
|
}
|
|
25
30
|
export interface KeywordBackfillState {
|
|
26
31
|
running: boolean;
|
|
27
32
|
updated: number;
|
|
28
33
|
failed: number;
|
|
34
|
+
/** 分批进度(UI 轮询据此显示「扫描中 / 第几条」,否则运行期只有一个「回填中…」): */
|
|
35
|
+
/** 每个请求处理的条数——已改逐条,永远为 1(与 redistill 同形状)。 */
|
|
36
|
+
chunk: number;
|
|
37
|
+
/** 扫描阶段确定的真目标总数(缺关键词;扫描未完成时为已发现数)。 */
|
|
38
|
+
candidates: number;
|
|
39
|
+
/** 本次已完成(送过 LLM 且已写回)**在请求完成时才自增**,否则“全失败”跟“全成功”看起来一样。 */
|
|
40
|
+
processed: number;
|
|
41
|
+
/** 仍在扫描阶段。 */
|
|
42
|
+
scanning: boolean;
|
|
43
|
+
/** 已完成条数(与 processed 同值)。 */
|
|
44
|
+
batchesDone: number;
|
|
45
|
+
/** 本次要处理的总条数。 */
|
|
46
|
+
batchTotal: number;
|
|
47
|
+
/** LLM 批调用并行度。 */
|
|
48
|
+
concurrency: number;
|
|
49
|
+
/** 预算/上限用尽未处理、留下次的条数。 */
|
|
50
|
+
deferred: number;
|
|
29
51
|
}
|
|
30
52
|
export declare function keywordBackfillState(): KeywordBackfillState;
|
|
31
53
|
/** 启动后台关键词回填;已在运行返回 false(单飞)。 */
|
|
@@ -38,9 +60,16 @@ export declare function startKeywordBackfill(deps: KeywordBackfillDeps): {
|
|
|
38
60
|
* 门控关(`cfg.keyword.enabled = false`)时入口立即返回,不碰任何记录与 fts。
|
|
39
61
|
*/
|
|
40
62
|
export declare function runKeywordBackfillOnce(deps: KeywordBackfillDeps): Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
export
|
|
43
|
-
record: MemoryRecord;
|
|
63
|
+
/** 单条标注结果。 */
|
|
64
|
+
export interface KeywordOneResult {
|
|
44
65
|
keywords: string[];
|
|
45
|
-
usageScenario
|
|
46
|
-
}
|
|
66
|
+
usageScenario: string[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 标注**一条**记忆的关键词与情景(一个请求只装一条)。失败/无合法关键词返回 null。
|
|
70
|
+
*
|
|
71
|
+
* 旧名 `labelKeywordChunk`(多条一次)已废:合并请求下一次截断/空输出就整批丢弃,
|
|
72
|
+
* 而逐条只花秒级。记账(state.failed 等)全部归调用方——标注器不再动全局状态,
|
|
73
|
+
* 否则同一条会被两处各计一次。
|
|
74
|
+
*/
|
|
75
|
+
export declare function labelKeywordOne(ctx: Context, cfg: MemoryConfig, logger: MemoryLogger, rec: MemoryRecord, callTimeoutMs?: number): Promise<KeywordOneResult | null>;
|
package/dist/keyword-backfill.js
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
import { callLLM, parseJsonLogged } from './llm.js';
|
|
2
2
|
import { normKeywords, normUsageScenario, KEYWORDS_METADATA_KEY } from './metadata-validators.js';
|
|
3
3
|
import { yieldLoop } from './util/yield.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// 一个请求只装**一条**记忆(旧形状是 20 条/次,已改)。与 redistill 同口径:
|
|
5
|
+
// 合并请求把爆炸半径从 1 条放大到 20 条(一次截断/空输出就整批丢弃),而实测单条
|
|
6
|
+
// 在本地端点上只是秒级,合并并不省时间。
|
|
6
7
|
/** 扫描页大小(轻量投影分页;与 wing-backfill 一致)。 */
|
|
7
8
|
const PAGE = 200;
|
|
8
9
|
/** 单次任务默认处理上限(cfg.backfill.maxRecords 优先)。 */
|
|
9
|
-
const DEFAULT_MAX_RECORDS =
|
|
10
|
-
/**
|
|
11
|
-
const DEFAULT_TIME_BUDGET_MS =
|
|
12
|
-
|
|
10
|
+
const DEFAULT_MAX_RECORDS = 40;
|
|
11
|
+
/** 总墙钟默认预算(cfg.backfill.timeBudgetMs 优先)。 */
|
|
12
|
+
const DEFAULT_TIME_BUDGET_MS = 1_800_000;
|
|
13
|
+
/** 默认并行度(cfg.backfill.concurrency 优先):1 = 串行逐条(用户定的形状)。 */
|
|
14
|
+
const DEFAULT_CONCURRENCY = 1;
|
|
15
|
+
/**
|
|
16
|
+
* 一次调用(含整条回退链)的默认超时(cfg.backfill.callTimeoutMs 优先)。
|
|
17
|
+
* 不能沿用 `llm.timeoutMs`(120s):它是整次调用的预算而不是每条路由各享一份,
|
|
18
|
+
* 主路由空转一轮再降级就会撞线。
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_CALL_TIMEOUT_MS = 240_000;
|
|
21
|
+
/** 单条关键词标注的输出预算(5 关键词 + 3 情景,实测远低于此)。 */
|
|
22
|
+
const LABEL_MAX_TOKENS = 512;
|
|
23
|
+
/** 并发度钳位(与 schema 的 min1/max8 同口径)。 */
|
|
24
|
+
function clampConcurrency(raw) {
|
|
25
|
+
const n = typeof raw === 'number' && Number.isFinite(raw) ? Math.round(raw) : DEFAULT_CONCURRENCY;
|
|
26
|
+
return Math.min(8, Math.max(1, n));
|
|
27
|
+
}
|
|
28
|
+
const state = {
|
|
29
|
+
running: false, updated: 0, failed: 0,
|
|
30
|
+
chunk: 1, candidates: 0, processed: 0,
|
|
31
|
+
scanning: false, batchesDone: 0, batchTotal: 0, concurrency: DEFAULT_CONCURRENCY, deferred: 0,
|
|
32
|
+
};
|
|
13
33
|
export function keywordBackfillState() {
|
|
14
34
|
return { ...state };
|
|
15
35
|
}
|
|
16
36
|
// ── SYSTEM 提示词(定稿版,由 tools/keyword-distill.prompt.md 的 ```text 块迁移) ──
|
|
17
37
|
const SYSTEM_PROMPT = [
|
|
18
|
-
'
|
|
38
|
+
'你是记忆库的关键词与情景标注器。给定**一条**记忆(含标题与正文),为它提炼:',
|
|
19
39
|
'1) keywords:最能代表该记忆的检索词,用于精确命中;2) usage_scenario:未来「何时应当浮现这条记忆」的场景短语。',
|
|
20
40
|
'',
|
|
21
41
|
'严格规则:',
|
|
@@ -24,10 +44,9 @@ const SYSTEM_PROMPT = [
|
|
|
24
44
|
'- keywords:2–5 个(宁少勿滥,够区分即可);每个 2–6 词或 1 个复合术语;不得与标题字面重复、不得互相近义堆叠、不收停用词/整句。',
|
|
25
45
|
'- usage_scenario:1–3 条;每条 5–10 词的短场景描述(不是完整命令句);彼此必须可区分,相似的合并成更泛化的一条。',
|
|
26
46
|
'- 语言跟随该记忆正文:中文记忆产出中文(技术标识符、API 名、错误码原样保留),英文记忆产出英文。',
|
|
27
|
-
'- id 必须原样照抄,不得改写、截断或改用序号;一条记忆只产一组,不要拆分。',
|
|
28
47
|
'',
|
|
29
|
-
'
|
|
30
|
-
'{"
|
|
48
|
+
'输出:只输出**一个 JSON 对象**(不是数组),不要多余文字、不要解释。形如:',
|
|
49
|
+
'{"keywords":["…","…"],"usage_scenario":["…","…"]}',
|
|
31
50
|
].join('\n');
|
|
32
51
|
/** 启动后台关键词回填;已在运行返回 false(单飞)。 */
|
|
33
52
|
export function startKeywordBackfill(deps) {
|
|
@@ -36,9 +55,18 @@ export function startKeywordBackfill(deps) {
|
|
|
36
55
|
state.running = true;
|
|
37
56
|
state.updated = 0;
|
|
38
57
|
state.failed = 0;
|
|
58
|
+
state.candidates = 0;
|
|
59
|
+
state.processed = 0;
|
|
60
|
+
state.scanning = true;
|
|
61
|
+
state.batchesDone = 0;
|
|
62
|
+
state.batchTotal = 0;
|
|
63
|
+
state.deferred = 0;
|
|
64
|
+
state.concurrency = clampConcurrency(deps.cfg.backfill?.concurrency);
|
|
39
65
|
void runKeywordBackfillOnce(deps).finally(() => {
|
|
40
66
|
state.running = false;
|
|
41
|
-
|
|
67
|
+
state.scanning = false;
|
|
68
|
+
deps.logger.info(`[memory] keyword 回填结束:成功 ${state.updated} 条,失败 ${state.failed} 条`
|
|
69
|
+
+ (state.deferred > 0 ? `,留待下次 ${state.deferred} 条` : ''));
|
|
42
70
|
});
|
|
43
71
|
return { started: true, running: true };
|
|
44
72
|
}
|
|
@@ -54,11 +82,14 @@ export async function runKeywordBackfillOnce(deps) {
|
|
|
54
82
|
}
|
|
55
83
|
const MAX_RECORDS = cfg.backfill?.maxRecords ?? DEFAULT_MAX_RECORDS;
|
|
56
84
|
const TIME_BUDGET_MS = cfg.backfill?.timeBudgetMs ?? DEFAULT_TIME_BUDGET_MS;
|
|
85
|
+
const LIMIT = clampConcurrency(cfg.backfill?.concurrency);
|
|
86
|
+
state.concurrency = LIMIT;
|
|
57
87
|
const startedAt = Date.now();
|
|
58
|
-
// ──
|
|
88
|
+
// ── 扫描阶段(轻量投影,不反序列化正文;分页 + 每页让位,不设墙钟)──
|
|
59
89
|
const total = await backend.size();
|
|
60
90
|
const pendingIds = [];
|
|
61
91
|
let scanned = 0;
|
|
92
|
+
state.scanning = true;
|
|
62
93
|
for (let offset = 0;; offset += PAGE) {
|
|
63
94
|
const page = await backend.allLite(PAGE, offset);
|
|
64
95
|
if (page.length === 0)
|
|
@@ -69,75 +100,119 @@ export async function runKeywordBackfillOnce(deps) {
|
|
|
69
100
|
pendingIds.push(r.id);
|
|
70
101
|
}
|
|
71
102
|
scanned += page.length;
|
|
72
|
-
|
|
73
|
-
logger.info(`[memory] keyword 回填:墙钟预算(${TIME_BUDGET_MS / 1000}s)在扫描阶段用尽(已扫 ${scanned}/${total}),留待再触发`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
103
|
+
state.candidates = pendingIds.length;
|
|
76
104
|
await yieldLoop();
|
|
77
105
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
state.scanning = false;
|
|
107
|
+
const batch = pendingIds.slice(0, MAX_RECORDS);
|
|
108
|
+
state.candidates = batch.length;
|
|
109
|
+
state.batchTotal = batch.length;
|
|
110
|
+
state.deferred = Math.max(0, pendingIds.length - batch.length);
|
|
111
|
+
const callTimeoutMs = cfg.backfill?.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
|
|
112
|
+
logger.info(`[memory] keyword 回填启动:已扫 ${scanned}/${total} 行,缺关键词 ${pendingIds.length} 条,本次处理 ${batch.length} 条,并行度 ${LIMIT}`);
|
|
113
|
+
/** 一条一个请求:取一条 → 标注 → **即时写回**(含 fts 重索引)。 */
|
|
114
|
+
const labelAndCommit = async (id) => {
|
|
115
|
+
const [rec] = await backend.getByIds([id]);
|
|
116
|
+
if (!rec || !rec.content || rec.content.trim() === '') {
|
|
117
|
+
state.processed++;
|
|
118
|
+
state.batchesDone++;
|
|
119
|
+
return;
|
|
85
120
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
meta.dsh_usage_scenario = usageScenario;
|
|
97
|
-
if (await backend.patchMetadataAndReindex(record.id, meta))
|
|
121
|
+
const labeled = await labelKeywordOne(deps.ctx, cfg, logger, rec, callTimeoutMs);
|
|
122
|
+
if (!labeled) {
|
|
123
|
+
state.failed++;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const meta = { ...(rec.metadata ?? {}) };
|
|
127
|
+
meta[KEYWORDS_METADATA_KEY] = labeled.keywords;
|
|
128
|
+
if (labeled.usageScenario.length > 0)
|
|
129
|
+
meta.dsh_usage_scenario = labeled.usageScenario;
|
|
130
|
+
if (await backend.patchMetadataAndReindex(rec.id, meta))
|
|
98
131
|
state.updated++;
|
|
99
132
|
else
|
|
100
133
|
state.failed++;
|
|
101
|
-
await yieldLoop();
|
|
102
134
|
}
|
|
135
|
+
// 完成才计数(同 redistill):否则“全失败”在面板上跟“全成功”长得一样。
|
|
136
|
+
state.processed++;
|
|
137
|
+
state.batchesDone++;
|
|
138
|
+
await yieldLoop();
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* 弹性工作池(与 redistill 同构):每完成一条重读一次**生效并发**,拖概览页滑块即时生效。
|
|
142
|
+
* 拖高立刻补 worker;拖低则各自干完手头那条后自然收工。缺省 getter = 启动值。
|
|
143
|
+
* 预算只在**条前**检查,中途不挖已烧 token 的那条。
|
|
144
|
+
*/
|
|
145
|
+
const readLimit = () => {
|
|
146
|
+
const raw = deps.concurrency ? deps.concurrency() : undefined;
|
|
147
|
+
return clampConcurrency(raw ?? cfg.backfill?.concurrency);
|
|
148
|
+
};
|
|
149
|
+
let cursor = 0;
|
|
150
|
+
let budgetHit = false;
|
|
151
|
+
const work = async () => {
|
|
152
|
+
for (;;) {
|
|
153
|
+
if (budgetHit || Date.now() - startedAt > TIME_BUDGET_MS) {
|
|
154
|
+
budgetHit = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const i = cursor++;
|
|
158
|
+
if (i >= batch.length)
|
|
159
|
+
return;
|
|
160
|
+
await labelAndCommit(batch[i]);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const inFlight = new Set();
|
|
164
|
+
const spawn = () => {
|
|
165
|
+
const p = work().finally(() => inFlight.delete(p));
|
|
166
|
+
inFlight.add(p);
|
|
167
|
+
};
|
|
168
|
+
for (;;) {
|
|
169
|
+
const target = Math.max(1, Math.min(readLimit(), batch.length));
|
|
170
|
+
state.concurrency = target; // 面板「并行 N」跟实时值走
|
|
171
|
+
while (!budgetHit && inFlight.size < target && cursor < batch.length)
|
|
172
|
+
spawn();
|
|
173
|
+
if (inFlight.size === 0)
|
|
174
|
+
break;
|
|
175
|
+
await Promise.race(inFlight);
|
|
176
|
+
}
|
|
177
|
+
if (budgetHit) {
|
|
178
|
+
state.deferred += Math.max(0, batch.length - state.processed);
|
|
179
|
+
logger.info(`[memory] keyword 回填:墙钟预算(${Math.round(TIME_BUDGET_MS / 1000)}s)用尽,累计留待再触发 ${state.deferred} 条`);
|
|
103
180
|
}
|
|
104
181
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
182
|
+
/**
|
|
183
|
+
* 标注**一条**记忆的关键词与情景(一个请求只装一条)。失败/无合法关键词返回 null。
|
|
184
|
+
*
|
|
185
|
+
* 旧名 `labelKeywordChunk`(多条一次)已废:合并请求下一次截断/空输出就整批丢弃,
|
|
186
|
+
* 而逐条只花秒级。记账(state.failed 等)全部归调用方——标注器不再动全局状态,
|
|
187
|
+
* 否则同一条会被两处各计一次。
|
|
188
|
+
*/
|
|
189
|
+
export async function labelKeywordOne(ctx, cfg, logger, rec, callTimeoutMs = DEFAULT_CALL_TIMEOUT_MS) {
|
|
190
|
+
const user = `记忆:\n标题:${rec.name ?? ''}\n正文:${rec.content.slice(0, 900)}`;
|
|
191
|
+
const t0 = Date.now();
|
|
192
|
+
const secs = () => Math.round((Date.now() - t0) / 10);
|
|
109
193
|
try {
|
|
110
194
|
const raw = await callLLM(ctx, cfg, {
|
|
111
195
|
system: SYSTEM_PROMPT,
|
|
112
196
|
user,
|
|
113
|
-
maxTokens:
|
|
197
|
+
maxTokens: LABEL_MAX_TOKENS,
|
|
114
198
|
layer: 'l1-extract',
|
|
115
199
|
logger,
|
|
200
|
+
signal: AbortSignal.timeout(callTimeoutMs > 0 ? callTimeoutMs : DEFAULT_CALL_TIMEOUT_MS),
|
|
116
201
|
});
|
|
202
|
+
logger.debug?.(`[memory] keyword 单条耗时 ${(Date.now() - t0) / 1000}s(id=${rec.id})`);
|
|
117
203
|
const parsed = parseJsonLogged(raw, 'keyword 回填', logger);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
logger.warn(`[memory] keyword 回填:id 配对失败,已丢弃(id=${String(item?.id).slice(0, 40)})`);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const keywords = normKeywords(item.keywords, 5);
|
|
129
|
-
if (keywords.length === 0) {
|
|
130
|
-
logger.warn(`[memory] keyword 回填:normKeywords 结果为空,已跳过(id=${record.id})`);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
const usageScenario = normUsageScenario(item.usage_scenario, 3);
|
|
134
|
-
out.push({ record, keywords, usageScenario: usageScenario.length > 0 ? usageScenario : undefined });
|
|
204
|
+
const obj = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
205
|
+
if (!obj || typeof obj !== 'object')
|
|
206
|
+
return null;
|
|
207
|
+
const keywords = normKeywords(obj.keywords, 5);
|
|
208
|
+
if (keywords.length === 0) {
|
|
209
|
+
logger.warn(`[memory] keyword 回填:normKeywords 结果为空,已跳过(id=${rec.id})`);
|
|
210
|
+
return null;
|
|
135
211
|
}
|
|
136
|
-
return
|
|
212
|
+
return { keywords, usageScenario: normUsageScenario(obj.usage_scenario, 3) };
|
|
137
213
|
}
|
|
138
214
|
catch (err) {
|
|
139
|
-
logger.warn(`[memory] keyword
|
|
140
|
-
|
|
141
|
-
return [];
|
|
215
|
+
logger.warn(`[memory] keyword 单条失败(id=${rec.id},已等 ${secs() / 10}s,原记录未改): ${err instanceof Error ? err.message : String(err)}`);
|
|
216
|
+
return null;
|
|
142
217
|
}
|
|
143
218
|
}
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* - Wing 合法性:metadata.hall 非空但不在词表(8 角 + general)→ 剥离非法值,转入 LLM 重标队列;
|
|
6
6
|
* - 认知 hall:按 type 静态映射(cognitive-hall.ts)可派生而 metadata.cogHall 缺失/不一致 → 补写修正;
|
|
7
7
|
* 2. **LLM 重标定**(有界批,受 wing.enabled 门控):
|
|
8
|
-
* - 未打标 wing 的记录复用一键回填的标注器(
|
|
8
|
+
* - 未打标 wing 的记录复用一键回填的标注器(labelWingOne)补打;
|
|
9
9
|
* - 涌现标签(tags,Room 的前身):同一批记录由标注器顺带产出 1-3 个 slug 标签写 metadata.tags。
|
|
10
10
|
*
|
|
11
|
+
* 形状约定(用户定):**按单元、串行、不合并** —— 一个请求只装一条记录
|
|
12
|
+
* (旧形状 20 条/请求,一次截断/空输出就带走整批)。
|
|
13
|
+
*
|
|
11
14
|
* 安全边界与一键回填一致:LLM 失败只跳过当前块、原记录零改动;机械与 LLM 写回都有上限,防止单次跑飞。
|
|
12
15
|
* 本阶段在反刍收尾(L2/L3 之后)执行,**任何失败不拖垮反刍整体**(只 warn + 计数)。
|
|
13
16
|
*/
|
|
@@ -38,27 +41,23 @@ export interface RelabelDeps {
|
|
|
38
41
|
backend: MemoryBackend;
|
|
39
42
|
logger: MemoryLogger;
|
|
40
43
|
}
|
|
41
|
-
/** 测试注入口:分别替换 wing 标注器与 tags 标注器(默认走真实 LLM 路径)。
|
|
44
|
+
/** 测试注入口:分别替换 wing 标注器与 tags 标注器(默认走真实 LLM 路径)。
|
|
45
|
+
* 两个标注器都是**逐条**形状(一个请求一条):成功返回结果,失败返回 null。 */
|
|
42
46
|
export interface RelabelOverrides {
|
|
43
|
-
wingLabeler?: (
|
|
44
|
-
|
|
45
|
-
wing: string;
|
|
46
|
-
}>>;
|
|
47
|
-
tagger?: (chunk: MemoryRecord[]) => Promise<Array<{
|
|
48
|
-
record: MemoryRecord;
|
|
49
|
-
tags: string[];
|
|
50
|
-
}>>;
|
|
47
|
+
wingLabeler?: (rec: MemoryRecord) => Promise<string | null>;
|
|
48
|
+
tagger?: (rec: MemoryRecord) => Promise<string[] | null>;
|
|
51
49
|
}
|
|
52
50
|
export interface RelabelOpts {
|
|
53
51
|
/**
|
|
54
52
|
* 批次进度回调(relabeling 阶段的 detail/子进度由此驱动)。
|
|
55
53
|
*
|
|
56
54
|
* 第 4 参 `label` 用于**区分机械段与 LLM 段**:机械巡检按 200 条/批推进,
|
|
57
|
-
* LLM
|
|
55
|
+
* LLM 段**逐条**推进,两者粒度不同,面板必须能分辨(否则用户看到的是
|
|
58
56
|
* 一个忽快忽慢的"重标定批次")。
|
|
59
57
|
*/
|
|
60
58
|
progress?: (text: string, done: number, total: number, label?: string) => void;
|
|
61
|
-
/** LLM 段墙钟预算(毫秒);超时停止,剩余计入 deferred 留待下次反刍。默认
|
|
59
|
+
/** LLM 段墙钟预算(毫秒);超时停止,剩余计入 deferred 留待下次反刍。默认 180s(逐条形状下
|
|
60
|
+
* 约 60 条 × 2.6s 的余量)。 */
|
|
62
61
|
timeBudgetMs?: number;
|
|
63
62
|
}
|
|
64
63
|
export declare function relabelPass(deps: RelabelDeps, overrides?: RelabelOverrides, opts?: RelabelOpts): Promise<RelabelStats>;
|