Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit db11712

Browse files
roomote[bot]roomotehannesrudolph
committed
fix(code-index): remove deprecated text-embedding-004 and migrate to gemini-embedding-001 (#11038)
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Hannes Rudolph <hrudolph@gmail.com>
1 parent 10d9577 commit db11712

5 files changed

Lines changed: 165 additions & 11 deletions

File tree

src/services/code-index/__tests__/service-factory.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ describe("CodeIndexServiceFactory", () => {
286286
// Arrange
287287
const testConfig = {
288288
embedderProvider: "gemini",
289-
modelId: "text-embedding-004",
289+
modelId: "gemini-embedding-001",
290290
geminiOptions: {
291291
apiKey: "test-gemini-api-key",
292292
},
@@ -297,6 +297,25 @@ describe("CodeIndexServiceFactory", () => {
297297
factory.createEmbedder()
298298

299299
// Assert
300+
expect(MockedGeminiEmbedder).toHaveBeenCalledWith("test-gemini-api-key", "gemini-embedding-001")
301+
})
302+
303+
it("should pass deprecated text-embedding-004 modelId to GeminiEmbedder (migration happens inside GeminiEmbedder)", () => {
304+
// Arrange - service-factory passes the config modelId directly;
305+
// GeminiEmbedder handles the migration internally
306+
const testConfig = {
307+
embedderProvider: "gemini",
308+
modelId: "text-embedding-004",
309+
geminiOptions: {
310+
apiKey: "test-gemini-api-key",
311+
},
312+
}
313+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
314+
315+
// Act
316+
factory.createEmbedder()
317+
318+
// Assert - factory passes the original modelId; GeminiEmbedder migrates it internally
300319
expect(MockedGeminiEmbedder).toHaveBeenCalledWith("test-gemini-api-key", "text-embedding-004")
301320
})
302321

src/services/code-index/embedders/__tests__/gemini.spec.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe("GeminiEmbedder", () => {
4444
it("should create an instance with specified model", () => {
4545
// Arrange
4646
const apiKey = "test-gemini-api-key"
47-
const modelId = "text-embedding-004"
47+
const modelId = "gemini-embedding-001"
4848

4949
// Act
5050
embedder = new GeminiEmbedder(apiKey, modelId)
@@ -53,7 +53,24 @@ describe("GeminiEmbedder", () => {
5353
expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith(
5454
"https://generativelanguage.googleapis.com/v1beta/openai/",
5555
apiKey,
56-
"text-embedding-004",
56+
"gemini-embedding-001",
57+
2048,
58+
)
59+
})
60+
61+
it("should migrate deprecated text-embedding-004 to gemini-embedding-001", () => {
62+
// Arrange
63+
const apiKey = "test-gemini-api-key"
64+
const deprecatedModelId = "text-embedding-004"
65+
66+
// Act
67+
embedder = new GeminiEmbedder(apiKey, deprecatedModelId)
68+
69+
// Assert - should be migrated to gemini-embedding-001
70+
expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith(
71+
"https://generativelanguage.googleapis.com/v1beta/openai/",
72+
apiKey,
73+
"gemini-embedding-001",
5774
2048,
5875
)
5976
})
@@ -109,8 +126,8 @@ describe("GeminiEmbedder", () => {
109126
})
110127

111128
it("should use provided model parameter when specified", async () => {
112-
// Arrange
113-
embedder = new GeminiEmbedder("test-api-key", "text-embedding-004")
129+
// Arrange - even with deprecated model in constructor, the runtime parameter takes precedence
130+
embedder = new GeminiEmbedder("test-api-key", "gemini-embedding-001")
114131
const texts = ["test text 1", "test text 2"]
115132
const mockResponse = {
116133
embeddings: [
@@ -120,7 +137,7 @@ describe("GeminiEmbedder", () => {
120137
}
121138
mockCreateEmbeddings.mockResolvedValue(mockResponse)
122139

123-
// Act
140+
// Act - specify a different model at runtime
124141
const result = await embedder.createEmbeddings(texts, "gemini-embedding-001")
125142

126143
// Assert

src/services/code-index/embedders/gemini.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,33 @@ import { TelemetryService } from "@roo-code/telemetry"
1010
* with configuration for Google's Gemini embedding API.
1111
*
1212
* Supported models:
13-
* - text-embedding-004 (dimension: 768)
14-
* - gemini-embedding-001 (dimension: 2048)
13+
* - gemini-embedding-001 (dimension: 3072)
14+
*
15+
* Note: text-embedding-004 has been deprecated and is automatically
16+
* migrated to gemini-embedding-001 for backward compatibility.
1517
*/
1618
export class GeminiEmbedder implements IEmbedder {
1719
private readonly openAICompatibleEmbedder: OpenAICompatibleEmbedder
1820
private static readonly GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
1921
private static readonly DEFAULT_MODEL = "gemini-embedding-001"
22+
/**
23+
* Deprecated models that are automatically migrated to their replacements.
24+
* Users with these models configured will be silently migrated without interruption.
25+
*/
26+
private static readonly DEPRECATED_MODEL_MIGRATIONS: Record<string, string> = {
27+
"text-embedding-004": "gemini-embedding-001",
28+
}
2029
private readonly modelId: string
2130

31+
/**
32+
* Migrates deprecated model IDs to their replacements.
33+
* @param modelId The model ID to potentially migrate
34+
* @returns The migrated model ID, or the original if no migration is needed
35+
*/
36+
private static migrateModelId(modelId: string): string {
37+
return GeminiEmbedder.DEPRECATED_MODEL_MIGRATIONS[modelId] ?? modelId
38+
}
39+
2240
/**
2341
* Creates a new Gemini embedder
2442
* @param apiKey The Gemini API key for authentication
@@ -29,8 +47,11 @@ export class GeminiEmbedder implements IEmbedder {
2947
throw new Error(t("embeddings:validation.apiKeyRequired"))
3048
}
3149

32-
// Use provided model or default
33-
this.modelId = modelId || GeminiEmbedder.DEFAULT_MODEL
50+
// Migrate deprecated models to their replacements silently
51+
const migratedModelId = modelId ? GeminiEmbedder.migrateModelId(modelId) : undefined
52+
53+
// Use provided model (after migration) or default
54+
this.modelId = migratedModelId || GeminiEmbedder.DEFAULT_MODEL
3455

3556
// Create an OpenAI Compatible embedder with Gemini's configuration
3657
this.openAICompatibleEmbedder = new OpenAICompatibleEmbedder(
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { describe, it, expect } from "vitest"
2+
import {
3+
getModelDimension,
4+
getModelScoreThreshold,
5+
getDefaultModelId,
6+
EMBEDDING_MODEL_PROFILES,
7+
} from "../embeddingModels"
8+
9+
describe("embeddingModels", () => {
10+
describe("EMBEDDING_MODEL_PROFILES", () => {
11+
it("should have gemini provider with gemini-embedding-001 model", () => {
12+
const geminiProfiles = EMBEDDING_MODEL_PROFILES.gemini
13+
expect(geminiProfiles).toBeDefined()
14+
expect(geminiProfiles!["gemini-embedding-001"]).toBeDefined()
15+
expect(geminiProfiles!["gemini-embedding-001"].dimension).toBe(3072)
16+
})
17+
18+
it("should have deprecated text-embedding-004 in gemini profiles for backward compatibility", () => {
19+
// This is critical for backward compatibility:
20+
// Users with text-embedding-004 configured need dimension lookup to work
21+
// even though the model is migrated to gemini-embedding-001 in GeminiEmbedder
22+
const geminiProfiles = EMBEDDING_MODEL_PROFILES.gemini
23+
expect(geminiProfiles).toBeDefined()
24+
expect(geminiProfiles!["text-embedding-004"]).toBeDefined()
25+
expect(geminiProfiles!["text-embedding-004"].dimension).toBe(3072)
26+
})
27+
})
28+
29+
describe("getModelDimension", () => {
30+
it("should return dimension for gemini-embedding-001", () => {
31+
const dimension = getModelDimension("gemini", "gemini-embedding-001")
32+
expect(dimension).toBe(3072)
33+
})
34+
35+
it("should return dimension for deprecated text-embedding-004", () => {
36+
// This ensures createVectorStore() works for users with text-embedding-004 configured
37+
// The dimension should be 3072 (matching gemini-embedding-001) because:
38+
// 1. GeminiEmbedder migrates text-embedding-004 to gemini-embedding-001
39+
// 2. gemini-embedding-001 produces 3072-dimensional embeddings
40+
// 3. Vector store dimension must match the actual embedding dimension
41+
const dimension = getModelDimension("gemini", "text-embedding-004")
42+
expect(dimension).toBe(3072)
43+
})
44+
45+
it("should return undefined for unknown model", () => {
46+
const dimension = getModelDimension("gemini", "unknown-model")
47+
expect(dimension).toBeUndefined()
48+
})
49+
50+
it("should return undefined for unknown provider", () => {
51+
const dimension = getModelDimension("unknown-provider" as any, "some-model")
52+
expect(dimension).toBeUndefined()
53+
})
54+
55+
it("should return correct dimensions for openai models", () => {
56+
expect(getModelDimension("openai", "text-embedding-3-small")).toBe(1536)
57+
expect(getModelDimension("openai", "text-embedding-3-large")).toBe(3072)
58+
expect(getModelDimension("openai", "text-embedding-ada-002")).toBe(1536)
59+
})
60+
})
61+
62+
describe("getModelScoreThreshold", () => {
63+
it("should return score threshold for gemini-embedding-001", () => {
64+
const threshold = getModelScoreThreshold("gemini", "gemini-embedding-001")
65+
expect(threshold).toBe(0.4)
66+
})
67+
68+
it("should return score threshold for deprecated text-embedding-004", () => {
69+
const threshold = getModelScoreThreshold("gemini", "text-embedding-004")
70+
expect(threshold).toBe(0.4)
71+
})
72+
73+
it("should return undefined for unknown model", () => {
74+
const threshold = getModelScoreThreshold("gemini", "unknown-model")
75+
expect(threshold).toBeUndefined()
76+
})
77+
})
78+
79+
describe("getDefaultModelId", () => {
80+
it("should return gemini-embedding-001 for gemini provider", () => {
81+
const defaultModel = getDefaultModelId("gemini")
82+
expect(defaultModel).toBe("gemini-embedding-001")
83+
})
84+
85+
it("should return text-embedding-3-small for openai provider", () => {
86+
const defaultModel = getDefaultModelId("openai")
87+
expect(defaultModel).toBe("text-embedding-3-small")
88+
})
89+
90+
it("should return codestral-embed-2505 for mistral provider", () => {
91+
const defaultModel = getDefaultModelId("mistral")
92+
expect(defaultModel).toBe("codestral-embed-2505")
93+
})
94+
})
95+
})

src/shared/embeddingModels.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
3434
},
3535
},
3636
gemini: {
37-
"text-embedding-004": { dimension: 768 },
3837
"gemini-embedding-001": { dimension: 3072, scoreThreshold: 0.4 },
38+
// Deprecated: text-embedding-004 is migrated to gemini-embedding-001 in GeminiEmbedder
39+
// Kept here for backward-compatible dimension lookup in createVectorStore()
40+
"text-embedding-004": { dimension: 3072, scoreThreshold: 0.4 },
3941
},
4042
mistral: {
4143
"codestral-embed-2505": { dimension: 1536, scoreThreshold: 0.4 },

0 commit comments

Comments
 (0)