fix(qdrant): add URL prefix handling for QdrantClient initialization (#5033)

This commit is contained in:
Chiao-Wei Wang 2025-06-24 02:00:53 +08:00 committed by GitHub
parent 1f05caa569
commit 3fda1efbd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 161 additions and 0 deletions

View file

@ -113,6 +113,7 @@ describe("QdrantVectorStore", () => {
host: "qdrant.ashbyfam.com",
https: true,
port: 443,
prefix: undefined, // No prefix for root path
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -127,6 +128,7 @@ describe("QdrantVectorStore", () => {
host: "example.com",
https: true,
port: 9000,
prefix: undefined, // No prefix for root path
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -145,6 +147,7 @@ describe("QdrantVectorStore", () => {
host: "example.com",
https: true,
port: 443,
prefix: "/api/v1", // Should have prefix
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -161,6 +164,7 @@ describe("QdrantVectorStore", () => {
host: "example.com",
https: false,
port: 80,
prefix: undefined, // No prefix for root path
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -175,6 +179,7 @@ describe("QdrantVectorStore", () => {
host: "localhost",
https: false,
port: 8080,
prefix: undefined, // No prefix for root path
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -193,6 +198,7 @@ describe("QdrantVectorStore", () => {
host: "example.com",
https: false,
port: 80,
prefix: "/api/v1", // Should have prefix
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
@ -337,6 +343,159 @@ describe("QdrantVectorStore", () => {
})
})
describe("URL Prefix Handling", () => {
it("should pass the URL pathname as prefix to QdrantClient if not root", () => {
const vectorStoreWithPrefix = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/some/path",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: "/some/path",
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithPrefix as any).qdrantUrl).toBe("http://localhost:6333/some/path")
})
it("should not pass prefix if the URL pathname is root ('/')", () => {
const vectorStoreWithoutPrefix = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: undefined,
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithoutPrefix as any).qdrantUrl).toBe("http://localhost:6333/")
})
it("should handle HTTPS URL with path as prefix", () => {
const vectorStoreWithHttpsPrefix = new QdrantVectorStore(
mockWorkspacePath,
"https://qdrant.ashbyfam.com/api",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "qdrant.ashbyfam.com",
https: true,
port: 443,
prefix: "/api",
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithHttpsPrefix as any).qdrantUrl).toBe("https://qdrant.ashbyfam.com/api")
})
it("should normalize URL pathname by removing trailing slash for prefix", () => {
const vectorStoreWithTrailingSlash = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/api/",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: "/api", // Trailing slash should be removed
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithTrailingSlash as any).qdrantUrl).toBe("http://localhost:6333/api/")
})
it("should normalize URL pathname by removing multiple trailing slashes for prefix", () => {
const vectorStoreWithMultipleTrailingSlashes = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/api///",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: "/api", // All trailing slashes should be removed
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithMultipleTrailingSlashes as any).qdrantUrl).toBe("http://localhost:6333/api///")
})
it("should handle multiple path segments correctly for prefix", () => {
const vectorStoreWithMultiSegment = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/api/v1/qdrant",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: "/api/v1/qdrant",
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithMultiSegment as any).qdrantUrl).toBe("http://localhost:6333/api/v1/qdrant")
})
it("should handle complex URL with multiple segments, multiple trailing slashes, query params, and fragment", () => {
const complexUrl = "https://example.com/ollama/api/v1///?key=value#pos"
const vectorStoreComplex = new QdrantVectorStore(mockWorkspacePath, complexUrl, mockVectorSize)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "example.com",
https: true,
port: 443,
prefix: "/ollama/api/v1", // Trailing slash removed, query/fragment ignored
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreComplex as any).qdrantUrl).toBe(complexUrl)
})
it("should ignore query parameters and fragments when determining prefix", () => {
const vectorStoreWithQueryParams = new QdrantVectorStore(
mockWorkspacePath,
"http://localhost:6333/api/path?key=value#fragment",
mockVectorSize,
)
expect(QdrantClient).toHaveBeenLastCalledWith({
host: "localhost",
https: false,
port: 6333,
prefix: "/api/path", // Query params and fragment should be ignored
apiKey: undefined,
headers: {
"User-Agent": "Roo-Code",
},
})
expect((vectorStoreWithQueryParams as any).qdrantUrl).toBe(
"http://localhost:6333/api/path?key=value#fragment",
)
})
})
describe("initialize", () => {
it("should create a new collection if none exists and return true", async () => {
// Mock getCollection to throw a 404-like error

View file

@ -57,6 +57,7 @@ export class QdrantVectorStore implements IVectorStore {
host: urlObj.hostname,
https: useHttps,
port: port,
prefix: urlObj.pathname === "/" ? undefined : urlObj.pathname.replace(/\/+$/, ""),
apiKey,
headers: {
"User-Agent": "Roo-Code",
@ -64,6 +65,7 @@ export class QdrantVectorStore implements IVectorStore {
})
} catch (urlError) {
// If URL parsing fails, fall back to URL-based config
// Note: This fallback won't correctly handle prefixes, but it's a last resort for malformed URLs.
this.client = new QdrantClient({
url: parsedUrl,
apiKey,