@logictan/dsh-verbatim-compaction 0.2.0 → 0.2.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/README.md +11 -3
- package/lib/index.js +30 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,11 +51,19 @@
|
|
|
51
51
|
- **工具调用从 `assistant/message` 的 block 里取**。日志里没有独立的 `tool/call` 事件类型,按它过滤会一条都取不到。
|
|
52
52
|
- **提取失败就退回纯逐字摘要**。模型不可达、超时、输出为空都返回 null;压缩本身不能因此失败,否则会话死于上下文溢出,比丢细节更糟。
|
|
53
53
|
|
|
54
|
-
代价是每轮一次模型调用(实测 13 轮合计约 74649
|
|
54
|
+
代价是每轮一次模型调用(实测 13 轮合计约 74649 字符材料)。提取块放在摘要**最前面**,与逐字摘要共用同一个预算:截断切的是尾部,因此预算紧时先削正文、保住结论。反过来放(曾经如此)会让「本轮得出了什么」成为第一个被删的东西。
|
|
55
55
|
|
|
56
56
|
## 为什么不会触发 shrink 守卫
|
|
57
57
|
|
|
58
|
-
基类的守卫比较「framed 摘要」与「被替换内容的 route
|
|
58
|
+
基类的守卫比较「framed 摘要」与「被替换内容的 route 价」,摘要不小于原内容就整次压缩失败。本插件把预算算成 `min(shadowedPrice × 0.5, shadowedPrice − 86 − 512)`,因此这条失败路径按构造不可能发生,而不是靠实测侥幸。
|
|
59
|
+
|
|
60
|
+
## 为什么会收敛(一条真实事故)
|
|
61
|
+
|
|
62
|
+
只满足守卫**不等于**收敛。守卫只要求摘要比被替换的内容小,一个占到被替换量 99.3% 的摘要照样通过守卫,而 surface 几乎没变。
|
|
63
|
+
|
|
64
|
+
会话 `小鹏汽车 App 改造评估` 上真实发生过:一次压缩 shadowed 87733 token,产出摘要 priced 87135——只回收 598 token(0.7%)。压缩后 surface 实测 **256704** token,阈值 **256464**,刚好卡在阈值上方,于是下一步立刻又压;此时 surface 上除 checkpoint 外没别的东西,第二次只 shadowed **1 个 seq**(就是刚写的那条 checkpoint),把同样的 34.8 万字符原样重抄一遍(346555 vs 348539),第三次直接 abort。
|
|
65
|
+
|
|
66
|
+
预算改成被替换量的一半后,同一份日志实测:257696 → **188120** token(0.73× 阈值),一轮就落到阈值以下。只压一条 checkpoint 时摘要也减半(279048 → 139865 字符),因此是几何衰减而非固定点。
|
|
59
67
|
|
|
60
68
|
## 安装
|
|
61
69
|
|
|
@@ -107,7 +115,7 @@ pnpm add @logictan/dsh-verbatim-compaction
|
|
|
107
115
|
|
|
108
116
|
## 已知边界
|
|
109
117
|
|
|
110
|
-
- **正文超预算会被截断。** 预算是硬的,两级依次触发:**先** user 正文超过预算一半时从**最新**往前算、最旧的先丢(用户刚说的话是下一轮唯一依赖的东西,丢它等于丢任务);**仍不够**才把助手正文按轮均分截断,每轮下限 800
|
|
118
|
+
- **正文超预算会被截断。** 预算是硬的,两级依次触发:**先** user 正文超过预算一半时从**最新**往前算、最旧的先丢(用户刚说的话是下一轮唯一依赖的东西,丢它等于丢任务);**仍不够**才把助手正文按轮均分截断,每轮下限 800 字符。在 122 轮、助手正文 31.6 万字符的真实会话上,第一级触发 104 处、第二级没触发——长会话里这是常态而不是守卫。
|
|
111
119
|
- **`turn/start` 缺失时轮次编号退化为 `turn ?`。** 不会丢正文,只是编号不可读。
|
|
112
120
|
- **注入内容不进摘要。** 判据是 `source.kind !== 'user'`——注入上下文在一个真实会话里占 user 消息字符的 97%,放进摘要会把人的原话淹掉。
|
|
113
121
|
- **不改善首轮之外的工具参数细节。** `bash` 命令本身(占工具参数 84.2%)不保留;需要「当时跑了什么命令」的场景不在本插件的覆盖范围内。
|
package/lib/index.js
CHANGED
|
@@ -34,14 +34,22 @@ import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
|
|
|
34
34
|
/** Cordis companion plugin name. Must equal this plugin's `cordis.patch.yml` row id. */
|
|
35
35
|
export const name = 'verbatim-compaction';
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
37
|
+
* Largest share of the shadowed price one digest may occupy.
|
|
38
|
+
*
|
|
39
|
+
* Satisfying the shrink guard is not convergence. The guard only requires the
|
|
40
|
+
* framed digest to be *smaller* than what it replaces; a digest priced at 99.3%
|
|
41
|
+
* of its span passes the guard and still leaves the surface where it started.
|
|
42
|
+
* Measured on session `小鹏汽车 App 改造评估`: one compaction shadowed 87733
|
|
43
|
+
* tokens and produced a digest priced at 87135 — 598 tokens (0.7%) reclaimed —
|
|
44
|
+
* and the resulting surface measured 256704 tokens against a 256464 threshold,
|
|
45
|
+
* so the very next step compacted again, this time shadowing exactly one seq
|
|
46
|
+
* (the checkpoint it had just written) and reproducing the same 348k characters
|
|
47
|
+
* until the third attempt was aborted.
|
|
48
|
+
*
|
|
49
|
+
* At half, each compaction makes progress however much history has accumulated,
|
|
50
|
+
* so one pass drops the surface well below the threshold.
|
|
43
51
|
*/
|
|
44
|
-
const
|
|
52
|
+
const DIGEST_SHARE_OF_SHADOWED = 0.5;
|
|
45
53
|
/** Framing overhead `frameSummary` adds: preamble plus both tags, 341 chars = 86 tokens. */
|
|
46
54
|
const FRAME_TOKENS = 86;
|
|
47
55
|
/** Safety margin below the shrink guard, in tokens. */
|
|
@@ -131,6 +139,8 @@ export function isErrorResult(event) {
|
|
|
131
139
|
const message = messageOf(event);
|
|
132
140
|
return message?.isError === true;
|
|
133
141
|
}
|
|
142
|
+
/** Heading that introduces the extracted section at the top of a digest. */
|
|
143
|
+
const EXTRACTED_HEADING = '# Extracted from tool traces';
|
|
134
144
|
/** Directive appended as the final user message of the extraction call. */
|
|
135
145
|
const EXTRACT_INSTRUCTION = [
|
|
136
146
|
'You are summarizing ONE turn of a coding session for a compaction checkpoint.',
|
|
@@ -633,16 +643,23 @@ export default class VerbatimCompactionEngine extends BasicCompactionEngine {
|
|
|
633
643
|
if (shadowedSet.has(node.seq))
|
|
634
644
|
shadowedPrice += node.tokens;
|
|
635
645
|
}
|
|
636
|
-
const budget = Math.max(0, Math.min(
|
|
646
|
+
const budget = Math.max(0, Math.min(
|
|
647
|
+
// Half the span is the convergence contract, not the guard's own bound.
|
|
648
|
+
// See `DIGEST_SHARE_OF_SHADOWED` for the 87733→87135 measurement.
|
|
649
|
+
Math.floor(shadowedPrice * DIGEST_SHARE_OF_SHADOWED), shadowedPrice - FRAME_TOKENS - SHRINK_MARGIN_TOKENS));
|
|
637
650
|
const verbatim = renderDigest(source, budget);
|
|
638
|
-
// Per-turn extraction,
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
651
|
+
// Per-turn extraction, prepended rather than appended.
|
|
652
|
+
//
|
|
653
|
+
// Order matters under truncation: `slice` cuts the tail, so appending the
|
|
654
|
+
// extraction put the most reconstructible content first and the least
|
|
655
|
+
// reconstructible content last — a tight budget deleted exactly the "what
|
|
656
|
+
// did this turn conclude / what did it overturn" record and kept the prose
|
|
657
|
+
// it summarised. Leading with the extraction makes the digest degrade the
|
|
658
|
+
// way the plan intends: conclusions survive, prose thins.
|
|
642
659
|
const extracted = await this.extractAll(source, agent, _signal);
|
|
643
660
|
const text = extracted.length === 0
|
|
644
661
|
? verbatim
|
|
645
|
-
: `${
|
|
662
|
+
: `${EXTRACTED_HEADING}\n${extracted}\n\n${verbatim}`.slice(0, Math.max(verbatim.length, budget * 4));
|
|
646
663
|
const route = session.requestHeader()?.config;
|
|
647
664
|
return {
|
|
648
665
|
summary: [{ type: 'text', text }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logictan/dsh-verbatim-compaction",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Deterministic compaction backend that keeps conversation prose verbatim in the checkpoint instead of re-summarizing it: user and assistant text survive word for word, tool results collapse to one-line pointers, reasoning is never read, and no model call is made.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|