From b5f2f1247b6f851b93e24658087c224baf698925 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 31 Oct 2025 23:26:20 +0000 Subject: [PATCH 1/2] fix: add keyword index for type field to fix Qdrant codebase_search error Fixes #8963 The codebase_search tool was failing with a "Index required but not found for type" error when using Qdrant. This was because the search() method filters by the type field to exclude metadata points, but no keyword index was created for this field. Added creation of a keyword index for the type field in the _createPayloadIndexes() method to enable proper filtering of metadata points during search operations. --- .../code-index/vector-store/qdrant-client.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts index 1efd9f8b3a1..ba62afc5f81 100644 --- a/src/services/code-index/vector-store/qdrant-client.ts +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -296,6 +296,23 @@ export class QdrantVectorStore implements IVectorStore { * Creates payload indexes for the collection, handling errors gracefully. */ private async _createPayloadIndexes(): Promise { + // Create index for the 'type' field to enable metadata filtering + try { + await this.client.createPayloadIndex(this.collectionName, { + field_name: "type", + field_schema: "keyword", + }) + } catch (indexError: any) { + const errorMessage = (indexError?.message || "").toLowerCase() + if (!errorMessage.includes("already exists")) { + console.warn( + `[QdrantVectorStore] Could not create payload index for type on ${this.collectionName}. Details:`, + indexError?.message || indexError, + ) + } + } + + // Create indexes for pathSegments fields for (let i = 0; i <= 4; i++) { try { await this.client.createPayloadIndex(this.collectionName, { From aab4372d32c2d4c406e4379d5f91417f4909f786 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 31 Oct 2025 23:28:40 +0000 Subject: [PATCH 2/2] test: update tests to expect type field index creation Updated test expectations to account for the new type field keyword index that is now created in _createPayloadIndexes() method. Tests now expect 6 index creation calls instead of 5 (1 for type field + 5 for pathSegments). --- .../__tests__/qdrant-client.spec.ts | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts b/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts index 439962862b1..ab7b15783e3 100644 --- a/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts +++ b/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts @@ -542,14 +542,18 @@ describe("QdrantVectorStore", () => { }) expect(mockQdrantClientInstance.deleteCollection).not.toHaveBeenCalled() - // Verify payload index creation + // Verify payload index creation - 'type' field first, then pathSegments + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { + field_name: "type", + field_schema: "keyword", + }) for (let i = 0; i <= 4; i++) { expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { field_name: `pathSegments.${i}`, field_schema: "keyword", }) } - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) }) it("should not create a new collection if one exists with matching vectorSize and return false", async () => { // Mock getCollection to return existing collection info with matching vector size @@ -572,14 +576,18 @@ describe("QdrantVectorStore", () => { expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled() expect(mockQdrantClientInstance.deleteCollection).not.toHaveBeenCalled() - // Verify payload index creation still happens + // Verify payload index creation still happens - 'type' field first, then pathSegments + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { + field_name: "type", + field_schema: "keyword", + }) for (let i = 0; i <= 4; i++) { expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { field_name: `pathSegments.${i}`, field_schema: "keyword", }) } - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) }) it("should recreate collection if it exists but vectorSize mismatches and return true", async () => { const differentVectorSize = 768 @@ -625,14 +633,18 @@ describe("QdrantVectorStore", () => { }, }) - // Verify payload index creation + // Verify payload index creation - 'type' field first, then pathSegments + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { + field_name: "type", + field_schema: "keyword", + }) for (let i = 0; i <= 4; i++) { expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledWith(expectedCollectionName, { field_name: `pathSegments.${i}`, field_schema: "keyword", }) } - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) ;(console.warn as any).mockRestore() // Restore console.warn }) it("should log warning for non-404 errors but still create collection", async () => { @@ -646,7 +658,7 @@ describe("QdrantVectorStore", () => { expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(1) expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1) expect(mockQdrantClientInstance.deleteCollection).not.toHaveBeenCalled() - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) expect(console.warn).toHaveBeenCalledWith( expect.stringContaining(`Warning during getCollectionInfo for "${expectedCollectionName}"`), genericError.message, @@ -693,11 +705,16 @@ describe("QdrantVectorStore", () => { expect(result).toBe(true) expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1) - // Verify all payload index creations were attempted - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + // Verify all payload index creations were attempted (6: type + 5 pathSegments) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) - // Verify warnings were logged for each failed index - expect(console.warn).toHaveBeenCalledTimes(5) + // Verify warnings were logged for each failed index (now 6) + expect(console.warn).toHaveBeenCalledTimes(6) + // Verify warning for 'type' index + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining(`Could not create payload index for type`), + indexError.message, + ) for (let i = 0; i <= 4; i++) { expect(console.warn).toHaveBeenCalledWith( expect.stringContaining(`Could not create payload index for pathSegments.${i}`), @@ -826,7 +843,7 @@ describe("QdrantVectorStore", () => { expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(2) expect(mockQdrantClientInstance.deleteCollection).toHaveBeenCalledTimes(1) expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1) - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) ;(console.warn as any).mockRestore() }) @@ -923,7 +940,7 @@ describe("QdrantVectorStore", () => { on_disk: true, }, }) - expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(5) + expect(mockQdrantClientInstance.createPayloadIndex).toHaveBeenCalledTimes(6) ;(console.warn as any).mockRestore() })