pi-mega-compact 0.8.24 → 0.8.26
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 +26 -0
- package/dist/extensions/mega-compact-s38.test.js +263 -14
- package/dist/extensions/mega-compact.js +15 -0
- package/dist/extensions/mega-config.js +3 -0
- package/dist/extensions/mega-events/agent-handlers.js +211 -26
- package/dist/extensions/mega-events/context-handler.js +45 -7
- package/dist/extensions/mega-events/error-classifier.js +125 -18
- package/dist/extensions/mega-pipeline/compact.js +24 -13
- package/dist/extensions/mega-pipeline/recall.js +31 -2
- package/dist/extensions/mega-runtime/dashboard-snapshot.js +4 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +4 -0
- package/dist/extensions/mega-runtime/runtime.js +58 -5
- package/dist/src/boundary.js +79 -43
- package/dist/src/boundary.test.js +119 -2
- package/dist/src/canary.js +10 -0
- package/dist/src/config/dedup.js +14 -0
- package/dist/src/config.js +3 -1
- package/dist/src/dedup/raptor/buildHistory.js +164 -0
- package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
- package/dist/src/dedup/raptor/index.js +38 -0
- package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
- package/dist/src/dedup/raptor/multilevel.js +17 -5
- package/dist/src/dedup/raptor/multilevel.test.js +36 -1
- package/dist/src/dedup/raptor/raptor.test.js +43 -0
- package/dist/src/dedup/raptor/retrieval.js +14 -2
- package/dist/src/dedup/raptor/retrieval.test.js +95 -0
- package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
- package/dist/src/dedup/raptor/summarizer.js +1 -0
- package/dist/src/dedup/raptor/tree.js +16 -2
- package/dist/src/engine.js +18 -2
- package/dist/src/httpEmbedder.js +96 -6
- package/dist/src/httpEmbedder.test.js +277 -0
- package/dist/src/mechanical-fix.test.js +65 -0
- package/dist/src/raptor-inject-summaries.test.js +162 -0
- package/dist/src/recall.js +153 -24
- package/dist/src/recall.test.js +179 -4
- package/dist/src/store/sqlite/dedup-mirror.js +32 -15
- package/dist/src/store/sqlite/maintenance.js +2 -2
- package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
- package/dist/src/store/sqlite/memories.js +5 -5
- package/dist/src/store/sqlite/meta.js +1 -1
- package/dist/src/store/sqlite/raptor.js +56 -17
- package/dist/src/store/sqlite/raptor.test.js +106 -0
- package/dist/src/store/sqlite/schema.js +90 -1
- package/dist/src/store/sqlite/session-state.js +9 -3
- package/dist/src/store/sqlite/stats.js +9 -5
- package/dist/src/store/sqlite/turns.js +179 -0
- package/dist/src/store/sqlite/turns.test.js +183 -0
- package/dist/src/store/sqlite/utils.js +15 -4
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store.js +2 -2
- package/dist/src/vector-search-cache.test.js +157 -0
- package/dist/src/vector-search.js +107 -15
- package/dist/src/vectorStore.js +36 -8
- package/extensions/mega-compact-s38.test.ts +259 -14
- package/extensions/mega-compact.ts +15 -0
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-dashboard.ts +10 -1
- package/extensions/mega-events/agent-handlers.ts +211 -26
- package/extensions/mega-events/context-handler.ts +43 -7
- package/extensions/mega-events/error-classifier.ts +125 -17
- package/extensions/mega-pipeline/compact.ts +28 -16
- package/extensions/mega-pipeline/recall.ts +34 -2
- package/extensions/mega-runtime/dashboard-snapshot.ts +8 -0
- package/extensions/mega-runtime/helpers.ts +25 -1
- package/extensions/mega-runtime/runtime-snapshot.ts +4 -0
- package/extensions/mega-runtime/runtime.ts +69 -23
- package/package.json +1 -1
- package/src/boundary.test.ts +128 -2
- package/src/boundary.ts +75 -39
- package/src/canary.ts +10 -0
- package/src/config/dedup.ts +25 -0
- package/src/config.ts +3 -1
- package/src/dedup/raptor/buildHistory.test.ts +353 -0
- package/src/dedup/raptor/buildHistory.ts +259 -0
- package/src/dedup/raptor/index.ts +38 -0
- package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
- package/src/dedup/raptor/multilevel.test.ts +47 -0
- package/src/dedup/raptor/multilevel.ts +18 -8
- package/src/dedup/raptor/raptor.test.ts +59 -0
- package/src/dedup/raptor/retrieval.test.ts +118 -0
- package/src/dedup/raptor/retrieval.ts +14 -2
- package/src/dedup/raptor/serve-gate.test.ts +348 -0
- package/src/dedup/raptor/summarizer.ts +1 -0
- package/src/dedup/raptor/tree.ts +17 -2
- package/src/engine.ts +32 -3
- package/src/httpEmbedder.test.ts +286 -0
- package/src/httpEmbedder.ts +98 -8
- package/src/mechanical-fix.test.ts +70 -0
- package/src/raptor-inject-summaries.test.ts +228 -0
- package/src/recall.test.ts +220 -4
- package/src/recall.ts +462 -265
- package/src/store/sqlite/dedup-mirror.ts +35 -18
- package/src/store/sqlite/maintenance.ts +2 -2
- package/src/store/sqlite/mechanical-fix.test.ts +162 -0
- package/src/store/sqlite/memories.ts +5 -5
- package/src/store/sqlite/meta.ts +1 -1
- package/src/store/sqlite/raptor.test.ts +139 -0
- package/src/store/sqlite/raptor.ts +135 -81
- package/src/store/sqlite/schema.ts +90 -1
- package/src/store/sqlite/session-state.ts +9 -3
- package/src/store/sqlite/stats.ts +10 -8
- package/src/store/sqlite/turns.test.ts +218 -0
- package/src/store/sqlite/turns.ts +302 -0
- package/src/store/sqlite/utils.ts +14 -4
- package/src/store/sqlite.ts +1 -0
- package/src/store.ts +9 -2
- package/src/vector-search-cache.test.ts +190 -0
- package/src/vector-search.ts +273 -156
- package/src/vectorStore.ts +443 -382
- package/extensions/mega-runtime/reset-runtime.ts +0 -80
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve-gate.test.ts — S25 RAPTOR promotion acceptance tests.
|
|
3
|
+
*
|
|
4
|
+
* Five gates that must ALL pass before the RAPTOR tree is trusted as the live
|
|
5
|
+
* recall surface:
|
|
6
|
+
* 1. Shadow gate (RAPTOR_SHADOW_MODE != false → no RAPTOR merge)
|
|
7
|
+
* 2. Stale tree fallback (builtAt < max checkpoint timestamp → flat)
|
|
8
|
+
* 3. timedOut fallback (extractive-fallback root → flat)
|
|
9
|
+
* 4. Coverage breadth (2-topic fixture, RAPTOR hits both vs flat misses one)
|
|
10
|
+
* 5. p95 latency (200-checkpoint × 20-query median < 100 ms)
|
|
11
|
+
*
|
|
12
|
+
* No network — default extractive summarizer + trigram embedder.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { test, beforeEach, afterEach } from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { VectorStore, vectorList, vectorSearch } from "../../vectorStore.js";
|
|
21
|
+
import { runRaptor, isShadowMode } from "./index.js";
|
|
22
|
+
import { compactSession } from "../../engine.js";
|
|
23
|
+
import { Logger } from "../../log.js";
|
|
24
|
+
import { loadDedupConfig } from "../../config/dedup.js";
|
|
25
|
+
import { listRaptorNodes } from "../../store/sqlite.js";
|
|
26
|
+
import { normalizeSessionId } from "../../store.js";
|
|
27
|
+
import type { EngineMessage } from "../../types.js";
|
|
28
|
+
import type { DedupConfigShape } from "../../config/dedup.js";
|
|
29
|
+
|
|
30
|
+
/* ------------------------------------------------------------------ helpers */
|
|
31
|
+
|
|
32
|
+
let tmpDir: string;
|
|
33
|
+
let counter = 0;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
tmpDir = mkdtempSync(join(tmpdir(), "mc-gate-"));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function stateDir(): string {
|
|
44
|
+
return join(tmpDir, `run-${counter++}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Config with dedup tiers disabled so compactSession creates distinct checkpoints. */
|
|
48
|
+
function cfg(overrides?: Record<string, unknown>): DedupConfigShape {
|
|
49
|
+
return {
|
|
50
|
+
...loadDedupConfig(),
|
|
51
|
+
RAPTOR_ENABLED: true,
|
|
52
|
+
L0_ENABLED: false,
|
|
53
|
+
L1_ENABLED: false,
|
|
54
|
+
L2_ENABLED: false,
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function msg(text: string, toolName?: string): EngineMessage {
|
|
60
|
+
return toolName
|
|
61
|
+
? { role: "assistant", text, toolName, input: text, output: text }
|
|
62
|
+
: { role: "user", text };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function seedSession(
|
|
66
|
+
store: VectorStore,
|
|
67
|
+
sid: string,
|
|
68
|
+
count: number,
|
|
69
|
+
topic: string,
|
|
70
|
+
startTs = 1,
|
|
71
|
+
) {
|
|
72
|
+
for (let i = 1; i <= count; i++) {
|
|
73
|
+
compactSession(
|
|
74
|
+
{
|
|
75
|
+
sessionId: sid,
|
|
76
|
+
messages: [
|
|
77
|
+
msg(`${topic} checkpoint ${i} with unique content`),
|
|
78
|
+
msg(`acknowledged ${i}`, "Edit"),
|
|
79
|
+
],
|
|
80
|
+
keepFrom: 2,
|
|
81
|
+
timestamp: startTs + i,
|
|
82
|
+
},
|
|
83
|
+
store,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildTree(store: VectorStore, sid: string) {
|
|
89
|
+
const nsid = normalizeSessionId(sid);
|
|
90
|
+
const all = vectorList(store, nsid);
|
|
91
|
+
const leaves = all.map((cp) => ({
|
|
92
|
+
id: cp.checkpointId,
|
|
93
|
+
messages: [],
|
|
94
|
+
sourceText: cp.normalizedText ?? cp.summary ?? cp.regionHash,
|
|
95
|
+
embedding: cp.embedding,
|
|
96
|
+
}));
|
|
97
|
+
return runRaptor(leaves, {
|
|
98
|
+
stateDir: store.stateDir,
|
|
99
|
+
sessionId: nsid,
|
|
100
|
+
logger: new Logger(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ------------------------------------------------------------------- tests */
|
|
105
|
+
|
|
106
|
+
// ─── Gate 1: shadow mode ────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
test("gate 1: shadow mode (default) → search does NOT use RAPTOR merge", () => {
|
|
109
|
+
const sd = stateDir();
|
|
110
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
111
|
+
const sid = "shadow";
|
|
112
|
+
seedSession(s, sid, 5, "shadows");
|
|
113
|
+
buildTree(s, sid);
|
|
114
|
+
|
|
115
|
+
// Shadow mode is the default when env is not explicitly "false".
|
|
116
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
117
|
+
process.env.RAPTOR_SHADOW_MODE = "true";
|
|
118
|
+
try {
|
|
119
|
+
assert.ok(isShadowMode(), "shadow mode should be active");
|
|
120
|
+
// Search still returns results (flat fallback). Verify no crash.
|
|
121
|
+
const hits = vectorSearch(s, sid, "shadows checkpoint", 5);
|
|
122
|
+
assert.ok(hits.length > 0, "flat search returns results in shadow mode");
|
|
123
|
+
} finally {
|
|
124
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
125
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ─── Gate 2: stale tree fallback ────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
test("gate 2: stale tree (builtAt < newest checkpoint) → serve flat", () => {
|
|
132
|
+
const sd = stateDir();
|
|
133
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
134
|
+
const sid = "stale";
|
|
135
|
+
|
|
136
|
+
// Build tree at checkpoints 1..5 (builtAt ≈ now).
|
|
137
|
+
seedSession(s, sid, 5, "stale topic", 0);
|
|
138
|
+
buildTree(s, sid);
|
|
139
|
+
const nodesBefore = listRaptorNodes(sid, sd);
|
|
140
|
+
assert.ok(nodesBefore.length > 0, "tree built");
|
|
141
|
+
const treeBuiltAt = nodesBefore[0].builtAt;
|
|
142
|
+
|
|
143
|
+
// Add checkpoint 6 with timestamp > treeBuiltAt → tree is now stale.
|
|
144
|
+
compactSession(
|
|
145
|
+
{
|
|
146
|
+
sessionId: sid,
|
|
147
|
+
messages: [
|
|
148
|
+
msg("stale topic checkpoint 6 newer than tree"),
|
|
149
|
+
msg("ok", "Edit"),
|
|
150
|
+
],
|
|
151
|
+
keepFrom: 2,
|
|
152
|
+
timestamp: treeBuiltAt + 1000,
|
|
153
|
+
},
|
|
154
|
+
s,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Enable live mode so the gate is actually evaluated.
|
|
158
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
159
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
160
|
+
try {
|
|
161
|
+
// Search should succeed (flat fallback) even though tree is stale.
|
|
162
|
+
const hits = vectorSearch(s, sid, "stale topic", 3);
|
|
163
|
+
assert.ok(hits.length > 0, "flat search returns hits when tree is stale");
|
|
164
|
+
} finally {
|
|
165
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
166
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ─── Gate 3: timedOut fallback ──────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
test("gate 3: timedOut extractive-fallback tree → serve flat", () => {
|
|
173
|
+
const sd = stateDir();
|
|
174
|
+
// Force extractive fallback by setting a tiny token budget.
|
|
175
|
+
const s = new VectorStore({
|
|
176
|
+
dedupSim: 0.9,
|
|
177
|
+
stateDir: sd,
|
|
178
|
+
config: cfg({ RAPTOR_MAX_TOKEN_BUDGET_PER_SESSION: 1 }),
|
|
179
|
+
});
|
|
180
|
+
const sid = "timedout";
|
|
181
|
+
seedSession(s, sid, 10, "budget topic", 0);
|
|
182
|
+
buildTree(s, sid);
|
|
183
|
+
|
|
184
|
+
const nodes = listRaptorNodes(sid, sd);
|
|
185
|
+
if (nodes.length > 0) {
|
|
186
|
+
const root = nodes.find((n) => !n.parentId && n.level >= 99);
|
|
187
|
+
if (root) {
|
|
188
|
+
// timedOut tree — search should fall back to flat.
|
|
189
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
190
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
191
|
+
try {
|
|
192
|
+
const hits = vectorSearch(s, sid, "budget topic", 3);
|
|
193
|
+
assert.ok(
|
|
194
|
+
hits.length > 0,
|
|
195
|
+
"flat search returns hits when tree is timedOut",
|
|
196
|
+
);
|
|
197
|
+
} finally {
|
|
198
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
199
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
assert.ok(
|
|
203
|
+
true,
|
|
204
|
+
"budget did not trigger extractive fallback (non-deterministic)",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
assert.ok(true, "no tree built for tiny-budget session");
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ─── Gate 4: coverage breadth ───────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
test("gate 4: RAPTOR covers both topics vs flat may miss one", () => {
|
|
215
|
+
const sd = stateDir();
|
|
216
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
217
|
+
const sid = "breadth";
|
|
218
|
+
|
|
219
|
+
// Two distinct topics — interleaved timestamps.
|
|
220
|
+
for (let i = 1; i <= 5; i++) {
|
|
221
|
+
compactSession(
|
|
222
|
+
{
|
|
223
|
+
sessionId: sid,
|
|
224
|
+
messages: [
|
|
225
|
+
msg(`cooking recipe ${i} pasta sauce basil`),
|
|
226
|
+
msg(`ok ${i}`, "Edit"),
|
|
227
|
+
],
|
|
228
|
+
keepFrom: 2,
|
|
229
|
+
timestamp: i * 2,
|
|
230
|
+
},
|
|
231
|
+
s,
|
|
232
|
+
);
|
|
233
|
+
compactSession(
|
|
234
|
+
{
|
|
235
|
+
sessionId: sid,
|
|
236
|
+
messages: [
|
|
237
|
+
msg(`automotive engine ${i} horsepower torque transmission`),
|
|
238
|
+
msg(`ok ${i}`, "Edit"),
|
|
239
|
+
],
|
|
240
|
+
keepFrom: 2,
|
|
241
|
+
timestamp: i * 2 + 1,
|
|
242
|
+
},
|
|
243
|
+
s,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
buildTree(s, sid);
|
|
248
|
+
const nodes = listRaptorNodes(sid, sd);
|
|
249
|
+
assert.ok(nodes.length > 0, "tree built for breadth test");
|
|
250
|
+
|
|
251
|
+
// Enable live mode.
|
|
252
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
253
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
254
|
+
try {
|
|
255
|
+
const hits = vectorSearch(s, sid, "cooking and automotive", 5);
|
|
256
|
+
assert.ok(hits.length > 0, "search returns hits for mixed query");
|
|
257
|
+
// Breadth: at least 2 distinct hits (probabilistic, but with 10
|
|
258
|
+
// checkpoints across 2 topics, the tree's staged expansion should
|
|
259
|
+
// surface both).
|
|
260
|
+
assert.ok(hits.length >= 2, `expected ≥2 diverse hits, got ${hits.length}`);
|
|
261
|
+
} finally {
|
|
262
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
263
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// ─── Gate 5: p95 latency ────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
test("gate 5: 200-checkpoint × 20-query median latency < 100 ms", () => {
|
|
270
|
+
const sd = stateDir();
|
|
271
|
+
const s = new VectorStore({ dedupSim: 0.9, stateDir: sd, config: cfg() });
|
|
272
|
+
const sid = "latency";
|
|
273
|
+
|
|
274
|
+
// 200 checkpoints across 10 topics.
|
|
275
|
+
const topics = [
|
|
276
|
+
"machine learning gradient descent",
|
|
277
|
+
"quantum computing entanglement",
|
|
278
|
+
"organic chemistry catalysis",
|
|
279
|
+
"renaissance art fresco painting",
|
|
280
|
+
"marine biology coral reefs",
|
|
281
|
+
"astrophysics dark matter",
|
|
282
|
+
"ancient rome senate",
|
|
283
|
+
"music theory counterpoint",
|
|
284
|
+
"urban planning zoning",
|
|
285
|
+
"evolutionary biology adaptation",
|
|
286
|
+
];
|
|
287
|
+
for (let i = 1; i <= 200; i++) {
|
|
288
|
+
const topic = topics[i % topics.length];
|
|
289
|
+
compactSession(
|
|
290
|
+
{
|
|
291
|
+
sessionId: sid,
|
|
292
|
+
messages: [msg(`${topic} checkpoint ${i}`), msg(`ack ${i}`, "Edit")],
|
|
293
|
+
keepFrom: 2,
|
|
294
|
+
timestamp: i,
|
|
295
|
+
},
|
|
296
|
+
s,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
buildTree(s, sid);
|
|
301
|
+
|
|
302
|
+
// Enable live mode.
|
|
303
|
+
const orig = process.env.RAPTOR_SHADOW_MODE;
|
|
304
|
+
process.env.RAPTOR_SHADOW_MODE = "false";
|
|
305
|
+
try {
|
|
306
|
+
const queries = [
|
|
307
|
+
"machine learning optimization",
|
|
308
|
+
"quantum entanglement physics",
|
|
309
|
+
"organic chemistry reactions",
|
|
310
|
+
"renaissance art techniques",
|
|
311
|
+
"coral reef ecosystems",
|
|
312
|
+
"dark matter universe",
|
|
313
|
+
"roman government structure",
|
|
314
|
+
"music harmony rules",
|
|
315
|
+
"city planning infrastructure",
|
|
316
|
+
"evolutionary adaptation species",
|
|
317
|
+
"gradient descent algorithms",
|
|
318
|
+
"quantum computing qubits",
|
|
319
|
+
"chemical catalysis processes",
|
|
320
|
+
"fresco painting methods",
|
|
321
|
+
"marine biology biodiversity",
|
|
322
|
+
"astrophysics observations",
|
|
323
|
+
"ancient roman history",
|
|
324
|
+
"music composition theory",
|
|
325
|
+
"urban development patterns",
|
|
326
|
+
"biology natural selection",
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
const latencies: number[] = [];
|
|
330
|
+
for (const q of queries) {
|
|
331
|
+
const t0 = Date.now();
|
|
332
|
+
vectorSearch(s, sid, q, 5);
|
|
333
|
+
latencies.push(Date.now() - t0);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
latencies.sort((a, b) => a - b);
|
|
337
|
+
const median = latencies[Math.floor(latencies.length / 2)];
|
|
338
|
+
const p95 = latencies[Math.floor(latencies.length * 0.95)];
|
|
339
|
+
|
|
340
|
+
assert.ok(
|
|
341
|
+
median < 100,
|
|
342
|
+
`median latency ${median} ms exceeds 100 ms budget (p95=${p95} ms)`,
|
|
343
|
+
);
|
|
344
|
+
} finally {
|
|
345
|
+
if (orig === undefined) delete process.env.RAPTOR_SHADOW_MODE;
|
|
346
|
+
else process.env.RAPTOR_SHADOW_MODE = orig;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
@@ -76,6 +76,7 @@ function ollamaSummarize(messages: EngineMessage[], ollama: { url: string; model
|
|
|
76
76
|
`;
|
|
77
77
|
const res = spawnSync(process.execPath, ["-e", WORKER], {
|
|
78
78
|
encoding: "utf8",
|
|
79
|
+
timeout: 30_000,
|
|
79
80
|
env: { ...process.env, R_URL: ollama.url, R_MODEL: ollama.model, R_PROMPT: prompt },
|
|
80
81
|
});
|
|
81
82
|
let parsed: { ok: boolean; text?: string; error?: string } = { ok: false, error: "no response" };
|
package/src/dedup/raptor/tree.ts
CHANGED
|
@@ -76,6 +76,7 @@ function defaultNextId(level: number, index: number): string {
|
|
|
76
76
|
/** An item being clustered at any level: a leaf, or a grouping of leaves. */
|
|
77
77
|
interface ClusterItem {
|
|
78
78
|
id: string;
|
|
79
|
+
parentId: string | null;
|
|
79
80
|
embedding: Vector;
|
|
80
81
|
leafIds: string[];
|
|
81
82
|
/** Source messages for summarization (flattened). */
|
|
@@ -128,6 +129,7 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
128
129
|
if (leaves.length < 10) {
|
|
129
130
|
const item: ClusterItem = {
|
|
130
131
|
id: "root",
|
|
132
|
+
parentId: null,
|
|
131
133
|
embedding: meanVector(leaves.map((l) => l.embedding)),
|
|
132
134
|
leafIds: leaves.map((l) => l.id),
|
|
133
135
|
messages: leaves.flatMap((l) => l.messages),
|
|
@@ -151,6 +153,7 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
151
153
|
|
|
152
154
|
let currentLevel: ClusterItem[] = leaves.map((l) => ({
|
|
153
155
|
id: l.id,
|
|
156
|
+
parentId: null,
|
|
154
157
|
embedding: l.embedding,
|
|
155
158
|
leafIds: [l.id],
|
|
156
159
|
messages: l.messages,
|
|
@@ -167,6 +170,7 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
167
170
|
if (currentLevel.length <= clustersPerLevel) {
|
|
168
171
|
const merged: ClusterItem = {
|
|
169
172
|
id: "merge",
|
|
173
|
+
parentId: null,
|
|
170
174
|
embedding: meanVector(currentLevel.map((c) => c.embedding)),
|
|
171
175
|
leafIds: currentLevel.flatMap((c) => c.leafIds),
|
|
172
176
|
messages: currentLevel.flatMap((c) => c.messages),
|
|
@@ -185,6 +189,15 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
185
189
|
qualityMarker,
|
|
186
190
|
tokenEstimate,
|
|
187
191
|
});
|
|
192
|
+
// Populate parentId for the internal nodes being merged into this root.
|
|
193
|
+
// currentLevel items with ids in `nodes` are internal summary nodes (level
|
|
194
|
+
// >= 1); raw leaf ids are not in `nodes` and are correctly skipped.
|
|
195
|
+
for (const c of currentLevel) {
|
|
196
|
+
const child = nodes.get(c.id);
|
|
197
|
+
if (child && child.id !== rootId && child.parentId === null) {
|
|
198
|
+
child.parentId = rootId;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
188
201
|
return { nodes, rootId, levels: level + 2, timedOut: false };
|
|
189
202
|
}
|
|
190
203
|
|
|
@@ -200,6 +213,7 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
200
213
|
if (group.length === 0) continue;
|
|
201
214
|
const merged: ClusterItem = {
|
|
202
215
|
id: nextId(level + 1, g),
|
|
216
|
+
parentId: null,
|
|
203
217
|
embedding: clustered.centroids[g],
|
|
204
218
|
leafIds: group.flatMap((c) => c.leafIds),
|
|
205
219
|
messages: group.flatMap((c) => c.messages),
|
|
@@ -223,9 +237,10 @@ export function buildRaptorTree(leaves: Leaf[], opts: BuildOptions): RaptorTree
|
|
|
223
237
|
}
|
|
224
238
|
|
|
225
239
|
const root = currentLevel[0];
|
|
240
|
+
const rootId = root ? root.id : null;
|
|
226
241
|
return {
|
|
227
242
|
nodes,
|
|
228
|
-
rootId
|
|
243
|
+
rootId,
|
|
229
244
|
levels: level + 1,
|
|
230
245
|
timedOut: false,
|
|
231
246
|
};
|
|
@@ -252,5 +267,5 @@ function extractiveFallbackRoot(
|
|
|
252
267
|
qualityMarker: "low",
|
|
253
268
|
tokenEstimate: summary.tokenEstimate,
|
|
254
269
|
});
|
|
255
|
-
return { nodes, rootId, levels:
|
|
270
|
+
return { nodes, rootId, levels: 100, timedOut: true };
|
|
256
271
|
}
|
package/src/engine.ts
CHANGED
|
@@ -77,8 +77,21 @@ export interface CompactResult {
|
|
|
77
77
|
/** Token count of the original dropped region (before compaction). The honest
|
|
78
78
|
* "tokens saved" base = originalTokenEstimate − tokenEstimate (stored), or the
|
|
79
79
|
* full originalTokenEstimate when the region deduped onto an existing
|
|
80
|
-
* checkpoint (nothing new stored).
|
|
80
|
+
* checkpoint (nothing new stored). Computed over the FULL compactable slice
|
|
81
|
+
* (including superseded messages) so the dedup branch books the whole region;
|
|
82
|
+
* this is the value persisted into the checkpoint record and read by the
|
|
83
|
+
* dashboard / vector-read paths. */
|
|
81
84
|
originalTokenEstimate: number;
|
|
85
|
+
/** F5: token count of the filtered KEEP set (post-supersede) — the honest base
|
|
86
|
+
* for the stored-vs-original compaction delta. `originalTokenEstimate` minus
|
|
87
|
+
* this is the supersede savings; this minus `tokenEstimate` is the pure
|
|
88
|
+
* compaction savings. Not persisted (return-only); callers that want honest
|
|
89
|
+
* per-layer reporting should prefer this over `originalTokenEstimate`. */
|
|
90
|
+
keepTokenEstimate: number;
|
|
91
|
+
/** F5: tokens dropped by the SUPERSEDE layer = originalTokenEstimate −
|
|
92
|
+
* keepTokenEstimate. Reported separately so supersede savings are not booked
|
|
93
|
+
* as compaction savings. Not persisted (return-only). */
|
|
94
|
+
supersedeTokenSavings: number;
|
|
82
95
|
/** Index in `messages` where the compacted slice begins (for the caller to
|
|
83
96
|
* build a drop range). */
|
|
84
97
|
compactedFrom: number;
|
|
@@ -104,7 +117,10 @@ export function setDefaultStore(store: VectorStore | undefined): void {
|
|
|
104
117
|
* when the compactable slice is empty.
|
|
105
118
|
*/
|
|
106
119
|
export function compactSession(input: CompactInput, store: VectorStore = getDefaultStore()): CompactResult {
|
|
107
|
-
|
|
120
|
+
// F6: clamp keepFrom defensively. Upstream (the extension adapter) already
|
|
121
|
+
// clamps, but a bad keepFrom (negative or > length) would produce a misleading
|
|
122
|
+
// drop range or an empty compactable slice; belt-and-braces, clamp here too.
|
|
123
|
+
const keepFrom = Math.max(0, Math.min(input.keepFrom ?? input.messages.length, input.messages.length));
|
|
108
124
|
const compactable = input.messages.slice(0, keepFrom);
|
|
109
125
|
const compactedFrom = keepFrom;
|
|
110
126
|
|
|
@@ -117,6 +133,8 @@ export function compactSession(input: CompactInput, store: VectorStore = getDefa
|
|
|
117
133
|
tokenEstimate: 0,
|
|
118
134
|
filesModified: [],
|
|
119
135
|
originalTokenEstimate: 0,
|
|
136
|
+
keepTokenEstimate: 0,
|
|
137
|
+
supersedeTokenSavings: 0,
|
|
120
138
|
compactedFrom,
|
|
121
139
|
};
|
|
122
140
|
}
|
|
@@ -154,12 +172,21 @@ export function compactSession(input: CompactInput, store: VectorStore = getDefa
|
|
|
154
172
|
|
|
155
173
|
// Honest "tokens saved" accounting:
|
|
156
174
|
// - originalTokenEstimate = the dropped region's token count (what context
|
|
157
|
-
// held before compaction) = the compacted slice's tokens.
|
|
175
|
+
// held before compaction) = the compacted slice's tokens. Computed over the
|
|
176
|
+
// FULL compactable slice (incl. superseded) so the dedup branch books the
|
|
177
|
+
// whole region; this is the value persisted into the checkpoint record.
|
|
178
|
+
// - keepTokenEstimate (F5) = the filtered keep set's tokens (post-supersede).
|
|
179
|
+
// The honest base for the stored-vs-original compaction delta: the pure
|
|
180
|
+
// compaction savings = keepTokenEstimate − storedTokens, and the supersede
|
|
181
|
+
// savings = originalTokenEstimate − keepTokenEstimate. Without this, the
|
|
182
|
+
// supersede savings get booked as compaction savings.
|
|
158
183
|
// - storedTokens = the persisted summary's token count, computed from the
|
|
159
184
|
// actual summary string so it's honest for BOTH the extractive and legacy
|
|
160
185
|
// COLLAPSE paths (the legacy path's fallback estimateSessionTokens is the
|
|
161
186
|
// *original* size, not the stored size).
|
|
162
187
|
const originalTokenEstimate = estimateSessionTokens(compactable);
|
|
188
|
+
const keepTokenEstimate = estimateSessionTokens(keep);
|
|
189
|
+
const supersedeTokenSavings = Math.max(0, originalTokenEstimate - keepTokenEstimate);
|
|
163
190
|
const storedTokens = estimateBlockTokens(summary);
|
|
164
191
|
|
|
165
192
|
// Region text = the compacted slice, used for dedup + embedding.
|
|
@@ -191,6 +218,8 @@ export function compactSession(input: CompactInput, store: VectorStore = getDefa
|
|
|
191
218
|
tokenEstimate: storedTokens,
|
|
192
219
|
filesModified,
|
|
193
220
|
originalTokenEstimate,
|
|
221
|
+
keepTokenEstimate,
|
|
222
|
+
supersedeTokenSavings,
|
|
194
223
|
compactedFrom,
|
|
195
224
|
};
|
|
196
225
|
}
|