diff --git a/extensions/memory-core/src/memory/index.test.ts b/extensions/memory-core/src/memory/index.test.ts index 38302609278..c9ccc2e83a5 100644 --- a/extensions/memory-core/src/memory/index.test.ts +++ b/extensions/memory-core/src/memory/index.test.ts @@ -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); diff --git a/extensions/memory-core/src/memory/manager-provider-state.ts b/extensions/memory-core/src/memory/manager-provider-state.ts index 2aa01a6ad28..730a240c828 100644 --- a/extensions/memory-core/src/memory/manager-provider-state.ts +++ b/extensions/memory-core/src/memory/manager-provider-state.ts @@ -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, diff --git a/extensions/memory-core/src/memory/manager-search-preflight.test.ts b/extensions/memory-core/src/memory/manager-search-preflight.test.ts new file mode 100644 index 00000000000..760575c3c0e --- /dev/null +++ b/extensions/memory-core/src/memory/manager-search-preflight.test.ts @@ -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, + }); + }); +}); diff --git a/extensions/memory-core/src/memory/manager-search-preflight.ts b/extensions/memory-core/src/memory/manager-search-preflight.ts new file mode 100644 index 00000000000..087016adca7 --- /dev/null +++ b/extensions/memory-core/src/memory/manager-search-preflight.ts @@ -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, + }; +} diff --git a/extensions/memory-core/src/memory/manager.mistral-provider.test.ts b/extensions/memory-core/src/memory/manager.mistral-provider.test.ts index 1554b7718e6..95c36b0acf4 100644 --- a/extensions/memory-core/src/memory/manager.mistral-provider.test.ts +++ b/extensions/memory-core/src/memory/manager.mistral-provider.test.ts @@ -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); + }); }); diff --git a/extensions/memory-core/src/memory/manager.ts b/extensions/memory-core/src/memory/manager.ts index 997a424be56..15e770eb9d8 100644 --- a/extensions/memory-core/src/memory/manager.ts +++ b/extensions/memory-core/src/memory/manager.ts @@ -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 { - 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;