recallnest 2.5.4 → 2.6.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.md +37 -0
- package/README.md +34 -9
- package/README_CN.md +37 -12
- package/package.json +6 -5
- package/src/__tests__/access-tracker-novelty.test.ts +66 -0
- package/src/__tests__/api-server-security.test.ts +102 -0
- package/src/__tests__/belief-history.test.ts +220 -0
- package/src/__tests__/capture-engine.test.ts +93 -3
- package/src/__tests__/category-min-scores.test.ts +83 -0
- package/src/__tests__/cluster-consolidation.test.ts +167 -4
- package/src/__tests__/conflict-store.test.ts +46 -2
- package/src/__tests__/consolidation-engine.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +46 -0
- package/src/__tests__/decay-exemption.test.ts +45 -0
- package/src/__tests__/dream-pipeline.test.ts +464 -5
- package/src/__tests__/freshness.test.ts +186 -0
- package/src/__tests__/hotness-blend.test.ts +207 -0
- package/src/__tests__/llm-client-pivot.test.ts +628 -0
- package/src/__tests__/maintenance-locks.test.ts +15 -0
- package/src/__tests__/memory-boundaries.test.ts +32 -0
- package/src/__tests__/noise-prototype-bank.test.ts +280 -0
- package/src/__tests__/pii-redact.test.ts +19 -0
- package/src/__tests__/pivot-apply.test.ts +373 -0
- package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
- package/src/__tests__/pivot-distill.test.ts +1178 -0
- package/src/__tests__/preference-matcher.test.ts +54 -2
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
- package/src/__tests__/recall-gate.test.ts +266 -0
- package/src/__tests__/resolve-db-path.test.ts +130 -0
- package/src/__tests__/resolve-tier.test.ts +64 -0
- package/src/__tests__/retrieval-eval.test.ts +163 -0
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
- package/src/__tests__/skill-promotion.test.ts +53 -0
- package/src/__tests__/stable-context-length-contract.test.ts +62 -0
- package/src/__tests__/store-read-consistency.test.ts +122 -0
- package/src/__tests__/store-schema-migration.test.ts +87 -0
- package/src/__tests__/store-upsert.test.ts +17 -0
- package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
- package/src/__tests__/ui-server-security.test.ts +93 -0
- package/src/access-tracker.ts +53 -3
- package/src/adaptive-retrieval.ts +11 -0
- package/src/api-server.ts +9 -2
- package/src/auto-recall.ts +61 -1
- package/src/belief-history.ts +164 -0
- package/src/capture-engine.ts +88 -11
- package/src/cli.ts +109 -14
- package/src/conflict-engine.ts +24 -3
- package/src/consolidation-engine.ts +146 -12
- package/src/context-composer-stable-selection.ts +7 -1
- package/src/data-checkup.ts +1 -1
- package/src/decay-engine.ts +59 -7
- package/src/doctor.ts +4 -2
- package/src/dream-pipeline.ts +359 -11
- package/src/env-config.ts +41 -0
- package/src/eval.ts +154 -11
- package/src/freshness.ts +192 -0
- package/src/http-request-policy.ts +148 -0
- package/src/ingest.ts +277 -2
- package/src/kg-extractor.ts +7 -1
- package/src/llm-client.ts +106 -11
- package/src/mcp-server.ts +2 -1
- package/src/mcp-tools-advanced.ts +13 -0
- package/src/mcp-tools-core.ts +5 -1
- package/src/memory-boundaries.ts +23 -2
- package/src/memory-health-rebalance.ts +4 -0
- package/src/memory-output.ts +16 -2
- package/src/memory-store-port.ts +3 -0
- package/src/noise-prototype-bank.ts +408 -0
- package/src/pii-detector.ts +52 -2
- package/src/pivot-apply.ts +399 -0
- package/src/preference-matcher.ts +37 -3
- package/src/recall-gate.ts +305 -0
- package/src/retrieval-profiles.ts +46 -4
- package/src/retriever.ts +95 -18
- package/src/retroactive-boost.ts +6 -2
- package/src/runtime-config.ts +70 -5
- package/src/skill-promotion.ts +58 -2
- package/src/store.ts +54 -19
- package/src/term-registry.ts +35 -0
- package/src/ui-server.ts +7 -0
- package/src/version.ts +19 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freshness 四态判定测试 —— 通用Wiki上下文记忆Agent方案借鉴落地(2026-07-21)。
|
|
3
|
+
* 覆盖 file / git-rev 两类依赖的 exact/compatible/uncertain/invalid 四态,
|
|
4
|
+
* 以及 opt-in 零回退(无 dependsOn → null)与共享缓存的 memoize。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import {
|
|
13
|
+
createFreshnessCache,
|
|
14
|
+
evaluateDependency,
|
|
15
|
+
evaluateEntryFreshness,
|
|
16
|
+
evaluateFreshness,
|
|
17
|
+
parseDependsOn,
|
|
18
|
+
parseDependsOnInput,
|
|
19
|
+
type Dependency,
|
|
20
|
+
} from "../freshness.js";
|
|
21
|
+
|
|
22
|
+
let tmp: string;
|
|
23
|
+
let filePath: string;
|
|
24
|
+
let fileMtime: string;
|
|
25
|
+
let notGitDir: string;
|
|
26
|
+
let gitRepo: string;
|
|
27
|
+
let gitHead: string;
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
tmp = mkdtempSync(join(tmpdir(), "rn-freshness-"));
|
|
31
|
+
|
|
32
|
+
// 真实文件,供 kind='file' 用例
|
|
33
|
+
filePath = join(tmp, "dep.txt");
|
|
34
|
+
writeFileSync(filePath, "hello");
|
|
35
|
+
fileMtime = String(Math.floor(statSync(filePath).mtimeMs));
|
|
36
|
+
|
|
37
|
+
// 明确的非 git 目录(父级 tmpdir 也非 git),供 invalid 用例
|
|
38
|
+
notGitDir = join(tmp, "notgit");
|
|
39
|
+
mkdirSync(notGitDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
// 真实 git 仓库,供 kind='git-rev' 用例
|
|
42
|
+
gitRepo = join(tmp, "repo");
|
|
43
|
+
mkdirSync(gitRepo, { recursive: true });
|
|
44
|
+
const git = (args: string[]): void => {
|
|
45
|
+
execFileSync("git", ["-C", gitRepo, ...args], { stdio: ["ignore", "pipe", "ignore"] });
|
|
46
|
+
};
|
|
47
|
+
git(["init", "-q"]);
|
|
48
|
+
git(["config", "user.email", "t@example.com"]);
|
|
49
|
+
git(["config", "user.name", "tester"]);
|
|
50
|
+
writeFileSync(join(gitRepo, "f.txt"), "x");
|
|
51
|
+
git(["add", "."]);
|
|
52
|
+
git(["commit", "-q", "-m", "init"]);
|
|
53
|
+
gitHead = execFileSync("git", ["-C", gitRepo, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterAll(() => {
|
|
57
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("parseDependsOn / parseDependsOnInput", () => {
|
|
61
|
+
test("无 dependsOn 的 metadata → null(opt-in 零回退)", () => {
|
|
62
|
+
expect(parseDependsOn(undefined)).toBeNull();
|
|
63
|
+
expect(parseDependsOn("{}")).toBeNull();
|
|
64
|
+
expect(parseDependsOn(JSON.stringify({ emotion: { valence: 0, arousal: 0 } }))).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("非法 JSON / 非法结构 → null", () => {
|
|
68
|
+
expect(parseDependsOn("{not json")).toBeNull();
|
|
69
|
+
expect(parseDependsOn(JSON.stringify({ dependsOn: "nope" }))).toBeNull();
|
|
70
|
+
expect(parseDependsOn(JSON.stringify({ dependsOn: [{ kind: "bogus", ref: "x" }] }))).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("合法 dependsOn → 解析出数组", () => {
|
|
74
|
+
const md = JSON.stringify({ dependsOn: [{ kind: "file", ref: "/a" }] });
|
|
75
|
+
const deps = parseDependsOn(md);
|
|
76
|
+
expect(deps).not.toBeNull();
|
|
77
|
+
expect(deps?.length).toBe(1);
|
|
78
|
+
expect(deps?.[0].kind).toBe("file");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("parseDependsOnInput 校验原始输入", () => {
|
|
82
|
+
expect(parseDependsOnInput(null)).toBeNull();
|
|
83
|
+
expect(parseDependsOnInput([{ kind: "file", ref: "/a" }])?.length).toBe(1);
|
|
84
|
+
expect(parseDependsOnInput([{ kind: "x", ref: "/a" }])).toBeNull();
|
|
85
|
+
expect(parseDependsOnInput([])).toBeNull(); // min(1)
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("file 依赖四态", () => {
|
|
90
|
+
const cache = createFreshnessCache();
|
|
91
|
+
|
|
92
|
+
test("存在 + 无 expected → exact", () => {
|
|
93
|
+
expect(evaluateDependency({ kind: "file", ref: filePath }, cache)).toBe("exact");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("存在 + mtime 匹配 → exact", () => {
|
|
97
|
+
expect(evaluateDependency({ kind: "file", ref: filePath, expected: fileMtime }, cache)).toBe("exact");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("存在 + mtime 落在兼容集次值 → compatible", () => {
|
|
101
|
+
expect(
|
|
102
|
+
evaluateDependency({ kind: "file", ref: filePath, expected: ["9999999999999", fileMtime] }, cache),
|
|
103
|
+
).toBe("compatible");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("存在 + mtime 不匹配 → uncertain", () => {
|
|
107
|
+
expect(evaluateDependency({ kind: "file", ref: filePath, expected: "1" }, cache)).toBe("uncertain");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("文件不存在 → invalid", () => {
|
|
111
|
+
expect(evaluateDependency({ kind: "file", ref: join(tmp, "nope.txt") }, cache)).toBe("invalid");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("git-rev 依赖四态", () => {
|
|
116
|
+
const cache = createFreshnessCache();
|
|
117
|
+
|
|
118
|
+
test("HEAD 匹配(完整 hash)→ exact", () => {
|
|
119
|
+
expect(evaluateDependency({ kind: "git-rev", ref: gitRepo, expected: gitHead }, cache)).toBe("exact");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("HEAD 匹配(短 hash 前缀)→ exact", () => {
|
|
123
|
+
expect(evaluateDependency({ kind: "git-rev", ref: gitRepo, expected: gitHead.slice(0, 8) }, cache)).toBe("exact");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("HEAD 落在兼容集次值 → compatible", () => {
|
|
127
|
+
expect(
|
|
128
|
+
evaluateDependency({ kind: "git-rev", ref: gitRepo, expected: ["deadbeefcafe1234", gitHead] }, cache),
|
|
129
|
+
).toBe("compatible");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("HEAD 不匹配 → uncertain", () => {
|
|
133
|
+
expect(evaluateDependency({ kind: "git-rev", ref: gitRepo, expected: "deadbeefcafe1234" }, cache)).toBe("uncertain");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("仓库内文件 + 无 expected → exact(仅存在性)", () => {
|
|
137
|
+
expect(evaluateDependency({ kind: "git-rev", ref: join(gitRepo, "f.txt") }, cache)).toBe("exact");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("非 git 目录 → invalid", () => {
|
|
141
|
+
expect(evaluateDependency({ kind: "git-rev", ref: notGitDir, expected: gitHead }, cache)).toBe("invalid");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("evaluateFreshness 聚合 + evaluateEntryFreshness 入口", () => {
|
|
146
|
+
test("多依赖取最差:exact + invalid → invalid", () => {
|
|
147
|
+
const deps: Dependency[] = [
|
|
148
|
+
{ kind: "file", ref: filePath },
|
|
149
|
+
{ kind: "file", ref: join(tmp, "gone.txt") },
|
|
150
|
+
];
|
|
151
|
+
expect(evaluateFreshness(deps)).toBe("invalid");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("多依赖取最差:exact + compatible → compatible", () => {
|
|
155
|
+
const deps: Dependency[] = [
|
|
156
|
+
{ kind: "file", ref: filePath },
|
|
157
|
+
{ kind: "git-rev", ref: gitRepo, expected: ["deadbeefcafe1234", gitHead] },
|
|
158
|
+
];
|
|
159
|
+
expect(evaluateFreshness(deps)).toBe("compatible");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("空依赖 → exact", () => {
|
|
163
|
+
expect(evaluateFreshness([])).toBe("exact");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("evaluateEntryFreshness: 无 dependsOn → null(零回退)", () => {
|
|
167
|
+
expect(evaluateEntryFreshness(undefined)).toBeNull();
|
|
168
|
+
expect(evaluateEntryFreshness("{}")).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("evaluateEntryFreshness: 有 dependsOn → 对应四态", () => {
|
|
172
|
+
const exactMd = JSON.stringify({ dependsOn: [{ kind: "file", ref: filePath, expected: fileMtime }] });
|
|
173
|
+
expect(evaluateEntryFreshness(exactMd)).toBe("exact");
|
|
174
|
+
const invalidMd = JSON.stringify({ dependsOn: [{ kind: "file", ref: join(tmp, "gone.txt") }] });
|
|
175
|
+
expect(evaluateEntryFreshness(invalidMd)).toBe("invalid");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("共享缓存 memoize:同一仓库多依赖只查一次 git", () => {
|
|
179
|
+
const cache = createFreshnessCache();
|
|
180
|
+
evaluateDependency({ kind: "git-rev", ref: gitRepo, expected: gitHead }, cache);
|
|
181
|
+
expect(cache.size).toBe(1);
|
|
182
|
+
// f.txt 位于 gitRepo 内,dirname=gitRepo,命中同一缓存键
|
|
183
|
+
evaluateDependency({ kind: "git-rev", ref: join(gitRepo, "f.txt"), expected: gitHead }, cache);
|
|
184
|
+
expect(cache.size).toBe(1);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { describe, it, expect } from "bun:test";
|
|
2
2
|
import { computeHotnessScore, parseAccessMetadata } from "../access-tracker.js";
|
|
3
|
+
import { TraceCollector } from "../retrieval-trace.js";
|
|
4
|
+
import {
|
|
5
|
+
createRetriever,
|
|
6
|
+
DEFAULT_CATEGORY_MIN_SCORES,
|
|
7
|
+
} from "../retriever.js";
|
|
8
|
+
|
|
9
|
+
function applyHotnessBlend(
|
|
10
|
+
score: number,
|
|
11
|
+
accessCount: number,
|
|
12
|
+
lastAccessedAt: number,
|
|
13
|
+
): number {
|
|
14
|
+
const retriever = createRetriever({} as any, {} as any, { hotnessWeight: 0.15 });
|
|
15
|
+
retriever.setAccessTracker({} as any);
|
|
16
|
+
const results = [{
|
|
17
|
+
entry: {
|
|
18
|
+
id: "hotness-test",
|
|
19
|
+
text: "hotness test",
|
|
20
|
+
vector: [],
|
|
21
|
+
category: "events",
|
|
22
|
+
scope: "test:hotness",
|
|
23
|
+
importance: 0.5,
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
metadata: JSON.stringify({ accessCount, lastAccessedAt }),
|
|
26
|
+
},
|
|
27
|
+
score,
|
|
28
|
+
sources: {},
|
|
29
|
+
}];
|
|
30
|
+
return (retriever as any).applyHotnessBlend(results)[0].score;
|
|
31
|
+
}
|
|
3
32
|
|
|
4
33
|
describe("computeHotnessScore", () => {
|
|
5
34
|
it("returns 0 for zero accesses", () => {
|
|
@@ -74,3 +103,181 @@ describe("parseAccessMetadata", () => {
|
|
|
74
103
|
expect(result.lastAccessedAt).toBe(0);
|
|
75
104
|
});
|
|
76
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("applyHotnessBlend", () => {
|
|
108
|
+
it("does not penalize a zero-access row", () => {
|
|
109
|
+
expect(applyHotnessBlend(0.6, 0, 0)).toBe(0.6);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("preserves the existing blend formula for an accessed row", () => {
|
|
113
|
+
const futureAccess = Date.now() + 60_000;
|
|
114
|
+
const hotness = computeHotnessScore(5, futureAccess);
|
|
115
|
+
expect(applyHotnessBlend(0.6, 5, futureAccess)).toBeCloseTo(
|
|
116
|
+
0.6 * 0.85 + hotness * 0.15,
|
|
117
|
+
12,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("still applies the existing penalty when an accessed row's hotness decays to zero", () => {
|
|
122
|
+
expect(computeHotnessScore(1, 1)).toBe(0);
|
|
123
|
+
expect(applyHotnessBlend(0.6, 1, 1)).toBeCloseTo(0.6 * 0.85, 12);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("cold-start hotness pipeline regression", () => {
|
|
128
|
+
it("recalls a78ad478 without dropping control dd05b422", async () => {
|
|
129
|
+
const query = "owner 模型 任务锁 并发 谁在干活 责任层 资源层";
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const text = `${query} `.repeat(80).slice(0, 1900);
|
|
132
|
+
|
|
133
|
+
expect(text).toHaveLength(1900);
|
|
134
|
+
|
|
135
|
+
function metadata(
|
|
136
|
+
source: "agent" | "manual",
|
|
137
|
+
confidence: number,
|
|
138
|
+
accessCount: number,
|
|
139
|
+
): string {
|
|
140
|
+
const lastAccessedAt = accessCount > 0 ? now : 0;
|
|
141
|
+
|
|
142
|
+
return JSON.stringify({
|
|
143
|
+
source,
|
|
144
|
+
confidence: {
|
|
145
|
+
score: confidence,
|
|
146
|
+
reliability: source === "manual" ? "direct" : "inferred",
|
|
147
|
+
},
|
|
148
|
+
accessCount,
|
|
149
|
+
lastAccessedAt,
|
|
150
|
+
boundary: {
|
|
151
|
+
layer: "durable",
|
|
152
|
+
authority: "structured-memory",
|
|
153
|
+
conflictPolicy: "latest-wins",
|
|
154
|
+
originalCategory: "patterns",
|
|
155
|
+
},
|
|
156
|
+
evolution: {
|
|
157
|
+
status: "active",
|
|
158
|
+
version: 1,
|
|
159
|
+
accessCount,
|
|
160
|
+
lastAccessedAt: accessCount > 0 ? lastAccessedAt : null,
|
|
161
|
+
validFrom: now,
|
|
162
|
+
validUntil: null,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const candidates = [
|
|
168
|
+
{
|
|
169
|
+
entry: {
|
|
170
|
+
id: "a78ad478",
|
|
171
|
+
text,
|
|
172
|
+
vector: [1, 0, 0],
|
|
173
|
+
category: "patterns",
|
|
174
|
+
scope: "project:hippo-wiki",
|
|
175
|
+
importance: 0.8,
|
|
176
|
+
timestamp: now,
|
|
177
|
+
metadata: metadata("agent", 0.7, 0),
|
|
178
|
+
},
|
|
179
|
+
score: 0.8628,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
entry: {
|
|
183
|
+
id: "dd05b422",
|
|
184
|
+
text,
|
|
185
|
+
vector: [0.99, 0.01, 0],
|
|
186
|
+
category: "patterns",
|
|
187
|
+
scope: "project:hippo-wiki",
|
|
188
|
+
importance: 0.8,
|
|
189
|
+
timestamp: now,
|
|
190
|
+
metadata: metadata("manual", 0.9, 4),
|
|
191
|
+
},
|
|
192
|
+
score: 0.8579,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
let vectorSearchCalls = 0;
|
|
197
|
+
|
|
198
|
+
const store = {
|
|
199
|
+
hasFtsSupport: false,
|
|
200
|
+
async vectorSearch(
|
|
201
|
+
_queryVector: number[],
|
|
202
|
+
limit: number,
|
|
203
|
+
minScore: number,
|
|
204
|
+
scopeFilter?: string[],
|
|
205
|
+
) {
|
|
206
|
+
vectorSearchCalls += 1;
|
|
207
|
+
return candidates
|
|
208
|
+
.filter(candidate => candidate.score >= minScore)
|
|
209
|
+
.filter(candidate =>
|
|
210
|
+
!scopeFilter || scopeFilter.includes(candidate.entry.scope),
|
|
211
|
+
)
|
|
212
|
+
.slice(0, limit);
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const embedder = {
|
|
217
|
+
async embedQuery() {
|
|
218
|
+
return [1, 0, 0];
|
|
219
|
+
},
|
|
220
|
+
async embedPassage() {
|
|
221
|
+
return [1, 0, 0];
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const retriever = createRetriever(
|
|
226
|
+
store as any,
|
|
227
|
+
embedder as any,
|
|
228
|
+
{
|
|
229
|
+
mode: "vector",
|
|
230
|
+
rerank: "none",
|
|
231
|
+
candidatePoolSize: 5,
|
|
232
|
+
minScore: 0.3,
|
|
233
|
+
hardMinScore: 0.38,
|
|
234
|
+
recencyHalfLifeDays: 14,
|
|
235
|
+
recencyWeight: 0.15,
|
|
236
|
+
lengthNormAnchor: 700,
|
|
237
|
+
timeDecayHalfLifeDays: 60,
|
|
238
|
+
hotnessWeight: 0.15,
|
|
239
|
+
filterNoise: false,
|
|
240
|
+
enableRIF: false,
|
|
241
|
+
sourceDiversity: 0,
|
|
242
|
+
multiHop: false,
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
retriever.setAccessTracker({
|
|
247
|
+
computeEffectiveHalfLife(baseHalfLife: number) {
|
|
248
|
+
return baseHalfLife;
|
|
249
|
+
},
|
|
250
|
+
recordAccess() {
|
|
251
|
+
throw new Error("pipeline regression must not record access");
|
|
252
|
+
},
|
|
253
|
+
} as any);
|
|
254
|
+
|
|
255
|
+
const trace = new TraceCollector();
|
|
256
|
+
const results = await retriever.retrieve({
|
|
257
|
+
query,
|
|
258
|
+
limit: 5,
|
|
259
|
+
scopeFilter: ["project:hippo-wiki"],
|
|
260
|
+
category: "patterns",
|
|
261
|
+
source: "auto-recall",
|
|
262
|
+
trace,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const ids = results.map(result => result.entry.id);
|
|
266
|
+
const cold = results.find(result => result.entry.id === "a78ad478");
|
|
267
|
+
const hardMin = trace
|
|
268
|
+
.finalize(query, "vector")
|
|
269
|
+
.stages.find(stage => stage.name === "hard_min_score");
|
|
270
|
+
|
|
271
|
+
expect(ids).toContain("a78ad478");
|
|
272
|
+
expect(ids).toContain("dd05b422");
|
|
273
|
+
expect(cold?.score).toBeGreaterThanOrEqual(
|
|
274
|
+
DEFAULT_CATEGORY_MIN_SCORES.patterns,
|
|
275
|
+
);
|
|
276
|
+
expect(hardMin).toMatchObject({
|
|
277
|
+
inputCount: 2,
|
|
278
|
+
outputCount: 2,
|
|
279
|
+
droppedCount: 0,
|
|
280
|
+
});
|
|
281
|
+
expect(vectorSearchCalls).toBe(1);
|
|
282
|
+
});
|
|
283
|
+
});
|