Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/retriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export interface RetrievalConfig {
recencyHalfLifeDays: number;
/** Max recency boost factor (default: 0.10) */
recencyWeight: number;
/** Minutes window for fresh memory boost. Memories stored within this window
* get an additional score bonus (default: 30). Set to 0 to disable. */
freshMemoryBoostMinutes?: number;
/** Base score boost for fresh memories (default: 0.15) */
freshMemoryBoostWeight?: number;
/** Filter noise from results (default: true) */
filterNoise: boolean;
/** Reranker API key (enables cross-encoder reranking) */
Expand Down Expand Up @@ -1364,6 +1369,25 @@ export class MemoryRetriever {
};
});

// 新鲜记忆加分:窗口内存储的记忆获得额外分数,
// 确保纠正信息能排在高频旧记忆之前。
const freshWindowMinutes = this.config.freshMemoryBoostMinutes ?? 30;
const freshWeight = this.config.freshMemoryBoostWeight ?? 0.15;
if (freshWindowMinutes > 0) {
const freshWindowMs = freshWindowMinutes * 60_000;
for (const r of boosted) {
const ts = r.entry.timestamp && r.entry.timestamp > 0 ? r.entry.timestamp : now;
const ageMs = now - ts;
if (ageMs < freshWindowMs) {
let bonus = freshWeight;
if (r.entry.category === "reflection" || r.entry.category === "preference") {
bonus += 0.05;
}
r.score = clamp01(r.score + bonus, r.score);
}
}
}

return boosted.sort((a, b) => b.score - a.score);
}

Expand Down
99 changes: 99 additions & 0 deletions test/fresh-memory-boost.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";

/**
* 新鲜记忆 recency boost 测试。
*
* 验证在 freshMemoryBoostMinutes 窗口内存储的记忆会获得额外加分,
* 确保纠正信息和最新上下文能排在高访问量旧记忆之前。
*/

// 模拟检索结果工厂
function makeResult(id, score, timestampMinutesAgo, category = "fact") {
const now = Date.now();
return {
entry: {
id,
text: `memory ${id}`,
category,
scope: "agent:test",
importance: 0.8,
timestamp: now - timestampMinutesAgo * 60_000,
vector: [0.1, 0.2, 0.3],
metadata: "{}",
},
score,
sources: {},
};
}

describe("fresh memory boost", () => {
it("应对窗口内的记忆加分", () => {
const oldMemory = makeResult("old-1", 0.65, 120);
const freshMemory = makeResult("fresh-1", 0.55, 5);

const freshBoost = 0.15;
const windowMinutes = 30;

const now = Date.now();
const results = [oldMemory, freshMemory].map((r) => {
const ageMinutes = (now - r.entry.timestamp) / 60_000;
if (ageMinutes < windowMinutes) {
return { ...r, score: Math.min(1, r.score + freshBoost) };
}
return r;
});

results.sort((a, b) => b.score - a.score);

assert.equal(results[0].entry.id, "fresh-1");
assert.ok(results[0].score > results[1].score);
});

it("应对 reflection/preference 类别额外加分", () => {
const freshFact = makeResult("fact-1", 0.50, 10, "fact");
const freshReflection = makeResult("refl-1", 0.50, 10, "reflection");

const freshBoost = 0.15;
const categoryBonus = 0.05;
const windowMinutes = 30;

const now = Date.now();
const results = [freshFact, freshReflection].map((r) => {
const ageMinutes = (now - r.entry.timestamp) / 60_000;
if (ageMinutes < windowMinutes) {
let boost = freshBoost;
if (r.entry.category === "reflection" || r.entry.category === "preference") {
boost += categoryBonus;
}
return { ...r, score: Math.min(1, r.score + boost) };
}
return r;
});

results.sort((a, b) => b.score - a.score);

assert.equal(results[0].entry.id, "refl-1");
assert.ok(results[0].score - results[1].score >= 0.04);
});

it("不应对窗口外的记忆加分", () => {
const oldMemory = makeResult("old-1", 0.60, 60);

const windowMinutes = 30;
const now = Date.now();
const ageMinutes = (now - oldMemory.entry.timestamp) / 60_000;

assert.ok(ageMinutes > windowMinutes);
assert.equal(oldMemory.score, 0.60);
});

it("freshMemoryBoostMinutes 为 0 时应禁用加分", () => {
const freshMemory = makeResult("fresh-1", 0.55, 5);
const windowMinutes = 0;

const boosted = windowMinutes > 0;
assert.equal(boosted, false);
assert.equal(freshMemory.score, 0.55);
});
});
Loading