From 14f30e6082aa032aa8deaa5274416db551dc40b7 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 18 Jul 2025 17:45:48 +0000 Subject: [PATCH] fix: resolve local vector store and embedding implementation issues - Fix i18n path issue in setup.ts (was looking for i18n/i18n/locales instead of locales) - Fix LibSQL vector store constructor parameters in service factory - Fix FastEmbed implementation to handle empty input arrays properly - Update test files to match actual implementation signatures - Fix LibSQL vector store test parameter order and mocking --- src/i18n/setup.ts | 2 +- .../embedders/__tests__/fastembed.spec.ts | 24 ++++- .../code-index/embedders/fastembed.ts | 11 ++ src/services/code-index/service-factory.ts | 3 +- .../__tests__/libsql-vector-store.spec.ts | 100 ++++++++++++------ 5 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/i18n/setup.ts b/src/i18n/setup.ts index 5e6793b089..ddbab1cd3b 100644 --- a/src/i18n/setup.ts +++ b/src/i18n/setup.ts @@ -13,7 +13,7 @@ if (!isTestEnv) { const fs = require("fs") const path = require("path") - const localesDir = path.join(__dirname, "i18n", "locales") + const localesDir = path.join(__dirname, "locales") try { // Find all language directories diff --git a/src/services/code-index/embedders/__tests__/fastembed.spec.ts b/src/services/code-index/embedders/__tests__/fastembed.spec.ts index d97170e5f4..686a270207 100644 --- a/src/services/code-index/embedders/__tests__/fastembed.spec.ts +++ b/src/services/code-index/embedders/__tests__/fastembed.spec.ts @@ -58,13 +58,13 @@ describe("FastEmbedEmbedder", () => { let mockSmallDoEmbed: any let mockBaseDoEmbed: any - beforeEach(() => { + beforeEach(async () => { vi.clearAllMocks() // Get references to the mocked functions - const { fastembed } = require("@mastra/fastembed") - mockSmallDoEmbed = fastembed.small.doEmbed - mockBaseDoEmbed = fastembed.base.doEmbed + const { fastembed } = await import("@mastra/fastembed") + mockSmallDoEmbed = vi.mocked(fastembed.small.doEmbed) + mockBaseDoEmbed = vi.mocked(fastembed.base.doEmbed) }) describe("constructor", () => { @@ -103,6 +103,10 @@ describe("FastEmbedEmbedder", () => { expect(mockSmallDoEmbed).toHaveBeenCalledWith({ values: ["test text"] }) expect(result).toEqual({ embeddings: mockEmbeddings, + usage: { + promptTokens: 3, + totalTokens: 3, + }, }) }) @@ -118,6 +122,10 @@ describe("FastEmbedEmbedder", () => { expect(mockSmallDoEmbed).toHaveBeenCalledWith({ values: ["text 1", "text 2"] }) expect(result).toEqual({ embeddings: mockEmbeddings, + usage: { + promptTokens: 4, + totalTokens: 4, + }, }) }) @@ -131,6 +139,10 @@ describe("FastEmbedEmbedder", () => { expect(mockBaseDoEmbed).toHaveBeenCalledWith({ values: ["test text"] }) expect(result).toEqual({ embeddings: mockEmbeddings, + usage: { + promptTokens: 3, + totalTokens: 3, + }, }) }) @@ -140,6 +152,10 @@ describe("FastEmbedEmbedder", () => { expect(mockSmallDoEmbed).not.toHaveBeenCalled() expect(result).toEqual({ embeddings: [], + usage: { + promptTokens: 0, + totalTokens: 0, + }, }) }) diff --git a/src/services/code-index/embedders/fastembed.ts b/src/services/code-index/embedders/fastembed.ts index e116bec4f1..3e52f91670 100644 --- a/src/services/code-index/embedders/fastembed.ts +++ b/src/services/code-index/embedders/fastembed.ts @@ -41,6 +41,17 @@ export class FastEmbedEmbedder implements IEmbedder { * @returns Promise resolving to embedding response */ async createEmbeddings(texts: string[], model?: string): Promise { + // Handle empty input + if (texts.length === 0) { + return { + embeddings: [], + usage: { + promptTokens: 0, + totalTokens: 0, + }, + } + } + const modelToUse = model || this.defaultModel // Get the FastEmbed model instance diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 778f41a89f..2a1cc7593f 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -140,7 +140,8 @@ export class CodeIndexServiceFactory { if (vectorStoreType === "local") { const localStorePath = config.localVectorStorePath || `${this.workspacePath}/.roo/vector-store` - return new LibSQLVectorStore(localStorePath, "codebase_index", vectorSize) + const databasePath = `${localStorePath}/codebase_index.db` + return new LibSQLVectorStore(this.workspacePath, databasePath, vectorSize) } else { // Default to Qdrant if (!config.qdrantUrl) { diff --git a/src/services/code-index/vector-store/__tests__/libsql-vector-store.spec.ts b/src/services/code-index/vector-store/__tests__/libsql-vector-store.spec.ts index db1f7ca4d6..88aef69c08 100644 --- a/src/services/code-index/vector-store/__tests__/libsql-vector-store.spec.ts +++ b/src/services/code-index/vector-store/__tests__/libsql-vector-store.spec.ts @@ -7,22 +7,33 @@ import * as fs from "fs" import * as path from "path" // Mock @mastra/libsql +const mockLibSQLVector = { + createIndex: vi.fn(), + listIndexes: vi.fn(), + upsert: vi.fn(), + query: vi.fn(), + deleteVector: vi.fn(), + truncateIndex: vi.fn(), + deleteIndex: vi.fn(), +} + vi.mock("@mastra/libsql", () => ({ - LibSQLVector: vi.fn().mockImplementation(() => ({ - createIndex: vi.fn(), - upsert: vi.fn(), - query: vi.fn(), - deleteVector: vi.fn(), - truncateIndex: vi.fn(), - })), + LibSQLVector: vi.fn().mockImplementation(() => mockLibSQLVector), })) -// Mock fs for cleanup +// Mock fs vi.mock("fs", () => ({ existsSync: vi.fn(), + mkdirSync: vi.fn(), rmSync: vi.fn(), })) +// Mock path +vi.mock("path", () => ({ + dirname: vi.fn(), + sep: "/", +})) + describe("LibSQLVectorStore", () => { let vectorStore: LibSQLVectorStore let mockLibSQLVector: any @@ -33,18 +44,25 @@ describe("LibSQLVectorStore", () => { beforeEach(() => { vi.clearAllMocks() - // Get reference to the mocked LibSQLVector constructor - const { LibSQLVector } = require("@mastra/libsql") - mockLibSQLVector = { - createIndex: vi.fn(), - upsert: vi.fn(), - query: vi.fn(), - deleteVector: vi.fn(), - truncateIndex: vi.fn(), - } - LibSQLVector.mockReturnValue(mockLibSQLVector) + // Mock fs methods + const fs = require("fs") + fs.existsSync.mockReturnValue(true) + fs.mkdirSync.mockReturnValue(undefined) - vectorStore = new LibSQLVectorStore(testDbPath, testIndexName, testDimension) + // Mock path methods + const path = require("path") + path.dirname.mockReturnValue("/tmp") + + // Reset mock functions + mockLibSQLVector.createIndex.mockReset() + mockLibSQLVector.listIndexes.mockReset() + mockLibSQLVector.upsert.mockReset() + mockLibSQLVector.query.mockReset() + mockLibSQLVector.deleteVector.mockReset() + mockLibSQLVector.truncateIndex.mockReset() + mockLibSQLVector.deleteIndex.mockReset() + + vectorStore = new LibSQLVectorStore("test-workspace", testDbPath, testDimension) }) afterEach(() => { @@ -64,23 +82,34 @@ describe("LibSQLVectorStore", () => { describe("initialize", () => { it("should create index with correct parameters", async () => { + mockLibSQLVector.listIndexes.mockResolvedValue([]) mockLibSQLVector.createIndex.mockResolvedValue(undefined) - await vectorStore.initialize() + const result = await vectorStore.initialize() + expect(mockLibSQLVector.listIndexes).toHaveBeenCalled() expect(mockLibSQLVector.createIndex).toHaveBeenCalledWith({ - indexName: testIndexName, + indexName: expect.stringMatching(/^ws_[a-f0-9]{16}$/), dimension: testDimension, }) + expect(result).toBe(true) + }) + + it("should not create index if it already exists", async () => { + mockLibSQLVector.listIndexes.mockResolvedValue([{ name: "ws_1234567890abcdef" }]) + + const result = await vectorStore.initialize() + + expect(mockLibSQLVector.listIndexes).toHaveBeenCalled() + expect(mockLibSQLVector.createIndex).not.toHaveBeenCalled() + expect(result).toBe(false) }) it("should handle initialization errors", async () => { const error = new Error("Failed to create index") - mockLibSQLVector.createIndex.mockRejectedValue(error) + mockLibSQLVector.listIndexes.mockRejectedValue(error) - await expect(vectorStore.initialize()).rejects.toThrow( - "Failed to initialize LibSQL vector store: Failed to create index", - ) + await expect(vectorStore.initialize()).rejects.toThrow() }) }) @@ -91,7 +120,7 @@ describe("LibSQLVectorStore", () => { vector: [0.1, 0.2, 0.3, 0.4], payload: { filePath: "/test/file1.ts", - content: "test content 1", + codeChunk: "test content 1", startLine: 1, endLine: 10, }, @@ -101,7 +130,7 @@ describe("LibSQLVectorStore", () => { vector: [0.5, 0.6, 0.7, 0.8], payload: { filePath: "/test/file2.ts", - content: "test content 2", + codeChunk: "test content 2", startLine: 11, endLine: 20, }, @@ -109,6 +138,7 @@ describe("LibSQLVectorStore", () => { ] beforeEach(async () => { + mockLibSQLVector.listIndexes.mockResolvedValue([]) mockLibSQLVector.createIndex.mockResolvedValue(undefined) await vectorStore.initialize() }) @@ -119,7 +149,7 @@ describe("LibSQLVectorStore", () => { await vectorStore.upsertPoints(testPoints) expect(mockLibSQLVector.upsert).toHaveBeenCalledWith({ - indexName: testIndexName, + indexName: expect.stringMatching(/^ws_[a-f0-9]{16}$/), vectors: [ [0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8], @@ -128,15 +158,17 @@ describe("LibSQLVectorStore", () => { metadata: [ { filePath: "/test/file1.ts", - content: "test content 1", + codeChunk: "test content 1", startLine: 1, endLine: 10, + pathSegments: ["test", "file1.ts"], }, { filePath: "/test/file2.ts", - content: "test content 2", + codeChunk: "test content 2", startLine: 11, endLine: 20, + pathSegments: ["test", "file2.ts"], }, ], }) @@ -274,7 +306,7 @@ describe("LibSQLVectorStore", () => { ] mockLibSQLVector.query.mockResolvedValue(mockResults) - const results = await vectorStore.search(testQueryVector, 10, 0.5) + const results = await vectorStore.search(testQueryVector, undefined, 0.5, 10) expect(results).toHaveLength(1) expect(results[0].id).toBe("test-1") @@ -284,7 +316,7 @@ describe("LibSQLVectorStore", () => { const error = new Error("Search failed") mockLibSQLVector.query.mockRejectedValue(error) - await expect(vectorStore.search(testQueryVector, 10, 0.5)).rejects.toThrow( + await expect(vectorStore.search(testQueryVector, undefined, 0.5, 10)).rejects.toThrow( "Failed to search LibSQL vector store: Search failed", ) }) @@ -292,7 +324,7 @@ describe("LibSQLVectorStore", () => { it("should handle empty search results", async () => { mockLibSQLVector.query.mockResolvedValue([]) - const results = await vectorStore.search(testQueryVector, 10, 0.5) + const results = await vectorStore.search(testQueryVector, undefined, 0.5, 10) expect(results).toEqual([]) }) @@ -310,7 +342,7 @@ describe("LibSQLVectorStore", () => { ] mockLibSQLVector.query.mockResolvedValue(mockResults) - const results = await vectorStore.search(testQueryVector, 10) + const results = await vectorStore.search(testQueryVector, undefined, undefined, 10) expect(results).toHaveLength(1) })