diff --git a/src/memory/embeddings-ollama.test.ts b/src/memory/embeddings-ollama.test.ts index 910a7515696..d8f2315db79 100644 --- a/src/memory/embeddings-ollama.test.ts +++ b/src/memory/embeddings-ollama.test.ts @@ -3,10 +3,10 @@ import type { OpenClawConfig } from "../config/config.js"; import { createOllamaEmbeddingProvider } from "./embeddings-ollama.js"; describe("embeddings-ollama", () => { - it("calls /api/embeddings and returns normalized vectors", async () => { + it("calls /api/embed and returns normalized vectors", async () => { const fetchMock = vi.fn( async () => - new Response(JSON.stringify({ embedding: [3, 4] }), { + new Response(JSON.stringify({ embeddings: [[3, 4]] }), { status: 200, headers: { "content-type": "application/json" }, }), @@ -31,7 +31,7 @@ describe("embeddings-ollama", () => { it("resolves baseUrl/apiKey/headers from models.providers.ollama and strips /v1", async () => { const fetchMock = vi.fn( async () => - new Response(JSON.stringify({ embedding: [1, 0] }), { + new Response(JSON.stringify({ embeddings: [[1, 0]] }), { status: 200, headers: { "content-type": "application/json" }, }), @@ -60,7 +60,7 @@ describe("embeddings-ollama", () => { await provider.embedQuery("hello"); expect(fetchMock).toHaveBeenCalledWith( - "http://127.0.0.1:11434/api/embeddings", + "http://127.0.0.1:11434/api/embed", expect.objectContaining({ method: "POST", headers: expect.objectContaining({ @@ -90,7 +90,7 @@ describe("embeddings-ollama", () => { it("falls back to env key when models.providers.ollama.apiKey is an unresolved SecretRef", async () => { const fetchMock = vi.fn( async () => - new Response(JSON.stringify({ embedding: [1, 0] }), { + new Response(JSON.stringify({ embeddings: [[1, 0]] }), { status: 200, headers: { "content-type": "application/json" }, }), @@ -118,7 +118,7 @@ describe("embeddings-ollama", () => { await provider.embedQuery("hello"); expect(fetchMock).toHaveBeenCalledWith( - "http://127.0.0.1:11434/api/embeddings", + "http://127.0.0.1:11434/api/embed", expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer ollama-env", diff --git a/src/memory/embeddings-ollama.ts b/src/memory/embeddings-ollama.ts index 7bd2bcf7428..6b745302501 100644 --- a/src/memory/embeddings-ollama.ts +++ b/src/memory/embeddings-ollama.ts @@ -73,7 +73,7 @@ export async function createOllamaEmbeddingProvider( options: EmbeddingProviderOptions, ): Promise<{ provider: EmbeddingProvider; client: OllamaEmbeddingClient }> { const client = resolveOllamaEmbeddingClient(options); - const embedUrl = `${client.baseUrl.replace(/\/$/, "")}/api/embeddings`; + const embedUrl = `${client.baseUrl.replace(/\/$/, "")}/api/embed`; const embedOne = async (text: string): Promise => { const json = await withRemoteHttpResponse({ @@ -82,19 +82,19 @@ export async function createOllamaEmbeddingProvider( init: { method: "POST", headers: client.headers, - body: JSON.stringify({ model: client.model, prompt: text }), + body: JSON.stringify({ model: client.model, input: text }), }, onResponse: async (res) => { if (!res.ok) { throw new Error(`Ollama embeddings HTTP ${res.status}: ${await res.text()}`); } - return (await res.json()) as { embedding?: number[] }; + return (await res.json()) as { embeddings?: number[][] }; }, }); - if (!Array.isArray(json.embedding)) { - throw new Error(`Ollama embeddings response missing embedding[]`); + if (!Array.isArray(json.embeddings) || !Array.isArray(json.embeddings[0])) { + throw new Error(`Ollama embeddings response missing embeddings[]`); } - return sanitizeAndNormalizeEmbedding(json.embedding); + return sanitizeAndNormalizeEmbedding(json.embeddings[0]); }; const provider: EmbeddingProvider = { @@ -102,7 +102,8 @@ export async function createOllamaEmbeddingProvider( model: client.model, embedQuery: embedOne, embedBatch: async (texts: string[]) => { - // Ollama /api/embeddings accepts one prompt per request. + // Ollama /api/embed supports batched input, but we fan-out here to + // keep error handling and response normalisation consistent per text. return await Promise.all(texts.map(embedOne)); }, };