From 1fc4342a02974c6d24ca2934ba69026d2dfebdf9 Mon Sep 17 00:00:00 2001 From: wood fish Date: Tue, 23 Jun 2026 01:49:43 +0800 Subject: [PATCH] fix(ollama): honor memory embedding output dimensionality (#94811) --- .../ollama/src/embedding-provider.test.ts | 36 +++++++++++++++++++ extensions/ollama/src/embedding-provider.ts | 10 ++++-- .../ollama/src/memory-embedding-adapter.ts | 1 + 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/extensions/ollama/src/embedding-provider.test.ts b/extensions/ollama/src/embedding-provider.test.ts index 63c0eae2c469..95d00edd0efc 100644 --- a/extensions/ollama/src/embedding-provider.test.ts +++ b/extensions/ollama/src/embedding-provider.test.ts @@ -147,6 +147,25 @@ describe("ollama embedding provider", () => { expect(vector[1]).toBeCloseTo(0.8, 5); }); + it("applies outputDimensionality before normalizing vectors", async () => { + mockEmbeddingFetch([3, 4, 12]); + + const { provider } = await createOllamaEmbeddingProvider({ + config: {} as OpenClawConfig, + provider: "ollama", + model: "unknown-embedder", + fallback: "none", + remote: { baseUrl: "http://127.0.0.1:11434" }, + outputDimensionality: 2, + }); + + const vector = await provider.embedQuery("hi"); + + expect(vector).toHaveLength(2); + expect(vector[0]).toBeCloseTo(0.6, 5); + expect(vector[1]).toBeCloseTo(0.8, 5); + }); + it("marks the configured Ollama origin for managed-proxy direct routing", async () => { const fetchMock = mockEmbeddingFetch([1, 0]); @@ -664,6 +683,23 @@ describe("ollama embedding provider", () => { expect(headers?.Authorization).toBeUndefined(); }); + it("includes outputDimensionality in the memory embedding cache identity", async () => { + const result = await ollamaMemoryEmbeddingProviderAdapter.create({ + config: {} as OpenClawConfig, + provider: "ollama", + model: "nomic-embed-text", + fallback: "none", + remote: { baseUrl: "http://127.0.0.1:11434" }, + outputDimensionality: 2, + }); + + expect(result.runtime?.cacheKeyData).toMatchObject({ + provider: "ollama", + model: "nomic-embed-text", + outputDimensionality: 2, + }); + }); + it("marks inline memory batches as local-server timeout work", async () => { const result = await ollamaMemoryEmbeddingProviderAdapter.create({ config: {} as OpenClawConfig, diff --git a/extensions/ollama/src/embedding-provider.ts b/extensions/ollama/src/embedding-provider.ts index 4132a3c5badf..9471fae8bc17 100644 --- a/extensions/ollama/src/embedding-provider.ts +++ b/extensions/ollama/src/embedding-provider.ts @@ -52,6 +52,7 @@ export type OllamaEmbeddingClient = { headers: Record; ssrfPolicy?: SsrFPolicy; model: string; + outputDimensionality?: number; embedBatch: (texts: string[]) => Promise; }; @@ -76,8 +77,10 @@ const QUERY_INSTRUCTION_TEMPLATES = [ }, ] as const; -function sanitizeAndNormalizeEmbedding(vec: unknown[]): number[] { - const sanitized = vec.map((value) => { +function sanitizeAndNormalizeEmbedding(vec: unknown[], outputDimensionality?: number): number[] { + const selected = + typeof outputDimensionality === "number" ? vec.slice(0, outputDimensionality) : vec; + const sanitized = selected.map((value) => { if (typeof value !== "number") { throw new Error("Ollama embed response contains a non-number embedding value"); } @@ -320,6 +323,7 @@ function resolveOllamaEmbeddingClient( headers, ssrfPolicy: ssrfPolicyFromHttpBaseUrlAllowedOrigin(baseUrl), model, + outputDimensionality: options.outputDimensionality, }; } @@ -364,7 +368,7 @@ export async function createOllamaEmbeddingProvider( if (!Array.isArray(embedding)) { throw new Error("Ollama embed response contains a non-array embedding"); } - return sanitizeAndNormalizeEmbedding(embedding); + return sanitizeAndNormalizeEmbedding(embedding, client.outputDimensionality); }); }; diff --git a/extensions/ollama/src/memory-embedding-adapter.ts b/extensions/ollama/src/memory-embedding-adapter.ts index 02786bdde8d0..e42f2e4f397d 100644 --- a/extensions/ollama/src/memory-embedding-adapter.ts +++ b/extensions/ollama/src/memory-embedding-adapter.ts @@ -24,6 +24,7 @@ export const ollamaMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapte cacheKeyData: { provider: "ollama", model: client.model, + outputDimensionality: client.outputDimensionality, }, }, };