mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-20 07:54:36 +00:00
Memory: keep keyword hits when hybrid vector misses
This commit is contained in:
@@ -81,6 +81,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Web UI/Assistant text: strip internal `<relevant-memories>...</relevant-memories>` scaffolding from rendered assistant messages (while preserving code-fence literals), preventing memory-context leakage in chat output for models that echo internal blocks. (#29851) Thanks @Valkster70.
|
||||
- Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz.
|
||||
- TUI/Session model status: clear stale runtime model identity when model overrides change so `/model` updates are reflected immediately in `sessions.patch` responses and `sessions.list` status surfaces. (#28619) Thanks @lejean2000.
|
||||
- Memory/Hybrid recall: when strict hybrid scoring yields no hits, preserve keyword-backed matches using a text-weight floor so freshly indexed lexical canaries no longer disappear behind `minScore` filtering. (#29112) Thanks @ceo-nada.
|
||||
- Podman/Quadlet setup: fix `sed` escaping and UID mismatch in Podman Quadlet setup. (#26414) Thanks @KnHack and @vincentkoc.
|
||||
- Browser/Navigate: resolve the correct `targetId` in navigate responses after renderer swaps. (#25326) Thanks @stone-jin and @vincentkoc.
|
||||
- Agents/Ollama discovery: skip Ollama discovery when explicit models are configured. (#28827) Thanks @Kansodata and @vincentkoc.
|
||||
|
||||
@@ -98,6 +98,7 @@ describe("memory index", () => {
|
||||
model?: string;
|
||||
vectorEnabled?: boolean;
|
||||
cacheEnabled?: boolean;
|
||||
minScore?: number;
|
||||
hybrid?: { enabled: boolean; vectorWeight?: number; textWeight?: number };
|
||||
}): TestCfg {
|
||||
return {
|
||||
@@ -112,7 +113,7 @@ describe("memory index", () => {
|
||||
chunking: { tokens: 4000, overlap: 0 },
|
||||
sync: { watch: false, onSessionStart: false, onSearch: true },
|
||||
query: {
|
||||
minScore: 0,
|
||||
minScore: params.minScore ?? 0,
|
||||
hybrid: params.hybrid ?? { enabled: false },
|
||||
},
|
||||
cache: params.cacheEnabled ? { enabled: true } : undefined,
|
||||
@@ -367,6 +368,25 @@ describe("memory index", () => {
|
||||
expect(results[0]?.path).toContain("memory/2026-01-12.md");
|
||||
});
|
||||
|
||||
it("preserves keyword-only hybrid hits when minScore exceeds text weight", async () => {
|
||||
const cfg = createCfg({
|
||||
storePath: indexMainPath,
|
||||
minScore: 0.35,
|
||||
hybrid: { enabled: true, vectorWeight: 0.7, textWeight: 0.3 },
|
||||
});
|
||||
const manager = await getPersistentManager(cfg);
|
||||
|
||||
const status = manager.status();
|
||||
if (!status.fts?.available) {
|
||||
return;
|
||||
}
|
||||
|
||||
await manager.sync({ reason: "test" });
|
||||
const results = await manager.search("zebra");
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0]?.path).toContain("memory/2026-01-12.md");
|
||||
});
|
||||
|
||||
it("reports vector availability after probe", async () => {
|
||||
const cfg = createCfg({ storePath: indexVectorPath, vectorEnabled: true });
|
||||
const manager = await getPersistentManager(cfg);
|
||||
|
||||
@@ -311,8 +311,28 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
|
||||
mmr: hybrid.mmr,
|
||||
temporalDecay: hybrid.temporalDecay,
|
||||
});
|
||||
const strict = merged.filter((entry) => entry.score >= minScore);
|
||||
if (strict.length > 0 || keywordResults.length === 0) {
|
||||
return strict.slice(0, maxResults);
|
||||
}
|
||||
|
||||
return merged.filter((entry) => entry.score >= minScore).slice(0, maxResults);
|
||||
// Hybrid defaults can produce keyword-only matches with max score equal to
|
||||
// textWeight (for example 0.3). If minScore is higher (for example 0.35),
|
||||
// these exact lexical hits get filtered out even when they are the only
|
||||
// relevant results.
|
||||
const relaxedMinScore = Math.min(minScore, hybrid.textWeight);
|
||||
const keywordKeys = new Set(
|
||||
keywordResults.map(
|
||||
(entry) => `${entry.source}:${entry.path}:${entry.startLine}:${entry.endLine}`,
|
||||
),
|
||||
);
|
||||
return merged
|
||||
.filter(
|
||||
(entry) =>
|
||||
keywordKeys.has(`${entry.source}:${entry.path}:${entry.startLine}:${entry.endLine}`) &&
|
||||
entry.score >= relaxedMinScore,
|
||||
)
|
||||
.slice(0, maxResults);
|
||||
}
|
||||
|
||||
private async searchVector(
|
||||
|
||||
Reference in New Issue
Block a user