hippo-memory 0.26.0 → 0.28.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/README.md +16 -0
- package/dist/cli.js +489 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/eval.d.ts +103 -0
- package/dist/eval.d.ts.map +1 -0
- package/dist/eval.js +187 -0
- package/dist/eval.js.map +1 -0
- package/dist/refine-llm.d.ts +53 -0
- package/dist/refine-llm.d.ts.map +1 -0
- package/dist/refine-llm.js +147 -0
- package/dist/refine-llm.js.map +1 -0
- package/dist/search.d.ts +91 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +215 -29
- package/dist/search.js.map +1 -1
- package/dist/shared.d.ts +7 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +31 -14
- package/dist/shared.js.map +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,KAAK,aAAa,EAA8C,MAAM,qBAAqB,CAAC;AAErG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE;QACV,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,KAAK,aAAa,EAA8C,MAAM,qBAAqB,CAAC;AAErG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE;QACV,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB;kEAC8D;IAC9D,GAAG,EAAE;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;iCACyB;QACzB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,EAAE;QACN;mFAC2E;QAC3E,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAmCD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CA4BzD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAGvE"}
|
package/dist/config.js
CHANGED
|
@@ -28,6 +28,13 @@ const DEFAULT_CONFIG = {
|
|
|
28
28
|
'refactor', 'perf', 'chore', 'breaking', 'deprecate',
|
|
29
29
|
],
|
|
30
30
|
physics: { ...DEFAULT_PHYSICS_CONFIG },
|
|
31
|
+
mmr: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
lambda: 0.7,
|
|
34
|
+
},
|
|
35
|
+
search: {
|
|
36
|
+
localBump: 1.2,
|
|
37
|
+
},
|
|
31
38
|
};
|
|
32
39
|
export function loadConfig(hippoRoot) {
|
|
33
40
|
const configPath = path.join(hippoRoot, 'config.json');
|
|
@@ -49,6 +56,8 @@ export function loadConfig(hippoRoot) {
|
|
|
49
56
|
global: { ...DEFAULT_CONFIG.global, ...(raw.global ?? {}) },
|
|
50
57
|
gitLearnPatterns: raw.gitLearnPatterns ?? DEFAULT_CONFIG.gitLearnPatterns,
|
|
51
58
|
physics: mergePhysicsConfig(raw.physics),
|
|
59
|
+
mmr: { ...DEFAULT_CONFIG.mmr, ...(raw.mmr ?? {}) },
|
|
60
|
+
search: { ...DEFAULT_CONFIG.search, ...(raw.search ?? {}) },
|
|
52
61
|
};
|
|
53
62
|
}
|
|
54
63
|
catch (err) {
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAsB,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/eval.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall eval harness.
|
|
3
|
+
*
|
|
4
|
+
* Given a corpus of (query, expected_memory_ids) cases, run recall against
|
|
5
|
+
* the store and report ranking-quality metrics: MRR, Recall@K, NDCG@K.
|
|
6
|
+
*
|
|
7
|
+
* The goal is to make recall quality measurable so MMR lambda, embedding
|
|
8
|
+
* weights, and future scoring tweaks can be tuned against evidence instead
|
|
9
|
+
* of intuition.
|
|
10
|
+
*/
|
|
11
|
+
import type { MemoryEntry } from './memory.js';
|
|
12
|
+
export interface EvalCase {
|
|
13
|
+
/** Free-form ID for humans to reference the case. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** The query text to run through recall. */
|
|
16
|
+
query: string;
|
|
17
|
+
/** Memory IDs considered relevant. At least one required. */
|
|
18
|
+
expectedIds: string[];
|
|
19
|
+
/** Optional short description so a failure report is self-explaining. */
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface EvalCaseResult {
|
|
23
|
+
case: EvalCase;
|
|
24
|
+
returnedIds: string[];
|
|
25
|
+
/** 1 / rank of the first expected id, else 0. */
|
|
26
|
+
mrr: number;
|
|
27
|
+
/** |expected ∩ returned[0..K]| / |expected|, 0 when expected is empty. */
|
|
28
|
+
recallAt5: number;
|
|
29
|
+
recallAt10: number;
|
|
30
|
+
/** Normalized DCG at 10 using binary relevance (expected = 1, else 0). */
|
|
31
|
+
ndcgAt10: number;
|
|
32
|
+
}
|
|
33
|
+
export interface EvalSummary {
|
|
34
|
+
cases: EvalCaseResult[];
|
|
35
|
+
/** Simple arithmetic means across cases. */
|
|
36
|
+
meanMrr: number;
|
|
37
|
+
meanRecallAt5: number;
|
|
38
|
+
meanRecallAt10: number;
|
|
39
|
+
meanNdcgAt10: number;
|
|
40
|
+
/** Wall-clock runtime in ms for the whole eval. */
|
|
41
|
+
durationMs: number;
|
|
42
|
+
}
|
|
43
|
+
export interface RunEvalOptions {
|
|
44
|
+
mmr?: boolean;
|
|
45
|
+
mmrLambda?: number;
|
|
46
|
+
embeddingWeight?: number;
|
|
47
|
+
/** Max returned results per case. Larger than K-at-10 so metrics stay honest. */
|
|
48
|
+
budget?: number;
|
|
49
|
+
hippoRoot?: string;
|
|
50
|
+
/** When set and initialized, eval runs through searchBothHybrid so global
|
|
51
|
+
* memories are in scope. Otherwise only the `entries` list is searched. */
|
|
52
|
+
globalRoot?: string;
|
|
53
|
+
/** Multiplier for local-over-global results when globalRoot is in play. */
|
|
54
|
+
localBump?: number;
|
|
55
|
+
now?: Date;
|
|
56
|
+
}
|
|
57
|
+
/** Mean Reciprocal Rank for a single ranking given expected ids. */
|
|
58
|
+
export declare function mrr(returned: string[], expected: string[]): number;
|
|
59
|
+
/** Recall@K — fraction of expected items found in the top-K. */
|
|
60
|
+
export declare function recallAtK(returned: string[], expected: string[], k: number): number;
|
|
61
|
+
/**
|
|
62
|
+
* Normalized Discounted Cumulative Gain at K with binary relevance.
|
|
63
|
+
* gain_i = 1 if returned[i] ∈ expected else 0. discount = log2(i + 2).
|
|
64
|
+
*/
|
|
65
|
+
export declare function ndcgAtK(returned: string[], expected: string[], k: number): number;
|
|
66
|
+
export declare function runEval(cases: EvalCase[], entries: MemoryEntry[], options?: RunEvalOptions): Promise<EvalSummary>;
|
|
67
|
+
export interface EvalDelta {
|
|
68
|
+
mrr: number;
|
|
69
|
+
recallAt5: number;
|
|
70
|
+
recallAt10: number;
|
|
71
|
+
ndcgAt10: number;
|
|
72
|
+
}
|
|
73
|
+
export interface CaseDelta {
|
|
74
|
+
id: string;
|
|
75
|
+
query: string;
|
|
76
|
+
mrrBefore: number;
|
|
77
|
+
mrrAfter: number;
|
|
78
|
+
r10Before: number;
|
|
79
|
+
r10After: number;
|
|
80
|
+
ndcgBefore: number;
|
|
81
|
+
ndcgAfter: number;
|
|
82
|
+
}
|
|
83
|
+
export interface EvalComparison {
|
|
84
|
+
aggregate: EvalDelta;
|
|
85
|
+
improved: CaseDelta[];
|
|
86
|
+
regressed: CaseDelta[];
|
|
87
|
+
unchanged: number;
|
|
88
|
+
onlyInBaseline: string[];
|
|
89
|
+
onlyInCurrent: string[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Compute pairwise deltas between a baseline and a current eval summary.
|
|
93
|
+
* Cases are matched by EvalCase.id; any mismatch is surfaced in the onlyIn
|
|
94
|
+
* arrays so the caller knows the corpora diverged.
|
|
95
|
+
*/
|
|
96
|
+
export declare function compareSummaries(baseline: EvalSummary, current: EvalSummary): EvalComparison;
|
|
97
|
+
/**
|
|
98
|
+
* For each memory, take its first 8 content words as a trivial query and
|
|
99
|
+
* expect that memory back. Useful as a smoke test: if recall can't find a
|
|
100
|
+
* memory by its own opening words, something is broken.
|
|
101
|
+
*/
|
|
102
|
+
export declare function bootstrapCorpus(entries: MemoryEntry[], maxCases?: number): EvalCase[];
|
|
103
|
+
//# sourceMappingURL=eval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.d.ts","sourceRoot":"","sources":["../src/eval.ts"],"names":[],"mappings":"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"}
|
package/dist/eval.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall eval harness.
|
|
3
|
+
*
|
|
4
|
+
* Given a corpus of (query, expected_memory_ids) cases, run recall against
|
|
5
|
+
* the store and report ranking-quality metrics: MRR, Recall@K, NDCG@K.
|
|
6
|
+
*
|
|
7
|
+
* The goal is to make recall quality measurable so MMR lambda, embedding
|
|
8
|
+
* weights, and future scoring tweaks can be tuned against evidence instead
|
|
9
|
+
* of intuition.
|
|
10
|
+
*/
|
|
11
|
+
import { hybridSearch } from './search.js';
|
|
12
|
+
import { searchBothHybrid } from './shared.js';
|
|
13
|
+
import { isInitialized } from './store.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Metrics — pure functions. K is inclusive of position K.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/** Mean Reciprocal Rank for a single ranking given expected ids. */
|
|
18
|
+
export function mrr(returned, expected) {
|
|
19
|
+
if (expected.length === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
const expectedSet = new Set(expected);
|
|
22
|
+
for (let i = 0; i < returned.length; i++) {
|
|
23
|
+
if (expectedSet.has(returned[i]))
|
|
24
|
+
return 1 / (i + 1);
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
/** Recall@K — fraction of expected items found in the top-K. */
|
|
29
|
+
export function recallAtK(returned, expected, k) {
|
|
30
|
+
if (expected.length === 0)
|
|
31
|
+
return 0;
|
|
32
|
+
const expectedSet = new Set(expected);
|
|
33
|
+
const topK = returned.slice(0, k);
|
|
34
|
+
let hits = 0;
|
|
35
|
+
for (const id of topK) {
|
|
36
|
+
if (expectedSet.has(id))
|
|
37
|
+
hits++;
|
|
38
|
+
}
|
|
39
|
+
return hits / expected.length;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalized Discounted Cumulative Gain at K with binary relevance.
|
|
43
|
+
* gain_i = 1 if returned[i] ∈ expected else 0. discount = log2(i + 2).
|
|
44
|
+
*/
|
|
45
|
+
export function ndcgAtK(returned, expected, k) {
|
|
46
|
+
if (expected.length === 0)
|
|
47
|
+
return 0;
|
|
48
|
+
const expectedSet = new Set(expected);
|
|
49
|
+
let dcg = 0;
|
|
50
|
+
for (let i = 0; i < Math.min(k, returned.length); i++) {
|
|
51
|
+
if (expectedSet.has(returned[i])) {
|
|
52
|
+
dcg += 1 / Math.log2(i + 2);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Ideal DCG: all relevant items at top positions.
|
|
56
|
+
const idealHits = Math.min(k, expected.length);
|
|
57
|
+
let idcg = 0;
|
|
58
|
+
for (let i = 0; i < idealHits; i++) {
|
|
59
|
+
idcg += 1 / Math.log2(i + 2);
|
|
60
|
+
}
|
|
61
|
+
return idcg === 0 ? 0 : dcg / idcg;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Runner
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
export async function runEval(cases, entries, options = {}) {
|
|
67
|
+
const budget = options.budget ?? 100_000; // generous so metrics aren't truncated
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
const results = [];
|
|
70
|
+
const useBothStores = Boolean(options.globalRoot && options.hippoRoot && isInitialized(options.globalRoot));
|
|
71
|
+
for (const c of cases) {
|
|
72
|
+
const ranked = useBothStores
|
|
73
|
+
? await searchBothHybrid(c.query, options.hippoRoot, options.globalRoot, {
|
|
74
|
+
budget,
|
|
75
|
+
now: options.now,
|
|
76
|
+
embeddingWeight: options.embeddingWeight,
|
|
77
|
+
mmr: options.mmr,
|
|
78
|
+
mmrLambda: options.mmrLambda,
|
|
79
|
+
localBump: options.localBump,
|
|
80
|
+
})
|
|
81
|
+
: await hybridSearch(c.query, entries, {
|
|
82
|
+
budget,
|
|
83
|
+
now: options.now,
|
|
84
|
+
hippoRoot: options.hippoRoot,
|
|
85
|
+
embeddingWeight: options.embeddingWeight,
|
|
86
|
+
mmr: options.mmr,
|
|
87
|
+
mmrLambda: options.mmrLambda,
|
|
88
|
+
});
|
|
89
|
+
const returnedIds = ranked.map((r) => r.entry.id);
|
|
90
|
+
results.push({
|
|
91
|
+
case: c,
|
|
92
|
+
returnedIds,
|
|
93
|
+
mrr: mrr(returnedIds, c.expectedIds),
|
|
94
|
+
recallAt5: recallAtK(returnedIds, c.expectedIds, 5),
|
|
95
|
+
recallAt10: recallAtK(returnedIds, c.expectedIds, 10),
|
|
96
|
+
ndcgAt10: ndcgAtK(returnedIds, c.expectedIds, 10),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const n = Math.max(1, results.length);
|
|
100
|
+
const meanMrr = results.reduce((s, r) => s + r.mrr, 0) / n;
|
|
101
|
+
const meanRecallAt5 = results.reduce((s, r) => s + r.recallAt5, 0) / n;
|
|
102
|
+
const meanRecallAt10 = results.reduce((s, r) => s + r.recallAt10, 0) / n;
|
|
103
|
+
const meanNdcgAt10 = results.reduce((s, r) => s + r.ndcgAt10, 0) / n;
|
|
104
|
+
return {
|
|
105
|
+
cases: results,
|
|
106
|
+
meanMrr,
|
|
107
|
+
meanRecallAt5,
|
|
108
|
+
meanRecallAt10,
|
|
109
|
+
meanNdcgAt10,
|
|
110
|
+
durationMs: Date.now() - start,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Compute pairwise deltas between a baseline and a current eval summary.
|
|
115
|
+
* Cases are matched by EvalCase.id; any mismatch is surfaced in the onlyIn
|
|
116
|
+
* arrays so the caller knows the corpora diverged.
|
|
117
|
+
*/
|
|
118
|
+
export function compareSummaries(baseline, current) {
|
|
119
|
+
const aggregate = {
|
|
120
|
+
mrr: current.meanMrr - baseline.meanMrr,
|
|
121
|
+
recallAt5: current.meanRecallAt5 - baseline.meanRecallAt5,
|
|
122
|
+
recallAt10: current.meanRecallAt10 - baseline.meanRecallAt10,
|
|
123
|
+
ndcgAt10: current.meanNdcgAt10 - baseline.meanNdcgAt10,
|
|
124
|
+
};
|
|
125
|
+
const baseById = new Map(baseline.cases.map((c) => [c.case.id, c]));
|
|
126
|
+
const curById = new Map(current.cases.map((c) => [c.case.id, c]));
|
|
127
|
+
const improved = [];
|
|
128
|
+
const regressed = [];
|
|
129
|
+
let unchanged = 0;
|
|
130
|
+
for (const [id, cur] of curById) {
|
|
131
|
+
const base = baseById.get(id);
|
|
132
|
+
if (!base)
|
|
133
|
+
continue;
|
|
134
|
+
const delta = {
|
|
135
|
+
id,
|
|
136
|
+
query: cur.case.query,
|
|
137
|
+
mrrBefore: base.mrr,
|
|
138
|
+
mrrAfter: cur.mrr,
|
|
139
|
+
r10Before: base.recallAt10,
|
|
140
|
+
r10After: cur.recallAt10,
|
|
141
|
+
ndcgBefore: base.ndcgAt10,
|
|
142
|
+
ndcgAfter: cur.ndcgAt10,
|
|
143
|
+
};
|
|
144
|
+
const ndcgDelta = delta.ndcgAfter - delta.ndcgBefore;
|
|
145
|
+
if (ndcgDelta > 1e-6)
|
|
146
|
+
improved.push(delta);
|
|
147
|
+
else if (ndcgDelta < -1e-6)
|
|
148
|
+
regressed.push(delta);
|
|
149
|
+
else
|
|
150
|
+
unchanged++;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
aggregate,
|
|
154
|
+
improved: improved.sort((a, b) => (b.ndcgAfter - b.ndcgBefore) - (a.ndcgAfter - a.ndcgBefore)),
|
|
155
|
+
regressed: regressed.sort((a, b) => (a.ndcgAfter - a.ndcgBefore) - (b.ndcgAfter - b.ndcgBefore)),
|
|
156
|
+
unchanged,
|
|
157
|
+
onlyInBaseline: [...baseById.keys()].filter((id) => !curById.has(id)),
|
|
158
|
+
onlyInCurrent: [...curById.keys()].filter((id) => !baseById.has(id)),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Bootstrap — generate a synthetic corpus from current memories
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/**
|
|
165
|
+
* For each memory, take its first 8 content words as a trivial query and
|
|
166
|
+
* expect that memory back. Useful as a smoke test: if recall can't find a
|
|
167
|
+
* memory by its own opening words, something is broken.
|
|
168
|
+
*/
|
|
169
|
+
export function bootstrapCorpus(entries, maxCases = 50) {
|
|
170
|
+
const cases = [];
|
|
171
|
+
for (const e of entries) {
|
|
172
|
+
if (cases.length >= maxCases)
|
|
173
|
+
break;
|
|
174
|
+
const words = e.content.trim().split(/\s+/).filter((w) => w.length > 2);
|
|
175
|
+
if (words.length < 3)
|
|
176
|
+
continue;
|
|
177
|
+
const query = words.slice(0, 8).join(' ');
|
|
178
|
+
cases.push({
|
|
179
|
+
id: `bootstrap_${e.id}`,
|
|
180
|
+
query,
|
|
181
|
+
expectedIds: [e.id],
|
|
182
|
+
description: `trivial self-query on memory ${e.id}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return cases;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=eval.js.map
|
package/dist/eval.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../src/eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuD3C,8EAA8E;AAC9E,0DAA0D;AAC1D,8EAA8E;AAE9E,oEAAoE;AACpE,MAAM,UAAU,GAAG,CAAC,QAAkB,EAAE,QAAkB;IACxD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,SAAS,CAAC,QAAkB,EAAE,QAAkB,EAAE,CAAS;IACzE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,QAAkB,EAAE,QAAkB,EAAE,CAAS;IACvE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACtD,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,kDAAkD;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;AACrC,CAAC;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAiB,EACjB,OAAsB,EACtB,UAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAG,uCAAuC;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,MAAM,aAAa,GAAG,OAAO,CAC3B,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAC7E,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,aAAa;YAC1B,CAAC,CAAC,MAAM,gBAAgB,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,SAAU,EAAE,OAAO,CAAC,UAAW,EAAE;gBACvE,MAAM;gBACN,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,eAAe,EAAE,OAAO,CAAC,eAAe;gBACxC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;aAC7B,CAAC;YACJ,CAAC,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE;gBACnC,MAAM;gBACN,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,eAAe,EAAE,OAAO,CAAC,eAAe;gBACxC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,SAAS,EAAE,OAAO,CAAC,SAAS;aAC7B,CAAC,CAAC;QACP,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,CAAC;YACP,WAAW;YACX,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;YACpC,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YACnD,UAAU,EAAE,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACrD,QAAQ,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;SAClD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IAErE,OAAO;QACL,KAAK,EAAE,OAAO;QACd,OAAO;QACP,aAAa;QACb,cAAc;QACd,YAAY;QACZ,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;KAC/B,CAAC;AACJ,CAAC;AAiCD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAqB,EAAE,OAAoB;IAC1E,MAAM,SAAS,GAAc;QAC3B,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO;QACvC,SAAS,EAAE,OAAO,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa;QACzD,UAAU,EAAE,OAAO,CAAC,cAAc,GAAG,QAAQ,CAAC,cAAc;QAC5D,QAAQ,EAAE,OAAO,CAAC,YAAY,GAAG,QAAQ,CAAC,YAAY;KACvD,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAElE,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,SAAS,GAAgB,EAAE,CAAC;IAClC,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,KAAK,GAAc;YACvB,EAAE;YACF,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK;YACrB,SAAS,EAAE,IAAI,CAAC,GAAG;YACnB,QAAQ,EAAE,GAAG,CAAC,GAAG;YACjB,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,QAAQ,EAAE,GAAG,CAAC,UAAU;YACxB,UAAU,EAAE,IAAI,CAAC,QAAQ;YACzB,SAAS,EAAE,GAAG,CAAC,QAAQ;SACxB,CAAC;QACF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC;QACrD,IAAI,SAAS,GAAG,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACtC,IAAI,SAAS,GAAG,CAAC,IAAI;YAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC7C,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,OAAO;QACL,SAAS;QACT,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAC9F,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAChG,SAAS;QACT,cAAc,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrE,aAAa,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,gEAAgE;AAChE,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAsB,EAAE,QAAQ,GAAG,EAAE;IACnE,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;YAAE,MAAM;QACpC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,aAAa,CAAC,CAAC,EAAE,EAAE;YACvB,KAAK;YACL,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnB,WAAW,EAAE,gCAAgC,CAAC,CAAC,EAAE,EAAE;SACpD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-powered refinement of consolidated semantic memories.
|
|
3
|
+
*
|
|
4
|
+
* The rule-based `mergeContents` in consolidate.ts produces functional but
|
|
5
|
+
* ugly semantic memories — typically "[Consolidated from N related memories]"
|
|
6
|
+
* prepended to the longest source, or a bulleted list. `hippo refine` takes
|
|
7
|
+
* those and asks Claude to synthesize a clean, generalized principle.
|
|
8
|
+
*
|
|
9
|
+
* Design choices:
|
|
10
|
+
* - Separate command (not baked into `hippo sleep`) so API-key users opt in.
|
|
11
|
+
* - Idempotent via the `llm-refined` tag — re-running skips already-refined.
|
|
12
|
+
* - Uses fetch directly so no SDK dependency.
|
|
13
|
+
* - On failure (API error, bad response), the original memory is untouched.
|
|
14
|
+
*/
|
|
15
|
+
import { MemoryEntry } from './memory.js';
|
|
16
|
+
export interface RefineOptions {
|
|
17
|
+
apiKey: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
/** Ignore the llm-refined tag and re-refine everything eligible. */
|
|
22
|
+
all?: boolean;
|
|
23
|
+
/** Injected for testing — defaults to the real fetch. */
|
|
24
|
+
fetcher?: typeof fetch;
|
|
25
|
+
}
|
|
26
|
+
export interface RefineResult {
|
|
27
|
+
scanned: number;
|
|
28
|
+
refined: number;
|
|
29
|
+
skipped: number;
|
|
30
|
+
failed: number;
|
|
31
|
+
details: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
status: 'refined' | 'skipped' | 'failed';
|
|
34
|
+
reason?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ask Claude to synthesize a clean semantic memory from the merged content
|
|
39
|
+
* plus the original source memories. Returns the refined content string or
|
|
40
|
+
* `null` when the API call failed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function refineSemanticMemory(merged: string, sources: MemoryEntry[], opts: {
|
|
43
|
+
apiKey: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
fetcher?: typeof fetch;
|
|
46
|
+
}): Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Scan the store for consolidated semantic memories, refine each with the
|
|
49
|
+
* LLM, and write the refined content back. Tags with `llm-refined` so
|
|
50
|
+
* repeated runs are idempotent (unless `all` is set).
|
|
51
|
+
*/
|
|
52
|
+
export declare function refineStore(hippoRoot: string, opts: RefineOptions): Promise<RefineResult>;
|
|
53
|
+
//# sourceMappingURL=refine-llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refine-llm.d.ts","sourceRoot":"","sources":["../src/refine-llm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAS,MAAM,aAAa,CAAC;AASjD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3F;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAC/D,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqDxB;AAOD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,YAAY,CAAC,CA+DvB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-powered refinement of consolidated semantic memories.
|
|
3
|
+
*
|
|
4
|
+
* The rule-based `mergeContents` in consolidate.ts produces functional but
|
|
5
|
+
* ugly semantic memories — typically "[Consolidated from N related memories]"
|
|
6
|
+
* prepended to the longest source, or a bulleted list. `hippo refine` takes
|
|
7
|
+
* those and asks Claude to synthesize a clean, generalized principle.
|
|
8
|
+
*
|
|
9
|
+
* Design choices:
|
|
10
|
+
* - Separate command (not baked into `hippo sleep`) so API-key users opt in.
|
|
11
|
+
* - Idempotent via the `llm-refined` tag — re-running skips already-refined.
|
|
12
|
+
* - Uses fetch directly so no SDK dependency.
|
|
13
|
+
* - On failure (API error, bad response), the original memory is untouched.
|
|
14
|
+
*/
|
|
15
|
+
import { Layer } from './memory.js';
|
|
16
|
+
import { loadAllEntries, readEntry, writeEntry } from './store.js';
|
|
17
|
+
const REFINED_TAG = 'llm-refined';
|
|
18
|
+
const CONSOLIDATED_MARKERS = [
|
|
19
|
+
'[Consolidated from',
|
|
20
|
+
'[Consolidated pattern from',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Ask Claude to synthesize a clean semantic memory from the merged content
|
|
24
|
+
* plus the original source memories. Returns the refined content string or
|
|
25
|
+
* `null` when the API call failed.
|
|
26
|
+
*/
|
|
27
|
+
export async function refineSemanticMemory(merged, sources, opts) {
|
|
28
|
+
const model = opts.model ?? 'claude-sonnet-4-6';
|
|
29
|
+
const fetchFn = opts.fetcher ?? fetch;
|
|
30
|
+
const sourceBlock = sources
|
|
31
|
+
.slice(0, 8)
|
|
32
|
+
.map((s, i) => `[source ${i + 1}] ${s.content.slice(0, 400)}`)
|
|
33
|
+
.join('\n\n');
|
|
34
|
+
const prompt = `You are refining a semantic memory in an agent's memory store. The rule-based consolidator merged several related episodic memories into one, but the output is clumsy. Produce a single coherent semantic memory that captures the underlying principle.
|
|
35
|
+
|
|
36
|
+
Rules:
|
|
37
|
+
- Output ONLY the refined content — no preamble, no quote marks, no "Here is...".
|
|
38
|
+
- Keep it concise: one paragraph, no headers, no bullet lists unless the sources are inherently a list.
|
|
39
|
+
- Preserve specific facts (names, numbers, paths, IDs) from the sources.
|
|
40
|
+
- Generalize: state the pattern, not each instance.
|
|
41
|
+
- Do NOT include the "[Consolidated from N ...]" marker.
|
|
42
|
+
|
|
43
|
+
Current merged content:
|
|
44
|
+
${merged}
|
|
45
|
+
|
|
46
|
+
Source memories (up to 8 shown):
|
|
47
|
+
${sourceBlock}`;
|
|
48
|
+
let res;
|
|
49
|
+
try {
|
|
50
|
+
res = await fetchFn('https://api.anthropic.com/v1/messages', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'content-type': 'application/json',
|
|
54
|
+
'x-api-key': opts.apiKey,
|
|
55
|
+
'anthropic-version': '2023-06-01',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
model,
|
|
59
|
+
max_tokens: 800,
|
|
60
|
+
messages: [{ role: 'user', content: prompt }],
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (!res.ok)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
const text = data.content?.[0]?.text?.trim() ?? '';
|
|
72
|
+
if (text.length < 10)
|
|
73
|
+
return null;
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isConsolidated(entry) {
|
|
81
|
+
if (entry.layer !== Layer.Semantic)
|
|
82
|
+
return false;
|
|
83
|
+
return CONSOLIDATED_MARKERS.some((m) => entry.content.startsWith(m));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Scan the store for consolidated semantic memories, refine each with the
|
|
87
|
+
* LLM, and write the refined content back. Tags with `llm-refined` so
|
|
88
|
+
* repeated runs are idempotent (unless `all` is set).
|
|
89
|
+
*/
|
|
90
|
+
export async function refineStore(hippoRoot, opts) {
|
|
91
|
+
const result = {
|
|
92
|
+
scanned: 0,
|
|
93
|
+
refined: 0,
|
|
94
|
+
skipped: 0,
|
|
95
|
+
failed: 0,
|
|
96
|
+
details: [],
|
|
97
|
+
};
|
|
98
|
+
const entries = loadAllEntries(hippoRoot);
|
|
99
|
+
let processed = 0;
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (!isConsolidated(entry))
|
|
102
|
+
continue;
|
|
103
|
+
result.scanned++;
|
|
104
|
+
if (!opts.all && entry.tags.includes(REFINED_TAG)) {
|
|
105
|
+
result.skipped++;
|
|
106
|
+
result.details.push({ id: entry.id, status: 'skipped', reason: 'already refined' });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (opts.limit !== undefined && processed >= opts.limit)
|
|
110
|
+
break;
|
|
111
|
+
processed++;
|
|
112
|
+
// Best-effort: walk parents_json (schema v9) to fetch originals. When
|
|
113
|
+
// parents aren't recorded we still refine using just the merged content.
|
|
114
|
+
const sources = [];
|
|
115
|
+
const parentIds = Array.isArray(entry.parents) ? entry.parents : [];
|
|
116
|
+
for (const pid of parentIds) {
|
|
117
|
+
const p = readEntry(hippoRoot, pid);
|
|
118
|
+
if (p)
|
|
119
|
+
sources.push(p);
|
|
120
|
+
}
|
|
121
|
+
const refined = await refineSemanticMemory(entry.content, sources, {
|
|
122
|
+
apiKey: opts.apiKey,
|
|
123
|
+
model: opts.model,
|
|
124
|
+
fetcher: opts.fetcher,
|
|
125
|
+
});
|
|
126
|
+
if (refined === null) {
|
|
127
|
+
result.failed++;
|
|
128
|
+
result.details.push({ id: entry.id, status: 'failed', reason: 'api error or empty response' });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (opts.dryRun) {
|
|
132
|
+
result.refined++;
|
|
133
|
+
result.details.push({ id: entry.id, status: 'refined', reason: 'dry-run (no write)' });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const updated = {
|
|
137
|
+
...entry,
|
|
138
|
+
content: refined,
|
|
139
|
+
tags: entry.tags.includes(REFINED_TAG) ? entry.tags : [...entry.tags, REFINED_TAG],
|
|
140
|
+
};
|
|
141
|
+
writeEntry(hippoRoot, updated);
|
|
142
|
+
result.refined++;
|
|
143
|
+
result.details.push({ id: entry.id, status: 'refined' });
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=refine-llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refine-llm.js","sourceRoot":"","sources":["../src/refine-llm.ts"],"names":[],"mappings":"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"}
|