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
package/README.en.md
CHANGED
|
@@ -103,6 +103,15 @@ long-lived facts never sink); tune via `recall.decayHalfLifeDays`, `0` disables.
|
|
|
103
103
|
also registers three model-callable memory tools: `memory_search` /
|
|
104
104
|
`conversation_search` / `memory_read_scene`.
|
|
105
105
|
|
|
106
|
+
**Cost dashboard**: every distillation LLM call (extract / dedup / L2 / L3) writes its
|
|
107
|
+
token cost to a SQLite detail table keyed by `provider/model` (configurable retention,
|
|
108
|
+
default 365 days with rolling cleanup on write; accounting failures only log a warning
|
|
109
|
+
and never block distillation). Visualize it under Settings → Memory → the **Cost** tab:
|
|
110
|
+
per-model trend lines (day/week/month granularity + last-N-days window + L1/L2/L3 layer
|
|
111
|
+
filter), a layer × time-window table (calls / output & reasoning tokens / mean / median),
|
|
112
|
+
and per-model totals — distillation overhead at a glance. Input is counted in characters
|
|
113
|
+
(DSH streaming usage carries no input tokens); output and reasoning in tokens.
|
|
114
|
+
|
|
106
115
|
In action: the "Context injection · memory" row surfaces relevant memories first, and
|
|
107
116
|
the model then calls `memory_read_scene` directly to read scene blocks before answering
|
|
108
117
|
from memory:
|
|
@@ -221,8 +230,8 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
|
|
|
221
230
|
config: # keys replace whole lines (no deep merge); write out all keys you want to keep
|
|
222
231
|
family: auto # default mode for new sessions: auto | chat | work
|
|
223
232
|
llm: # static distillation route (both fields set = deployment pin,
|
|
224
|
-
provider: '' # which outranks the settings-page
|
|
225
|
-
model: '' # follows the
|
|
233
|
+
provider: '' # which outranks the settings-page route chain; when empty the route
|
|
234
|
+
model: '' # follows the route-chain primary row in the settings page or the default model)
|
|
226
235
|
```
|
|
227
236
|
|
|
228
237
|
| Field | Default | Description |
|
|
@@ -263,15 +272,39 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
|
|
|
263
272
|
| `embedding.allowLocalModels` | `true` | Allow the local embedding tier (deployment ceiling; when off, no model downloads and no local tier in settings) |
|
|
264
273
|
| `embedding.mirror` | `https://hf-mirror.com` | Download mirror root for local models (can be changed back to `https://huggingface.co`) |
|
|
265
274
|
| `embedding.proxy` | `''` | Three-state download proxy: `''` (default) = auto-detect proxy env vars (`HTTPS_PROXY`/`ALL_PROXY` etc., honoring `NO_PROXY`); `none` = disable, always direct; any other value = proxy URL (e.g. `http://127.0.0.1:7890`). Direct connections to the mirror are intermittently unreachable on some networks (connect timeouts and poisoned bytes have both been observed) — keep the default auto-detection on machines with a proxy |
|
|
266
|
-
| `llm.provider/model` | empty | Static distillation route (deployment pin): when **both** fields are set the route is locked, outranking the settings-page
|
|
275
|
+
| `llm.provider/model` | empty | Static distillation route (deployment pin): when **both** fields are set the route is locked, outranking the settings-page runtime route chain and the default model (deployments can force distillation onto a specific route); when empty the route follows "settings-page route-chain primary → default model". At runtime, configure the primary route and fallback chain in the **route-chain editor** under Settings → Memory → Overview → distillation parameters (pick from **configured providers**, including custom ones added in dsh Settings → Models; the primary row may stay empty to follow the default model) — a non-empty chain takes over this static config wholesale, effective immediately with no restart |
|
|
276
|
+
| `llm.fallbacks` | `[]` | Distillation fallback chain: an ordered list of backup routes tried one by one when the primary route fails (error / cut-off / network error / **empty output**); each entry is `{provider, model, reasoningEffort?}` (a non-empty effort overrides the global `llm.reasoningEffort`, still clamped by model capability); entries identical to the primary route are skipped; **each route gets the full `timeoutMs`**; when all routes fail, the existing per-session backoff takes over. Empty list (default) = single-route behavior unchanged (see [Distillation fallback chain & slow-TTFT models](#distillation-fallback-chain--slow-ttft-models) below); a non-empty settings-page runtime chain (`distillChain`) takes over **both** the primary route and the fallback chain (a single-row chain = explicitly no fallbacks), empty = follow this config |
|
|
267
277
|
| `llm.maxTokens` | `65536` | Fallback output cap for non-layered calls. Each distillation stage has its own budget (extraction 16k / dedup 8k / L2 32k / L3 16k; auto ×4 when the reasoning effort is high/xhigh/max, so thinking can't starve the text budget); the per-layer budgets are runtime-adjustable in Settings → Memory → Overview → distillation parameters (empty/0 = built-in defaults) |
|
|
268
|
-
| `llm.reasoningEffort` | empty | Distillation reasoning effort: empty = **auto** (resolved from model capability: the model's default tier, else `high`); an explicit value (`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) is only sent when the model declares support — effort vocabularies differ across providers (deepseek accepts `off`, OpenAI-style APIs use `none`, models that declare no tiers get nothing), and unsupported tiers degrade to not-sending with a one-time warning; output budgets auto-×4 at high/xhigh/max.
|
|
278
|
+
| `llm.reasoningEffort` | empty | Distillation reasoning effort: empty = **auto** (resolved from model capability: the model's default tier, else `high`); an explicit value (`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) is only sent when the model declares support — effort vocabularies differ across providers (deepseek accepts `off`, OpenAI-style APIs use `none`, models that declare no tiers get nothing), and unsupported tiers degrade to not-sending with a one-time warning; output budgets auto-×4 at high/xhigh/max. At runtime, override the effort **per route** in the settings-page route-chain editor (per-row dropdown; the tier list follows each model's declared capability live, defaulting to this value) |
|
|
269
279
|
| `llm.temperature` | `0.3` | Distillation temperature |
|
|
270
280
|
| `llm.maxInputChars` | `700000` | Input character budget per distillation call (over-budget L1 inputs are chunked automatically); runtime-adjustable in Settings → distillation parameters → input budget (empty/0 = follow this value) |
|
|
271
281
|
| `llm.timeoutMs` | `120000` | Per-call distillation timeout (ms) |
|
|
282
|
+
| `tokenCost.retentionDays` | `365` | Retention (days) for distillation cost details (the `token_cost` table); rows older than this are rolled away on write. `0` = keep forever. Also the upper bound of the cost dashboard's "last N days" window |
|
|
272
283
|
| `tools` | `true` | Whether to register model-callable memory tools |
|
|
273
284
|
| `benchControl` | `false` | Register the in-process bench control service (rebuild trigger / session-mode setting / distillation usage snapshot — used by the benchmark's lifecycle track). Off by default — zero surface in production deployments; do not enable casually |
|
|
274
285
|
|
|
286
|
+
### Distillation fallback chain & slow-TTFT models
|
|
287
|
+
|
|
288
|
+
Free/slow tiers of some inference providers have **first-token latencies (TTFT) upwards of 20 seconds**, while some upstream gateways cut a silent connection at ~20s — distillation calls then fail at a fixed ~20s (`llm aborted`) long before the plugin's 120s timeout could ever matter (the scenario measured in [#31](https://github.com/JunNanLYS/dsh-layered-memory/issues/31)). Three mitigations, pick as needed:
|
|
289
|
+
|
|
290
|
+
1. **Switch route** (most direct): change the primary route live in the route-chain editor under Settings → Memory → Overview → distillation parameters (or move a fast route to the head of the chain), or pin `llm.provider`/`llm.model` statically.
|
|
291
|
+
2. **Fallback chain** (automatic demotion): when the primary route fails, backup routes are tried in order with no manual intervention:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
llm:
|
|
295
|
+
provider: opencode-go # primary route (may be left unpinned: settings-page route-chain primary / default model)
|
|
296
|
+
model: ox-alpha-free
|
|
297
|
+
fallbacks: # entry order = demotion priority; unset = single-route behavior unchanged
|
|
298
|
+
- provider: opencode-go
|
|
299
|
+
model: deepseek-v4-flash
|
|
300
|
+
reasoningEffort: low # optional: per-route effort override (defaults to the global value)
|
|
301
|
+
- provider: deepseek-official
|
|
302
|
+
model: deepseek-v4-flash
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Failure = error / cut-off / network error / **empty output** (stream ends normally with 0 characters — worthless for distillation since parsing always fails, so it is treated as a route failure rather than an empty return); caller-initiated cancellation does not demote; each route gets the **full** `llm.timeoutMs` (a shared budget would give a slow-TTFT fallback route less time than its real first-packet needs, defeating the chain); token costs are recorded per attempt (failed attempts get a row too, with whatever tokens arrived before the stream broke), and successful calls are attributed to the route that actually served. The route chain can also be adjusted at runtime in the route-chain editor under Settings → Memory → Overview → distillation parameters (no config edit or restart needed); the YAML below suits deployments that want to pin the static chain.
|
|
306
|
+
3. **Raise the timeout**: `llm.timeoutMs` only helps when the route is genuinely slow but the gateway doesn't cut; if the gateway kills at 20s, raising the plugin timeout is futile — use the first two layers.
|
|
307
|
+
|
|
275
308
|
## Storage Layout
|
|
276
309
|
|
|
277
310
|
<p align="center">
|
package/README.md
CHANGED
|
@@ -76,6 +76,13 @@ npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution no
|
|
|
76
76
|
|
|
77
77
|
插件挂在 dsh 原生事件上(`session/event` 捕获、`agent/pre-step` 注入),蒸馏调用复用宿主 `ctx.llm`。召回以**消息侧注入**呈现:相关记忆作为一条合成消息排在用户新消息之前,会话流里显示为**"上下文注入 · memory"**行(点开看命中内容)——用户能直接看到"记忆生效了";注入内容有长度预算与时间预算,超限截断/超时跳过,绝不拖慢对话。**同会话去重**:已注入过的记忆不再重复注入(模型上下文里已经有了,追问同类问题时省 token);上下文被 `/compact` 压缩或清空时自动重置,记忆可重新注入;被更新的记忆(内容变化换新 id)不受旧压制。**时效加权**:召回排序按 `相关度 × max(0.5, 0.5^(距上次更新天数/30))` 软加权——相关度相近的候选之间新鲜记忆优先(名额自然轮转),相关度足够高的老记忆照常召回(地板保证最多损失一半排序分,长期事实不沉底);`recall.decayHalfLifeDays` 可调,0=关闭。
|
|
78
78
|
|
|
79
|
+
**成本看板**:每次蒸馏 LLM 调用(抽取/去重/L2/L3)的 token 成本按 `provider/model` 写入
|
|
80
|
+
SQLite 明细表(保留期可配置,默认 365 天,写入时滚动清理;记账失败只告警、绝不阻塞蒸馏),
|
|
81
|
+
设置页 → 记忆 → **成本** Tab 可视化:按模型分色的趋势折线(日/周/月粒度 + 近 N 天窗口 +
|
|
82
|
+
L1/L2/L3 层级过滤)、层级 × 时间窗口表格(调用数 / 输出与思考 token / 均值 / 中位数)、
|
|
83
|
+
按模型累计——蒸馏开销一目了然。输入按字符计(dsh 流式 usage 不含输入 token),
|
|
84
|
+
输出与思考按 token 计。
|
|
85
|
+
|
|
79
86
|
**记忆工具(3):**
|
|
80
87
|
- memory_search
|
|
81
88
|
- conversation_search
|
|
@@ -225,8 +232,8 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
|
|
|
225
232
|
name: dsh-layered-memory
|
|
226
233
|
config: # 键按行整体替换(不深合并),按需写全要保留的键
|
|
227
234
|
family: auto # 新会话默认档:auto | chat | work
|
|
228
|
-
llm: # 蒸馏模型静态路由(双字段齐 = 部署 pin
|
|
229
|
-
provider: '' #
|
|
235
|
+
llm: # 蒸馏模型静态路由(双字段齐 = 部署 pin,优先于设置页路由链;
|
|
236
|
+
provider: '' # 留空则跟随设置页路由链主路由或当前默认模型)
|
|
230
237
|
model: ''
|
|
231
238
|
```
|
|
232
239
|
|
|
@@ -268,15 +275,39 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
|
|
|
268
275
|
| `embedding.allowLocalModels` | `true` | 允许本地嵌入档(部署上限:关闭后设置页不能下载模型、不能切本地档) |
|
|
269
276
|
| `embedding.mirror` | `https://hf-mirror.com` | 本地模型下载镜像根地址(可改回官方 `https://huggingface.co`) |
|
|
270
277
|
| `embedding.proxy` | `''` | 模型下载代理三态:`''`(默认)= 自动探测代理环境变量(`HTTPS_PROXY`/`ALL_PROXY` 等,尊重 `NO_PROXY`);`none` = 禁用强制直连;其他值 = 代理 URL(如 `http://127.0.0.1:7890`)。镜像直连在国内网络间歇不可达(直连超时与污染字节交替出现过),开代理的机器建议保持默认自动探测 |
|
|
271
|
-
| `llm.provider/model` | 空 | 蒸馏模型静态路由(部署 pin):provider 与 model
|
|
278
|
+
| `llm.provider/model` | 空 | 蒸馏模型静态路由(部署 pin):provider 与 model **双字段齐**时锁定蒸馏路由,优先于设置页的运行时路由链与默认模型(部署可强制蒸馏走指定路由);留空则跟随"设置页路由链主路由 → 默认模型"。运行时可在设置页 → 记忆 → 概览 → 蒸馏参数的**蒸馏路由链编辑器**里配置主路由与回退链(从**已配置的供应商**(含 dsh 设置 → 模型里添加的自定义供应商)中选择,主路由行可留空跟随默认模型),非空即整体接管本静态配置,即时生效无需重启 |
|
|
279
|
+
| `llm.fallbacks` | `[]` | 蒸馏回退链:主路由失败(报错/被掐断/网络异常/**空输出**)后按条目顺序逐个降级尝试的备用路由列表,条目 = `{provider, model, reasoningEffort?}`(档位非空覆盖全局 `llm.reasoningEffort`,仍按模型能力钳制);与主路由完全相同的条目自动跳过;**每条路由各享全额 `timeoutMs`**;全部失败交既有按会话退避重试。空数组(缺省)= 单路由行为不变(详见下方[蒸馏回退链与慢 TTFT 模型](#蒸馏回退链与慢-ttft-模型));设置页运行时路由链(`distillChain`)非空时**整体接管**主路由与回退链(单行链 = 显式无回退),空 = 跟随本配置 |
|
|
272
280
|
| `llm.maxTokens` | `65536` | 未分层调用的兜底输出总闸。各蒸馏层有独立预算(抽取 16k / 去重 8k / L2 32k / L3 16k;思考档 high/xhigh/max 时自动 ×4,防 reasoning 吃光预算),分层预算可在设置页 → 记忆 → 概览 → 蒸馏参数运行时调整(留空/0 = 跟随内置默认) |
|
|
273
|
-
| `llm.reasoningEffort` | 空 | 蒸馏思考档位:空串 = **自动**(按模型能力解析:模型默认档 → `high`);显式值(`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`)仅在该模型声明支持时发送——跨供应商 effort 词汇表不同(deepseek 认 `off`,OpenAI 系是 `none`,未声明档位的模型不传),不支持的档位自动降级为不传并告警一次;思考档 high/xhigh/max 时输出预算自动 ×4
|
|
281
|
+
| `llm.reasoningEffort` | 空 | 蒸馏思考档位:空串 = **自动**(按模型能力解析:模型默认档 → `high`);显式值(`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`)仅在该模型声明支持时发送——跨供应商 effort 词汇表不同(deepseek 认 `off`,OpenAI 系是 `none`,未声明档位的模型不传),不支持的档位自动降级为不传并告警一次;思考档 high/xhigh/max 时输出预算自动 ×4。运行时可在设置页路由链编辑器里**逐路由**覆盖档位(行内下拉,词表按各模型声明的能力实时显示,缺省跟随本值) |
|
|
274
282
|
| `llm.temperature` | `0.3` | 蒸馏温度 |
|
|
275
283
|
| `llm.maxInputChars` | `700000` | 单次蒸馏输入字符预算(超限的 L1 输入自动分块抽取);运行时可在设置页 → 蒸馏参数 → 输入预算调整(留空/0 = 跟随本值) |
|
|
276
284
|
| `llm.timeoutMs` | `120000` | 单次蒸馏调用超时(ms) |
|
|
285
|
+
| `tokenCost.retentionDays` | `365` | 蒸馏成本明细(token_cost 表)保留天数,写入时滚动清理更早行;`0` = 永久保留。成本看板「近 N 天」窗口上限同此值 |
|
|
277
286
|
| `tools` | `true` | 是否注册模型可调用的记忆工具 |
|
|
278
287
|
| `benchControl` | `false` | 注册 bench 控制服务(进程内 rebuild 触发/会话档位设置/蒸馏用量快照,供基准 lifecycle 赛道)。默认关——生产部署零表面积,勿随意开启 |
|
|
279
288
|
|
|
289
|
+
### 蒸馏回退链与慢 TTFT 模型
|
|
290
|
+
|
|
291
|
+
部分推理供应商的免费/慢速档位**首 token 延迟(TTFT)可达 20 秒以上**,而部分上游网关会在连接静默约 20 秒时掐断——蒸馏调用以固定 ~20s 失败(`llm aborted`),插件侧 120s 超时根本轮不到生效([#31](https://github.com/JunNanLYS/dsh-layered-memory/issues/31) 的实测场景)。三层缓解按需取用:
|
|
292
|
+
|
|
293
|
+
1. **换路由**(最直接):设置页 → 记忆 → 概览 → 蒸馏参数的路由链编辑器即时改主路由(或把快路由排到链首),或静态 pin `llm.provider`/`llm.model`。
|
|
294
|
+
2. **回退链**(自动降级):主路由失败时按序自动换备用路由,无需人工干预:
|
|
295
|
+
|
|
296
|
+
```yaml
|
|
297
|
+
llm:
|
|
298
|
+
provider: opencode-go # 主路由(也可不 pin,跟随设置页路由链主路由/默认模型)
|
|
299
|
+
model: ox-alpha-free
|
|
300
|
+
fallbacks: # 条目顺序 = 降级优先级;不配置 = 单路由行为不变
|
|
301
|
+
- provider: opencode-go
|
|
302
|
+
model: deepseek-v4-flash
|
|
303
|
+
reasoningEffort: low # 可选:该路由的档位覆盖(缺省跟随全局)
|
|
304
|
+
- provider: deepseek-official
|
|
305
|
+
model: deepseek-v4-flash
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
失败 = 报错 / 被掐断 / 网络异常 / **空输出**(流正常结束但 0 字符——对蒸馏而言必然在解析阶段报废,改判为该路由失败而非返回空串);调用方主动取消不降级;每条路由各享**全额** `llm.timeoutMs`(共享预算会让慢 TTFT 的回退路由拿到的窗口小于它真实需要的首包时间,回退链形同虚设);token 成本逐次尝试记账(失败尝试也计一行,含流中断前已到的 token),成功调用归因到实际服务的路由。路由链也可在设置页 → 记忆 → 概览 → 蒸馏参数的「蒸馏路由链」编辑器里运行时调整(无需改配置重启);本 YAML 适合部署者固化静态链。
|
|
309
|
+
3. **调高超时**:`llm.timeoutMs` 只在路由确实慢但网关不掐时有用;网关 20s 掐断的场景调插件超时无效,请用前两层。
|
|
310
|
+
|
|
280
311
|
## 日志与故障排查
|
|
281
312
|
|
|
282
313
|
dsh 宿主把插件日志打到控制台;插件另把 info 级以上镜像到数据目录的 `memory.log`。
|