diff --git a/README.md b/README.md index 678351a8..7c4dea82 100644 --- a/README.md +++ b/README.md @@ -719,7 +719,7 @@ npm install @xenova/transformers | Provider | Model | Cost | Notes | |---|---|---|---| | **Local (recommended)** | `all-MiniLM-L6-v2` | Free | Offline, +8pp recall over BM25-only | -| Gemini | `text-embedding-004` | Free tier | 1500 RPM | +| Gemini | `gemini-embedding-001` | Free tier | 100+ languages, 768/1536/3072 dims (MRL), 2048-token input. Replaces `text-embedding-004` ([deprecated, shutdown Jan 14, 2026](https://ai.google.dev/gemini-api/docs/deprecations)) | | OpenAI | `text-embedding-3-small` | $0.02/1M | Highest quality | | Voyage AI | `voyage-code-3` | Paid | Optimized for code | | Cohere | `embed-english-v3.0` | Free trial | General purpose | diff --git a/src/config.ts b/src/config.ts index 2898552d..a4b676cf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,7 @@ function detectProvider(env: Record): ProviderConfig { } return { provider: "gemini", - model: env["GEMINI_MODEL"] || "gemini-2.0-flash", + model: env["GEMINI_MODEL"] || "gemini-2.5-flash", maxTokens, }; } diff --git a/src/providers/embedding/gemini.ts b/src/providers/embedding/gemini.ts index 74dca6f5..6cfb2764 100644 --- a/src/providers/embedding/gemini.ts +++ b/src/providers/embedding/gemini.ts @@ -2,7 +2,8 @@ import type { EmbeddingProvider } from "../../types.js"; import { getEnvVar } from "../../config.js"; const BATCH_LIMIT = 100; -const API_BASE = "https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:batchEmbedContent"; +const MODEL = "models/gemini-embedding-001"; +const API_BASE = `https://generativelanguage.googleapis.com/v1beta/${MODEL}:batchEmbedContents`; export class GeminiEmbeddingProvider implements EmbeddingProvider { readonly name = "gemini"; @@ -29,8 +30,9 @@ export class GeminiEmbeddingProvider implements EmbeddingProvider { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requests: chunk.map((t) => ({ - model: "models/text-embedding-004", + model: MODEL, content: { parts: [{ text: t }] }, + outputDimensionality: this.dimensions, })), }), }); @@ -45,10 +47,31 @@ export class GeminiEmbeddingProvider implements EmbeddingProvider { }; for (const emb of data.embeddings) { - results.push(new Float32Array(emb.values)); + results.push(l2Normalize(new Float32Array(emb.values))); } } return results; } } + +let zeroNormWarned = false; + +function l2Normalize(vec: Float32Array): Float32Array { + let sum = 0; + for (let i = 0; i < vec.length; i++) sum += vec[i]! * vec[i]!; + const norm = Math.sqrt(sum); + if (norm === 0) { + if (!zeroNormWarned) { + zeroNormWarned = true; + process.stderr.write( + `[agentmemory] warn: gemini-embedding-001 returned a zero-norm ` + + `embedding (length=${vec.length}); leaving it un-normalized. ` + + `Subsequent zero-norm vectors will not be reported.\n`, + ); + } + return vec; + } + for (let i = 0; i < vec.length; i++) vec[i] = vec[i]! / norm; + return vec; +}