Memory/QMD: normalize Han-script BM25 search queries

This commit is contained in:
Vignesh Natarajan
2026-02-22 01:53:00 -08:00
parent 9f0b6a8c92
commit 99a2f5379e
3 changed files with 156 additions and 2 deletions

View File

@@ -729,6 +729,121 @@ describe("QmdMemoryManager", () => {
await manager.close();
});
it("normalizes mixed Han-script BM25 queries before qmd search", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
searchMode: "search",
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args[0] === "search") {
const child = createMockChild({ autoClose: false });
emitAndClose(child, "stdout", "[]");
return child;
}
return createMockChild();
});
const { manager, resolved } = await createManager();
const maxResults = resolved.qmd?.limits.maxResults;
if (!maxResults) {
throw new Error("qmd maxResults missing");
}
await expect(
manager.search("記憶系統升級 QMD", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
const searchCall = spawnMock.mock.calls.find(
(call: unknown[]) => (call[1] as string[])?.[0] === "search",
);
expect(searchCall?.[1]).toEqual([
"search",
"記憶 憶系 系統 統升 升級 qmd",
"--json",
"-n",
String(maxResults),
"-c",
"workspace-main",
]);
await manager.close();
});
it("falls back to the original query when Han normalization yields no BM25 tokens", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
searchMode: "search",
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args[0] === "search") {
const child = createMockChild({ autoClose: false });
emitAndClose(child, "stdout", "[]");
return child;
}
return createMockChild();
});
const { manager } = await createManager();
await expect(manager.search("記", { sessionKey: "agent:main:slack:dm:u123" })).resolves.toEqual(
[],
);
const searchCall = spawnMock.mock.calls.find(
(call: unknown[]) => (call[1] as string[])?.[0] === "search",
);
expect(searchCall?.[1]?.[1]).toBe("記");
await manager.close();
});
it("keeps original Han queries in qmd query mode", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
searchMode: "query",
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args[0] === "query") {
const child = createMockChild({ autoClose: false });
emitAndClose(child, "stdout", "[]");
return child;
}
return createMockChild();
});
const { manager } = await createManager();
await expect(
manager.search("記憶系統升級 QMD", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
const queryCall = spawnMock.mock.calls.find(
(call: unknown[]) => (call[1] as string[])?.[0] === "query",
);
expect(queryCall?.[1]?.[1]).toBe("記憶系統升級 QMD");
await manager.close();
});
it("retries search with qmd query when configured mode rejects flags", async () => {
cfg = {
...cfg,