perf: extract memory search preflight helpers

This commit is contained in:
Peter Steinberger
2026-04-06 21:27:41 +01:00
parent 5b6e552b51
commit ca27d932b4
6 changed files with 125 additions and 55 deletions

View File

@@ -485,48 +485,6 @@ describe("memory index", () => {
}
});
it("passes Gemini outputDimensionality from config into the provider", async () => {
const cfg = createCfg({
storePath: indexMainPath,
provider: "gemini",
model: "gemini-embedding-2-preview",
outputDimensionality: 1536,
});
const result = await getMemorySearchManager({ cfg, agentId: "main" });
const manager = requireManager(result);
await manager.probeEmbeddingAvailability();
expect(
providerCalls.some(
(call) =>
call.provider === "gemini" &&
call.model === "gemini-embedding-2-preview" &&
call.outputDimensionality === 1536,
),
).toBe(true);
await manager.close?.();
});
it("does not initialize the provider when searching an empty index", async () => {
const cfg = createCfg({
storePath: path.join(workspaceDir, `index-empty-${randomUUID()}.sqlite`),
provider: "gemini",
model: "gemini-embedding-2-preview",
outputDimensionality: 1536,
onSearch: false,
});
const result = await getMemorySearchManager({ cfg, agentId: "main" });
const manager = requireManager(result);
const results = await manager.search("hello");
expect(results).toEqual([]);
expect(providerCalls).toEqual([]);
await manager.close?.();
});
it("reuses cached embeddings on forced reindex", async () => {
const cfg = createCfg({ storePath: indexMainPath, cacheEnabled: true });
const manager = await getPersistentManager(cfg);

View File

@@ -17,6 +17,26 @@ export type MemoryResolvedProviderState = {
providerRuntime?: EmbeddingProviderRuntime;
};
export function resolveMemoryPrimaryProviderRequest(params: {
settings: ResolvedMemorySearchConfig;
}): {
provider: string;
model: string;
remote: ResolvedMemorySearchConfig["remote"];
outputDimensionality: ResolvedMemorySearchConfig["outputDimensionality"];
fallback: ResolvedMemorySearchConfig["fallback"];
local: ResolvedMemorySearchConfig["local"];
} {
return {
provider: params.settings.provider,
model: params.settings.model,
remote: params.settings.remote,
outputDimensionality: params.settings.outputDimensionality,
fallback: params.settings.fallback,
local: params.settings.local,
};
}
export function resolveMemoryProviderState(
result: Pick<
EmbeddingProviderResult,

View File

@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { resolveMemorySearchPreflight } from "./manager-search-preflight.js";
describe("memory manager search preflight", () => {
it("skips search and provider init for blank queries", () => {
expect(
resolveMemorySearchPreflight({
query: " ",
hasIndexedContent: true,
}),
).toEqual({
normalizedQuery: "",
shouldInitializeProvider: false,
shouldSearch: false,
});
});
it("skips provider init when the index is empty", () => {
expect(
resolveMemorySearchPreflight({
query: "hello",
hasIndexedContent: false,
}),
).toEqual({
normalizedQuery: "hello",
shouldInitializeProvider: false,
shouldSearch: false,
});
});
it("allows provider init when query and indexed content are present", () => {
expect(
resolveMemorySearchPreflight({
query: " hello ",
hasIndexedContent: true,
}),
).toEqual({
normalizedQuery: "hello",
shouldInitializeProvider: true,
shouldSearch: true,
});
});
});

View File

@@ -0,0 +1,32 @@
export function resolveMemorySearchPreflight(params: { query: string; hasIndexedContent: boolean }):
| {
normalizedQuery: string;
shouldInitializeProvider: boolean;
shouldSearch: true;
}
| {
normalizedQuery: string;
shouldInitializeProvider: false;
shouldSearch: false;
} {
const normalizedQuery = params.query.trim();
if (!normalizedQuery) {
return {
normalizedQuery,
shouldInitializeProvider: false,
shouldSearch: false,
};
}
if (!params.hasIndexedContent) {
return {
normalizedQuery,
shouldInitializeProvider: false,
shouldSearch: false,
};
}
return {
normalizedQuery,
shouldInitializeProvider: true,
shouldSearch: true,
};
}

View File

@@ -6,6 +6,7 @@ import { describe, expect, it, vi } from "vitest";
import {
applyMemoryFallbackProviderState,
resolveMemoryFallbackProviderRequest,
resolveMemoryPrimaryProviderRequest,
resolveMemoryProviderState,
} from "./manager-provider-state.js";
@@ -114,4 +115,19 @@ describe("memory manager mistral provider wiring", () => {
expect(request?.model).toBe(DEFAULT_OLLAMA_EMBEDDING_MODEL);
expect(request?.fallback).toBe("none");
});
it("includes outputDimensionality in the primary provider request", () => {
const request = resolveMemoryPrimaryProviderRequest({
settings: {
...createSettings({ provider: "mistral" }),
provider: "gemini",
model: "gemini-embedding-2-preview",
outputDimensionality: 1536,
} as ResolvedMemorySearchConfig,
});
expect(request.provider).toBe("gemini");
expect(request.model).toBe("gemini-embedding-2-preview");
expect(request.outputDimensionality).toBe(1536);
});
});

View File

@@ -34,7 +34,11 @@ import {
resolveSingletonManagedCache,
} from "./manager-cache.js";
import { MemoryManagerEmbeddingOps } from "./manager-embedding-ops.js";
import { resolveMemoryProviderState } from "./manager-provider-state.js";
import {
resolveMemoryPrimaryProviderRequest,
resolveMemoryProviderState,
} from "./manager-provider-state.js";
import { resolveMemorySearchPreflight } from "./manager-search-preflight.js";
import { searchKeyword, searchVector } from "./manager-search.js";
import {
collectMemoryStatusAggregate,
@@ -142,12 +146,7 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
return await createEmbeddingProvider({
config: params.cfg,
agentDir: resolveAgentDir(params.cfg, params.agentId),
provider: params.settings.provider,
remote: params.settings.remote,
model: params.settings.model,
outputDimensionality: params.settings.outputDimensionality,
fallback: params.settings.fallback,
local: params.settings.local,
...resolveMemoryPrimaryProviderRequest({ settings: params.settings }),
});
}
@@ -293,10 +292,14 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
sessionKey?: string;
},
): Promise<MemorySearchResult[]> {
const cleaned = query.trim();
if (!cleaned) {
const preflight = resolveMemorySearchPreflight({
query,
hasIndexedContent: this.hasIndexedContent(),
});
if (!preflight.shouldSearch) {
return [];
}
const cleaned = preflight.normalizedQuery;
void this.warmSession(opts?.sessionKey);
startAsyncSearchSync({
enabled: this.settings.sync.onSearch,
@@ -307,11 +310,9 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
log.warn(`memory sync failed (search): ${String(err)}`);
},
});
const hasIndexedContent = this.hasIndexedContent();
if (!hasIndexedContent) {
return [];
if (preflight.shouldInitializeProvider) {
await this.ensureProviderInitialized();
}
await this.ensureProviderInitialized();
const minScore = opts?.minScore ?? this.settings.query.minScore;
const maxResults = opts?.maxResults ?? this.settings.query.maxResults;
const hybrid = this.settings.query.hybrid;