fix(ollama): honor memory embedding output dimensionality (#94811)

This commit is contained in:
wood fish
2026-06-23 01:49:43 +08:00
committed by GitHub
parent 9fbc8a74ef
commit 1fc4342a02
3 changed files with 44 additions and 3 deletions
@@ -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,
+7 -3
View File
@@ -52,6 +52,7 @@ export type OllamaEmbeddingClient = {
headers: Record<string, string>;
ssrfPolicy?: SsrFPolicy;
model: string;
outputDimensionality?: number;
embedBatch: (texts: string[]) => Promise<number[][]>;
};
@@ -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);
});
};
@@ -24,6 +24,7 @@ export const ollamaMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapte
cacheKeyData: {
provider: "ollama",
model: client.model,
outputDimensionality: client.outputDimensionality,
},
},
};