@mingxy/cerebro 1.11.15 → 1.12.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/package.json +1 -1
- package/schema.json +55 -0
- package/src/client.ts +48 -2
- package/src/config.ts +24 -0
- package/src/hooks.ts +70 -10
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -96,6 +96,61 @@
|
|
|
96
96
|
"default": 10,
|
|
97
97
|
"minimum": 1,
|
|
98
98
|
"maximum": 50
|
|
99
|
+
},
|
|
100
|
+
"fetchMultiplier": {
|
|
101
|
+
"type": "number",
|
|
102
|
+
"description": "Search breadth multiplier: fetch_limit = max_results * N",
|
|
103
|
+
"default": 3,
|
|
104
|
+
"minimum": 1,
|
|
105
|
+
"maximum": 10
|
|
106
|
+
},
|
|
107
|
+
"topkCapMultiplier": {
|
|
108
|
+
"type": "number",
|
|
109
|
+
"description": "Candidate cap multiplier: topk_cap = max_results * N",
|
|
110
|
+
"default": 2,
|
|
111
|
+
"minimum": 1,
|
|
112
|
+
"maximum": 10
|
|
113
|
+
},
|
|
114
|
+
"mmrJaccardThreshold": {
|
|
115
|
+
"type": "number",
|
|
116
|
+
"description": "Jaccard similarity threshold for MMR diversity penalty",
|
|
117
|
+
"default": 0.85,
|
|
118
|
+
"minimum": 0.0,
|
|
119
|
+
"maximum": 1.0
|
|
120
|
+
},
|
|
121
|
+
"mmrPenaltyFactor": {
|
|
122
|
+
"type": "number",
|
|
123
|
+
"description": "Score penalty factor for similar memories in MMR diversity",
|
|
124
|
+
"default": 0.5,
|
|
125
|
+
"minimum": 0.0,
|
|
126
|
+
"maximum": 1.0
|
|
127
|
+
},
|
|
128
|
+
"phase2Multiplier": {
|
|
129
|
+
"type": "number",
|
|
130
|
+
"description": "Phase2 global fallback search multiplier",
|
|
131
|
+
"default": 2,
|
|
132
|
+
"minimum": 1,
|
|
133
|
+
"maximum": 10
|
|
134
|
+
},
|
|
135
|
+
"llmMaxEval": {
|
|
136
|
+
"type": "number",
|
|
137
|
+
"description": "Maximum candidates sent to LLM for relevance evaluation",
|
|
138
|
+
"default": 15,
|
|
139
|
+
"minimum": 1,
|
|
140
|
+
"maximum": 50
|
|
141
|
+
},
|
|
142
|
+
"refineStrategy": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"description": "LLM refinement strategy: strict (high only), balanced (high+medium), loose (keep all)",
|
|
145
|
+
"enum": ["strict", "balanced", "loose"],
|
|
146
|
+
"default": "balanced"
|
|
147
|
+
},
|
|
148
|
+
"refineMediumChars": {
|
|
149
|
+
"type": "number",
|
|
150
|
+
"description": "Character limit for medium-relevance content truncation",
|
|
151
|
+
"default": 200,
|
|
152
|
+
"minimum": 50,
|
|
153
|
+
"maximum": 2000
|
|
99
154
|
}
|
|
100
155
|
},
|
|
101
156
|
"additionalProperties": false
|
package/src/client.ts
CHANGED
|
@@ -55,6 +55,14 @@ export interface ClusteredRecallResult {
|
|
|
55
55
|
standalone_memories: MemoryDto[];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export interface DiscardedItem {
|
|
59
|
+
memory_id: string;
|
|
60
|
+
content: string;
|
|
61
|
+
score: number;
|
|
62
|
+
refine_relevance?: string;
|
|
63
|
+
refine_reasoning?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
export interface ShouldRecallResponse {
|
|
59
67
|
should_recall: boolean;
|
|
60
68
|
query?: string;
|
|
@@ -62,8 +70,8 @@ export interface ShouldRecallResponse {
|
|
|
62
70
|
similarity_score?: number;
|
|
63
71
|
confidence?: number;
|
|
64
72
|
memories?: SearchResult[];
|
|
73
|
+
discarded?: DiscardedItem[];
|
|
65
74
|
clustered?: ClusteredRecallResult;
|
|
66
|
-
event_id?: string;
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
export interface MemoryRelation {
|
|
@@ -332,6 +340,16 @@ export class CerebroClient {
|
|
|
332
340
|
max_results?: number,
|
|
333
341
|
project_tags?: string[],
|
|
334
342
|
conversation_context?: string[],
|
|
343
|
+
recall_overrides?: {
|
|
344
|
+
fetch_multiplier?: number;
|
|
345
|
+
topk_cap_multiplier?: number;
|
|
346
|
+
mmr_jaccard_threshold?: number;
|
|
347
|
+
mmr_penalty_factor?: number;
|
|
348
|
+
phase2_multiplier?: number;
|
|
349
|
+
llm_max_eval?: number;
|
|
350
|
+
refine_strategy?: string;
|
|
351
|
+
refine_medium_chars?: number;
|
|
352
|
+
},
|
|
335
353
|
): Promise<ShouldRecallResponse | null> {
|
|
336
354
|
const res = await this.post<ShouldRecallResponse>("/v1/should-recall", {
|
|
337
355
|
query_text,
|
|
@@ -341,6 +359,7 @@ export class CerebroClient {
|
|
|
341
359
|
max_results,
|
|
342
360
|
project_tags,
|
|
343
361
|
conversation_context,
|
|
362
|
+
...recall_overrides,
|
|
344
363
|
}, 20_000);
|
|
345
364
|
return res;
|
|
346
365
|
}
|
|
@@ -348,15 +367,42 @@ export class CerebroClient {
|
|
|
348
367
|
async updateProfileInjected(
|
|
349
368
|
event_id: string,
|
|
350
369
|
profile_injected: boolean,
|
|
370
|
+
profile_content?: string,
|
|
351
371
|
): Promise<unknown | null> {
|
|
372
|
+
const body: Record<string, unknown> = { profile_injected };
|
|
373
|
+
if (profile_content !== undefined) {
|
|
374
|
+
body.profile_content = profile_content;
|
|
375
|
+
}
|
|
352
376
|
const res = await this.patch(
|
|
353
377
|
`/v1/recall-events/${event_id}/profile-injected`,
|
|
354
|
-
|
|
378
|
+
body,
|
|
355
379
|
10_000,
|
|
356
380
|
);
|
|
357
381
|
return res;
|
|
358
382
|
}
|
|
359
383
|
|
|
384
|
+
async createRecallEvent(params: {
|
|
385
|
+
session_id: string;
|
|
386
|
+
recall_type?: string;
|
|
387
|
+
query_text: string;
|
|
388
|
+
max_score: number;
|
|
389
|
+
llm_confidence: number;
|
|
390
|
+
profile_injected: boolean;
|
|
391
|
+
kept_count: number;
|
|
392
|
+
discarded_count: number;
|
|
393
|
+
injected_count: number;
|
|
394
|
+
profile_content?: string;
|
|
395
|
+
items?: Array<{
|
|
396
|
+
memory_id: string;
|
|
397
|
+
score: number;
|
|
398
|
+
refine_relevance?: string;
|
|
399
|
+
refine_reasoning?: string;
|
|
400
|
+
is_kept: boolean;
|
|
401
|
+
}>;
|
|
402
|
+
}): Promise<{ ok: boolean; event_id?: string } | null> {
|
|
403
|
+
return this.post("/v1/recall-events", params, 10_000);
|
|
404
|
+
}
|
|
405
|
+
|
|
360
406
|
async sessionIngest(
|
|
361
407
|
messages: Array<{ role: string; content: string }>,
|
|
362
408
|
sessionId?: string,
|
package/src/config.ts
CHANGED
|
@@ -22,6 +22,14 @@ export interface OmemPluginConfig {
|
|
|
22
22
|
recall: {
|
|
23
23
|
similarityThreshold: number;
|
|
24
24
|
maxRecallResults: number;
|
|
25
|
+
fetchMultiplier: number;
|
|
26
|
+
topkCapMultiplier: number;
|
|
27
|
+
mmrJaccardThreshold: number;
|
|
28
|
+
mmrPenaltyFactor: number;
|
|
29
|
+
phase2Multiplier: number;
|
|
30
|
+
llmMaxEval: number;
|
|
31
|
+
refineStrategy: "strict" | "balanced" | "loose";
|
|
32
|
+
refineMediumChars: number;
|
|
25
33
|
};
|
|
26
34
|
logging: {
|
|
27
35
|
logEnabled: boolean;
|
|
@@ -55,6 +63,14 @@ const DEFAULTS: OmemPluginConfig = {
|
|
|
55
63
|
recall: {
|
|
56
64
|
similarityThreshold: 0.4,
|
|
57
65
|
maxRecallResults: 10,
|
|
66
|
+
fetchMultiplier: 3,
|
|
67
|
+
topkCapMultiplier: 2,
|
|
68
|
+
mmrJaccardThreshold: 0.85,
|
|
69
|
+
mmrPenaltyFactor: 0.5,
|
|
70
|
+
phase2Multiplier: 2,
|
|
71
|
+
llmMaxEval: 15,
|
|
72
|
+
refineStrategy: "balanced",
|
|
73
|
+
refineMediumChars: 200,
|
|
58
74
|
},
|
|
59
75
|
logging: {
|
|
60
76
|
logEnabled: true,
|
|
@@ -111,6 +127,14 @@ function migrateFlatToNested(flat: FlatConfig): OmemPluginConfig {
|
|
|
111
127
|
recall: {
|
|
112
128
|
similarityThreshold: flat.similarityThreshold ?? DEFAULTS.recall.similarityThreshold,
|
|
113
129
|
maxRecallResults: flat.maxRecallResults ?? DEFAULTS.recall.maxRecallResults,
|
|
130
|
+
fetchMultiplier: DEFAULTS.recall.fetchMultiplier,
|
|
131
|
+
topkCapMultiplier: DEFAULTS.recall.topkCapMultiplier,
|
|
132
|
+
mmrJaccardThreshold: DEFAULTS.recall.mmrJaccardThreshold,
|
|
133
|
+
mmrPenaltyFactor: DEFAULTS.recall.mmrPenaltyFactor,
|
|
134
|
+
phase2Multiplier: DEFAULTS.recall.phase2Multiplier,
|
|
135
|
+
llmMaxEval: DEFAULTS.recall.llmMaxEval,
|
|
136
|
+
refineStrategy: DEFAULTS.recall.refineStrategy,
|
|
137
|
+
refineMediumChars: DEFAULTS.recall.refineMediumChars,
|
|
114
138
|
},
|
|
115
139
|
logging: {
|
|
116
140
|
logEnabled: flat.logEnabled ?? DEFAULTS.logging.logEnabled,
|
package/src/hooks.ts
CHANGED
|
@@ -299,6 +299,14 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
299
299
|
export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}, getAgentName?: () => string) {
|
|
300
300
|
const similarityThreshold = config.recall?.similarityThreshold ?? 0.4;
|
|
301
301
|
const maxRecallResults = config.recall?.maxRecallResults ?? 10;
|
|
302
|
+
const fetchMultiplier = config.recall?.fetchMultiplier ?? 3;
|
|
303
|
+
const topkCapMultiplier = config.recall?.topkCapMultiplier ?? 2;
|
|
304
|
+
const mmrJaccardThreshold = config.recall?.mmrJaccardThreshold ?? 0.85;
|
|
305
|
+
const mmrPenaltyFactor = config.recall?.mmrPenaltyFactor ?? 0.5;
|
|
306
|
+
const phase2Multiplier = config.recall?.phase2Multiplier ?? 2;
|
|
307
|
+
const llmMaxEval = config.recall?.llmMaxEval ?? 15;
|
|
308
|
+
const refineStrategy = config.recall?.refineStrategy ?? "balanced";
|
|
309
|
+
const refineMediumChars = config.recall?.refineMediumChars ?? 200;
|
|
302
310
|
const maxContentLength = Math.max(MIN_CONTENT_LENGTH, config.content?.maxContentLength ?? 500);
|
|
303
311
|
const maxContentChars = Math.max(MIN_CONTENT_CHARS, config.content?.maxContentChars ?? 30000);
|
|
304
312
|
const toastDelayMs = config.ui?.toastDelayMs ?? 7000;
|
|
@@ -348,13 +356,23 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
348
356
|
similarityThreshold, maxRecallResults,
|
|
349
357
|
projectTags.length > 0 ? projectTags : undefined,
|
|
350
358
|
conversationContext && conversationContext.length > 0 ? conversationContext : undefined,
|
|
359
|
+
{
|
|
360
|
+
fetch_multiplier: fetchMultiplier,
|
|
361
|
+
topk_cap_multiplier: topkCapMultiplier,
|
|
362
|
+
mmr_jaccard_threshold: mmrJaccardThreshold,
|
|
363
|
+
mmr_penalty_factor: mmrPenaltyFactor,
|
|
364
|
+
phase2_multiplier: phase2Multiplier,
|
|
365
|
+
llm_max_eval: llmMaxEval,
|
|
366
|
+
refine_strategy: refineStrategy,
|
|
367
|
+
refine_medium_chars: refineMediumChars,
|
|
368
|
+
},
|
|
351
369
|
);
|
|
352
370
|
|
|
353
371
|
if (!shouldRecallRes) {
|
|
354
372
|
showToast(tui, "🧠 Cerebro Service Unavailable", "Unable to reach memory API · check connection", "error", toastDelayMs);
|
|
355
373
|
return;
|
|
356
374
|
}
|
|
357
|
-
logDebug("autoRecallHook shouldRecall result", { shouldRecall: shouldRecallRes.should_recall, confidence: shouldRecallRes.confidence, memCount: shouldRecallRes.memories?.length ?? 0, clustered: !!shouldRecallRes.clustered });
|
|
375
|
+
logDebug("autoRecallHook shouldRecall result", { shouldRecall: shouldRecallRes.should_recall, confidence: shouldRecallRes.confidence, memCount: shouldRecallRes.memories?.length ?? 0, discardedCount: shouldRecallRes.discarded?.length ?? 0, clustered: !!shouldRecallRes.clustered });
|
|
358
376
|
|
|
359
377
|
const profile = await client.getProfile();
|
|
360
378
|
let profileInjected = false;
|
|
@@ -398,10 +416,56 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
398
416
|
}
|
|
399
417
|
}
|
|
400
418
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
419
|
+
const storedMemoryIds = shouldRecallRes.memories?.map((r) => r.memory.id) ?? [];
|
|
420
|
+
const storedDiscardedIds = shouldRecallRes.discarded?.map((d) => d.memory_id) ?? [];
|
|
421
|
+
const maxScore = storedMemoryIds.length > 0
|
|
422
|
+
? Math.max(...(shouldRecallRes.memories?.map((r) => r.score) ?? [0]))
|
|
423
|
+
: 0;
|
|
424
|
+
|
|
425
|
+
const createEventAndReturn = async (
|
|
426
|
+
injectedCount: number,
|
|
427
|
+
keptCount: number,
|
|
428
|
+
discardedCount: number,
|
|
429
|
+
): Promise<string | undefined> => {
|
|
430
|
+
try {
|
|
431
|
+
const items = [
|
|
432
|
+
...(shouldRecallRes.memories?.map((r) => ({
|
|
433
|
+
memory_id: r.memory.id,
|
|
434
|
+
score: r.score,
|
|
435
|
+
refine_relevance: r.refine_relevance,
|
|
436
|
+
refine_reasoning: r.refine_reasoning,
|
|
437
|
+
is_kept: true,
|
|
438
|
+
})) ?? []),
|
|
439
|
+
...(shouldRecallRes.discarded?.map((d) => ({
|
|
440
|
+
memory_id: d.memory_id,
|
|
441
|
+
score: d.score,
|
|
442
|
+
refine_relevance: d.refine_relevance,
|
|
443
|
+
refine_reasoning: d.refine_reasoning,
|
|
444
|
+
is_kept: false,
|
|
445
|
+
})) ?? []),
|
|
446
|
+
];
|
|
447
|
+
const result = await client.createRecallEvent({
|
|
448
|
+
session_id: input.sessionID!,
|
|
449
|
+
recall_type: "auto",
|
|
450
|
+
query_text,
|
|
451
|
+
max_score: maxScore,
|
|
452
|
+
llm_confidence: shouldRecallRes.confidence ?? 0,
|
|
453
|
+
profile_injected: profileInjected,
|
|
454
|
+
kept_count: keptCount,
|
|
455
|
+
discarded_count: discardedCount,
|
|
456
|
+
injected_count: injectedCount,
|
|
457
|
+
profile_content: profileInjected && profileBlock ? profileBlock : undefined,
|
|
458
|
+
items: items.length > 0 ? items : undefined,
|
|
459
|
+
});
|
|
460
|
+
return result?.event_id;
|
|
461
|
+
} catch (e) {
|
|
462
|
+
logErr("autoRecallHook createRecallEvent failed", { error: String(e) });
|
|
463
|
+
return undefined;
|
|
404
464
|
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
if (!shouldRecallRes.should_recall) {
|
|
468
|
+
await createEventAndReturn(0, 0, storedDiscardedIds.length);
|
|
405
469
|
if (profileInjected && isFirstInjection) {
|
|
406
470
|
showToast(tui, "👨 Profile Injected", `${profileCountText} · no memory recall needed`, "success", toastDelayMs);
|
|
407
471
|
}
|
|
@@ -415,9 +479,7 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
415
479
|
const newResults = results.filter((r) => !existingIds.has(r.memory.id));
|
|
416
480
|
logDebug("autoRecallHook dedup", { totalResults: results.length, existingCount: existingIds.size, newCount: newResults.length });
|
|
417
481
|
if (newResults.length === 0) {
|
|
418
|
-
|
|
419
|
-
await client.updateProfileInjected(shouldRecallRes.event_id, true).catch(() => {});
|
|
420
|
-
}
|
|
482
|
+
await createEventAndReturn(0, storedMemoryIds.length, storedDiscardedIds.length);
|
|
421
483
|
if (profileInjected && isFirstInjection) {
|
|
422
484
|
showToast(tui, "👨 Profile Injected", `${profileCountText} · all memories already injected`, "success", toastDelayMs);
|
|
423
485
|
}
|
|
@@ -453,9 +515,7 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
453
515
|
injectedMemoryIds.set(input.sessionID, new Set([...existingIds, ...newIds]));
|
|
454
516
|
logDebug("autoRecallHook injection complete", { newIds: newIds.length, clustered: !!clustered });
|
|
455
517
|
|
|
456
|
-
|
|
457
|
-
await client.updateProfileInjected(shouldRecallRes.event_id, true).catch(() => {});
|
|
458
|
-
}
|
|
518
|
+
await createEventAndReturn(newResults.length, storedMemoryIds.length, storedDiscardedIds.length);
|
|
459
519
|
|
|
460
520
|
const memDynamic = newResults.filter((r) => r.memory.memory_type === "fact" || r.memory.memory_type === "event").length;
|
|
461
521
|
const memStatic = newResults.filter((r) => r.memory.memory_type === "pinned" || r.memory.memory_type === "preference").length;
|