@mhalder/qdrant-mcp-server 3.3.3 → 3.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +0 -2
- package/.github/workflows/claude-code-review.yml +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/biome.json +3 -2
- package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
- package/build/code/chunker/tree-sitter-chunker.js +2 -12
- package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +12 -18
- package/build/code/indexer.js.map +1 -1
- package/build/code/scanner.js +1 -1
- package/build/code/scanner.js.map +1 -1
- package/build/embeddings/cohere.d.ts +1 -1
- package/build/embeddings/cohere.d.ts.map +1 -1
- package/build/embeddings/cohere.js +2 -2
- package/build/embeddings/cohere.js.map +1 -1
- package/build/embeddings/cohere.test.js +1 -5
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/factory.d.ts +1 -1
- package/build/embeddings/factory.d.ts.map +1 -1
- package/build/embeddings/factory.js +7 -9
- package/build/embeddings/factory.js.map +1 -1
- package/build/embeddings/factory.test.js +3 -3
- package/build/embeddings/factory.test.js.map +1 -1
- package/build/embeddings/ollama.d.ts +1 -1
- package/build/embeddings/ollama.d.ts.map +1 -1
- package/build/embeddings/ollama.js +6 -8
- package/build/embeddings/ollama.js.map +1 -1
- package/build/embeddings/ollama.test.js +2 -6
- package/build/embeddings/ollama.test.js.map +1 -1
- package/build/embeddings/openai.d.ts +1 -1
- package/build/embeddings/openai.d.ts.map +1 -1
- package/build/embeddings/openai.js +4 -7
- package/build/embeddings/openai.js.map +1 -1
- package/build/embeddings/openai.test.js +3 -12
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/embeddings/sparse.test.js +12 -2
- package/build/embeddings/sparse.test.js.map +1 -1
- package/build/embeddings/voyage.d.ts +1 -1
- package/build/embeddings/voyage.d.ts.map +1 -1
- package/build/embeddings/voyage.js +2 -3
- package/build/embeddings/voyage.js.map +1 -1
- package/build/embeddings/voyage.test.js +2 -6
- package/build/embeddings/voyage.test.js.map +1 -1
- package/build/git/chunker.d.ts.map +1 -1
- package/build/git/chunker.js +2 -2
- package/build/git/chunker.js.map +1 -1
- package/build/git/chunker.test.js +1 -1
- package/build/git/chunker.test.js.map +1 -1
- package/build/git/extractor.d.ts.map +1 -1
- package/build/git/extractor.integration.test.js +9 -5
- package/build/git/extractor.integration.test.js.map +1 -1
- package/build/git/extractor.js +1 -1
- package/build/git/extractor.js.map +1 -1
- package/build/git/extractor.test.js +2 -2
- package/build/git/extractor.test.js.map +1 -1
- package/build/git/index.d.ts +4 -4
- package/build/git/index.d.ts.map +1 -1
- package/build/git/index.js +3 -3
- package/build/git/index.js.map +1 -1
- package/build/git/indexer.d.ts.map +1 -1
- package/build/git/indexer.js +9 -21
- package/build/git/indexer.js.map +1 -1
- package/build/git/indexer.test.js +4 -8
- package/build/git/indexer.test.js.map +1 -1
- package/build/git/sync/synchronizer.d.ts.map +1 -1
- package/build/git/sync/synchronizer.js.map +1 -1
- package/build/git/sync/synchronizer.test.js +4 -2
- package/build/git/sync/synchronizer.test.js.map +1 -1
- package/build/index.js +5 -9
- package/build/index.js.map +1 -1
- package/build/index.test.js +3 -3
- package/build/index.test.js.map +1 -1
- package/build/logger.d.ts.map +1 -1
- package/build/logger.js +1 -9
- package/build/logger.js.map +1 -1
- package/build/prompts/register.d.ts.map +1 -1
- package/build/prompts/register.js.map +1 -1
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +10 -34
- package/build/qdrant/client.test.js.map +1 -1
- package/build/resources/index.d.ts +1 -1
- package/build/resources/index.d.ts.map +1 -1
- package/build/resources/index.js +1 -1
- package/build/resources/index.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +3 -9
- package/build/tools/code.js.map +1 -1
- package/build/tools/collection.d.ts.map +1 -1
- package/build/tools/collection.js +1 -3
- package/build/tools/collection.js.map +1 -1
- package/build/tools/document.d.ts.map +1 -1
- package/build/tools/document.js +1 -1
- package/build/tools/document.js.map +1 -1
- package/build/tools/federated.d.ts.map +1 -1
- package/build/tools/federated.js +15 -6
- package/build/tools/federated.js.map +1 -1
- package/build/tools/federated.test.js +18 -22
- package/build/tools/federated.test.js.map +1 -1
- package/build/tools/git-history.d.ts.map +1 -1
- package/build/tools/git-history.js +3 -7
- package/build/tools/git-history.js.map +1 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +1 -3
- package/build/tools/logging.js.map +1 -1
- package/build/tools/logging.test.js +1 -1
- package/build/tools/logging.test.js.map +1 -1
- package/build/tools/schemas.d.ts.map +1 -1
- package/build/tools/schemas.js +17 -64
- package/build/tools/schemas.js.map +1 -1
- package/build/tools/search.d.ts.map +1 -1
- package/build/tools/search.js +1 -1
- package/build/tools/search.js.map +1 -1
- package/commitlint.config.js +12 -23
- package/package.json +1 -1
- package/scripts/verify-providers.js +12 -32
- package/src/code/chunker/tree-sitter-chunker.ts +9 -35
- package/src/code/indexer.ts +45 -107
- package/src/code/scanner.ts +1 -1
- package/src/embeddings/cohere.test.ts +17 -45
- package/src/embeddings/cohere.ts +10 -17
- package/src/embeddings/factory.test.ts +18 -18
- package/src/embeddings/factory.ts +18 -25
- package/src/embeddings/ollama.test.ts +38 -67
- package/src/embeddings/ollama.ts +15 -27
- package/src/embeddings/openai.test.ts +17 -53
- package/src/embeddings/openai.ts +11 -22
- package/src/embeddings/sparse.test.ts +12 -2
- package/src/embeddings/voyage.test.ts +39 -80
- package/src/embeddings/voyage.ts +9 -13
- package/src/git/chunker.test.ts +1 -1
- package/src/git/chunker.ts +6 -22
- package/src/git/extractor.integration.test.ts +12 -16
- package/src/git/extractor.test.ts +21 -35
- package/src/git/extractor.ts +14 -36
- package/src/git/index.ts +9 -10
- package/src/git/indexer.test.ts +29 -57
- package/src/git/indexer.ts +38 -86
- package/src/git/sync/synchronizer.test.ts +6 -9
- package/src/git/sync/synchronizer.ts +2 -5
- package/src/index.test.ts +7 -9
- package/src/index.ts +34 -80
- package/src/logger.ts +3 -14
- package/src/prompts/register.ts +3 -10
- package/src/qdrant/client.test.ts +63 -169
- package/src/qdrant/client.ts +19 -45
- package/src/resources/index.ts +4 -10
- package/src/tools/code.ts +43 -66
- package/src/tools/collection.ts +19 -38
- package/src/tools/document.ts +10 -19
- package/src/tools/federated.test.ts +34 -57
- package/src/tools/federated.ts +88 -108
- package/src/tools/git-history.ts +32 -60
- package/src/tools/index.ts +1 -4
- package/src/tools/logging.test.ts +10 -10
- package/src/tools/logging.ts +8 -18
- package/src/tools/schemas.ts +23 -78
- package/src/tools/search.ts +77 -94
- package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
- package/tests/code/indexer.test.ts +100 -192
- package/tests/code/integration.test.ts +61 -117
- package/tests/code/scanner.test.ts +12 -39
- package/tests/code/sync/snapshot.test.ts +4 -14
- package/tests/code/sync/synchronizer.test.ts +10 -40
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EmbeddingProviderFactory, type FactoryConfig } from "./factory.js";
|
|
3
|
-
import { OpenAIEmbeddings } from "./openai.js";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
2
|
import { CohereEmbeddings } from "./cohere.js";
|
|
5
|
-
import {
|
|
3
|
+
import { EmbeddingProviderFactory } from "./factory.js";
|
|
6
4
|
import { OllamaEmbeddings } from "./ollama.js";
|
|
5
|
+
import { OpenAIEmbeddings } from "./openai.js";
|
|
6
|
+
import { VoyageEmbeddings } from "./voyage.js";
|
|
7
7
|
|
|
8
8
|
vi.mock("../logger.js", () => ({
|
|
9
9
|
default: {
|
|
@@ -36,7 +36,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
36
36
|
expect(() =>
|
|
37
37
|
EmbeddingProviderFactory.create({
|
|
38
38
|
provider: "unknown" as any,
|
|
39
|
-
})
|
|
39
|
+
})
|
|
40
40
|
).toThrow("Unknown embedding provider: unknown");
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -44,7 +44,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
44
44
|
expect(() =>
|
|
45
45
|
EmbeddingProviderFactory.create({
|
|
46
46
|
provider: "invalid" as any,
|
|
47
|
-
})
|
|
47
|
+
})
|
|
48
48
|
).toThrow("openai, cohere, voyage, ollama");
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -54,7 +54,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
54
54
|
expect(() =>
|
|
55
55
|
EmbeddingProviderFactory.create({
|
|
56
56
|
provider: "openai",
|
|
57
|
-
})
|
|
57
|
+
})
|
|
58
58
|
).toThrow("API key is required for OpenAI provider");
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -110,7 +110,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
110
110
|
expect(() =>
|
|
111
111
|
EmbeddingProviderFactory.create({
|
|
112
112
|
provider: "cohere",
|
|
113
|
-
})
|
|
113
|
+
})
|
|
114
114
|
).toThrow("API key is required for Cohere provider");
|
|
115
115
|
});
|
|
116
116
|
|
|
@@ -151,7 +151,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
151
151
|
expect(() =>
|
|
152
152
|
EmbeddingProviderFactory.create({
|
|
153
153
|
provider: "voyage",
|
|
154
|
-
})
|
|
154
|
+
})
|
|
155
155
|
).toThrow("API key is required for Voyage AI provider");
|
|
156
156
|
});
|
|
157
157
|
|
|
@@ -357,7 +357,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
357
357
|
process.env.EMBEDDING_DIMENSIONS = "not-a-number";
|
|
358
358
|
|
|
359
359
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
360
|
-
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "not-a-number"'
|
|
360
|
+
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "not-a-number"'
|
|
361
361
|
);
|
|
362
362
|
});
|
|
363
363
|
|
|
@@ -367,7 +367,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
367
367
|
process.env.EMBEDDING_DIMENSIONS = "-100";
|
|
368
368
|
|
|
369
369
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
370
|
-
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "-100"'
|
|
370
|
+
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "-100"'
|
|
371
371
|
);
|
|
372
372
|
});
|
|
373
373
|
|
|
@@ -377,7 +377,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
377
377
|
process.env.EMBEDDING_DIMENSIONS = "0";
|
|
378
378
|
|
|
379
379
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
380
|
-
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "0"'
|
|
380
|
+
'Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "0"'
|
|
381
381
|
);
|
|
382
382
|
});
|
|
383
383
|
|
|
@@ -387,7 +387,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
387
387
|
process.env.EMBEDDING_MAX_REQUESTS_PER_MINUTE = "invalid";
|
|
388
388
|
|
|
389
389
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
390
|
-
'Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "invalid"'
|
|
390
|
+
'Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "invalid"'
|
|
391
391
|
);
|
|
392
392
|
});
|
|
393
393
|
|
|
@@ -397,7 +397,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
397
397
|
process.env.EMBEDDING_MAX_REQUESTS_PER_MINUTE = "-50";
|
|
398
398
|
|
|
399
399
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
400
|
-
'Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "-50"'
|
|
400
|
+
'Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "-50"'
|
|
401
401
|
);
|
|
402
402
|
});
|
|
403
403
|
|
|
@@ -407,7 +407,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
407
407
|
process.env.EMBEDDING_RETRY_ATTEMPTS = "abc";
|
|
408
408
|
|
|
409
409
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
410
|
-
'Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "abc"'
|
|
410
|
+
'Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "abc"'
|
|
411
411
|
);
|
|
412
412
|
});
|
|
413
413
|
|
|
@@ -417,7 +417,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
417
417
|
process.env.EMBEDDING_RETRY_ATTEMPTS = "-5";
|
|
418
418
|
|
|
419
419
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
420
|
-
'Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "-5"'
|
|
420
|
+
'Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "-5"'
|
|
421
421
|
);
|
|
422
422
|
});
|
|
423
423
|
|
|
@@ -427,7 +427,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
427
427
|
process.env.EMBEDDING_RETRY_DELAY = "xyz";
|
|
428
428
|
|
|
429
429
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
430
|
-
'Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "xyz"'
|
|
430
|
+
'Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "xyz"'
|
|
431
431
|
);
|
|
432
432
|
});
|
|
433
433
|
|
|
@@ -437,7 +437,7 @@ describe("EmbeddingProviderFactory", () => {
|
|
|
437
437
|
process.env.EMBEDDING_RETRY_DELAY = "-1000";
|
|
438
438
|
|
|
439
439
|
expect(() => EmbeddingProviderFactory.createFromEnv()).toThrow(
|
|
440
|
-
'Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "-1000"'
|
|
440
|
+
'Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "-1000"'
|
|
441
441
|
);
|
|
442
442
|
});
|
|
443
443
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logger from "../logger.js";
|
|
2
|
-
import { EmbeddingProvider, ProviderConfig } from "./base.js";
|
|
3
|
-
import { OpenAIEmbeddings } from "./openai.js";
|
|
2
|
+
import type { EmbeddingProvider, ProviderConfig } from "./base.js";
|
|
4
3
|
import { CohereEmbeddings } from "./cohere.js";
|
|
5
|
-
import { VoyageEmbeddings } from "./voyage.js";
|
|
6
4
|
import { OllamaEmbeddings } from "./ollama.js";
|
|
5
|
+
import { OpenAIEmbeddings } from "./openai.js";
|
|
6
|
+
import { VoyageEmbeddings } from "./voyage.js";
|
|
7
7
|
|
|
8
8
|
export type EmbeddingProviderType = "openai" | "cohere" | "voyage" | "ollama";
|
|
9
9
|
|
|
@@ -13,8 +13,7 @@ export interface FactoryConfig extends ProviderConfig {
|
|
|
13
13
|
|
|
14
14
|
export class EmbeddingProviderFactory {
|
|
15
15
|
static create(config: FactoryConfig): EmbeddingProvider {
|
|
16
|
-
const { provider, model, dimensions, rateLimitConfig, apiKey, baseUrl } =
|
|
17
|
-
config;
|
|
16
|
+
const { provider, model, dimensions, rateLimitConfig, apiKey, baseUrl } = config;
|
|
18
17
|
|
|
19
18
|
logger.info({ provider, model }, "Creating embedding provider");
|
|
20
19
|
|
|
@@ -27,7 +26,7 @@ export class EmbeddingProviderFactory {
|
|
|
27
26
|
apiKey,
|
|
28
27
|
model || "text-embedding-3-small",
|
|
29
28
|
dimensions,
|
|
30
|
-
rateLimitConfig
|
|
29
|
+
rateLimitConfig
|
|
31
30
|
);
|
|
32
31
|
|
|
33
32
|
case "cohere":
|
|
@@ -38,7 +37,7 @@ export class EmbeddingProviderFactory {
|
|
|
38
37
|
apiKey,
|
|
39
38
|
model || "embed-english-v3.0",
|
|
40
39
|
dimensions,
|
|
41
|
-
rateLimitConfig
|
|
40
|
+
rateLimitConfig
|
|
42
41
|
);
|
|
43
42
|
|
|
44
43
|
case "voyage":
|
|
@@ -50,7 +49,7 @@ export class EmbeddingProviderFactory {
|
|
|
50
49
|
model || "voyage-2",
|
|
51
50
|
dimensions,
|
|
52
51
|
rateLimitConfig,
|
|
53
|
-
baseUrl || "https://api.voyageai.com/v1"
|
|
52
|
+
baseUrl || "https://api.voyageai.com/v1"
|
|
54
53
|
);
|
|
55
54
|
|
|
56
55
|
case "ollama":
|
|
@@ -58,12 +57,12 @@ export class EmbeddingProviderFactory {
|
|
|
58
57
|
model || "nomic-embed-text",
|
|
59
58
|
dimensions,
|
|
60
59
|
rateLimitConfig,
|
|
61
|
-
baseUrl || "http://localhost:11434"
|
|
60
|
+
baseUrl || "http://localhost:11434"
|
|
62
61
|
);
|
|
63
62
|
|
|
64
63
|
default:
|
|
65
64
|
throw new Error(
|
|
66
|
-
`Unknown embedding provider: ${provider}. Supported providers: openai, cohere, voyage, ollama
|
|
65
|
+
`Unknown embedding provider: ${provider}. Supported providers: openai, cohere, voyage, ollama`
|
|
67
66
|
);
|
|
68
67
|
}
|
|
69
68
|
}
|
|
@@ -97,9 +96,9 @@ export class EmbeddingProviderFactory {
|
|
|
97
96
|
: undefined;
|
|
98
97
|
|
|
99
98
|
// Validate dimensions
|
|
100
|
-
if (dimensions !== undefined && (isNaN(dimensions) || dimensions <= 0)) {
|
|
99
|
+
if (dimensions !== undefined && (Number.isNaN(dimensions) || dimensions <= 0)) {
|
|
101
100
|
throw new Error(
|
|
102
|
-
`Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "${process.env.EMBEDDING_DIMENSIONS}"
|
|
101
|
+
`Invalid EMBEDDING_DIMENSIONS: must be a positive integer, got "${process.env.EMBEDDING_DIMENSIONS}"`
|
|
103
102
|
);
|
|
104
103
|
}
|
|
105
104
|
|
|
@@ -113,10 +112,10 @@ export class EmbeddingProviderFactory {
|
|
|
113
112
|
// Validate maxRequestsPerMinute
|
|
114
113
|
if (
|
|
115
114
|
maxRequestsPerMinute !== undefined &&
|
|
116
|
-
(isNaN(maxRequestsPerMinute) || maxRequestsPerMinute <= 0)
|
|
115
|
+
(Number.isNaN(maxRequestsPerMinute) || maxRequestsPerMinute <= 0)
|
|
117
116
|
) {
|
|
118
117
|
throw new Error(
|
|
119
|
-
`Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "${process.env.EMBEDDING_MAX_REQUESTS_PER_MINUTE}"
|
|
118
|
+
`Invalid EMBEDDING_MAX_REQUESTS_PER_MINUTE: must be a positive integer, got "${process.env.EMBEDDING_MAX_REQUESTS_PER_MINUTE}"`
|
|
120
119
|
);
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -125,12 +124,9 @@ export class EmbeddingProviderFactory {
|
|
|
125
124
|
: undefined;
|
|
126
125
|
|
|
127
126
|
// Validate retryAttempts
|
|
128
|
-
if (
|
|
129
|
-
retryAttempts !== undefined &&
|
|
130
|
-
(isNaN(retryAttempts) || retryAttempts < 0)
|
|
131
|
-
) {
|
|
127
|
+
if (retryAttempts !== undefined && (Number.isNaN(retryAttempts) || retryAttempts < 0)) {
|
|
132
128
|
throw new Error(
|
|
133
|
-
`Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "${process.env.EMBEDDING_RETRY_ATTEMPTS}"
|
|
129
|
+
`Invalid EMBEDDING_RETRY_ATTEMPTS: must be a non-negative integer, got "${process.env.EMBEDDING_RETRY_ATTEMPTS}"`
|
|
134
130
|
);
|
|
135
131
|
}
|
|
136
132
|
|
|
@@ -139,12 +135,9 @@ export class EmbeddingProviderFactory {
|
|
|
139
135
|
: undefined;
|
|
140
136
|
|
|
141
137
|
// Validate retryDelayMs
|
|
142
|
-
if (
|
|
143
|
-
retryDelayMs !== undefined &&
|
|
144
|
-
(isNaN(retryDelayMs) || retryDelayMs < 0)
|
|
145
|
-
) {
|
|
138
|
+
if (retryDelayMs !== undefined && (Number.isNaN(retryDelayMs) || retryDelayMs < 0)) {
|
|
146
139
|
throw new Error(
|
|
147
|
-
`Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "${process.env.EMBEDDING_RETRY_DELAY}"
|
|
140
|
+
`Invalid EMBEDDING_RETRY_DELAY: must be a non-negative integer, got "${process.env.EMBEDDING_RETRY_DELAY}"`
|
|
148
141
|
);
|
|
149
142
|
}
|
|
150
143
|
|
|
@@ -154,7 +147,7 @@ export class EmbeddingProviderFactory {
|
|
|
154
147
|
retryDelayMs,
|
|
155
148
|
};
|
|
156
149
|
|
|
157
|
-
return
|
|
150
|
+
return EmbeddingProviderFactory.create({
|
|
158
151
|
provider,
|
|
159
152
|
model,
|
|
160
153
|
dimensions,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { OllamaEmbeddings } from "./ollama.js";
|
|
3
3
|
|
|
4
4
|
// Mock fetch globally
|
|
@@ -54,7 +54,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
54
54
|
"nomic-embed-text",
|
|
55
55
|
undefined,
|
|
56
56
|
undefined,
|
|
57
|
-
"http://custom:11434"
|
|
57
|
+
"http://custom:11434"
|
|
58
58
|
);
|
|
59
59
|
expect(customEmbeddings).toBeDefined();
|
|
60
60
|
});
|
|
@@ -89,19 +89,16 @@ describe("OllamaEmbeddings", () => {
|
|
|
89
89
|
embedding: mockEmbedding,
|
|
90
90
|
dimensions: 768,
|
|
91
91
|
});
|
|
92
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
headers: {
|
|
97
|
-
"Content-Type": "application/json",
|
|
98
|
-
},
|
|
99
|
-
body: JSON.stringify({
|
|
100
|
-
model: "nomic-embed-text",
|
|
101
|
-
prompt: "test text",
|
|
102
|
-
}),
|
|
92
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:11434/api/embeddings", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "application/json",
|
|
103
96
|
},
|
|
104
|
-
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
model: "nomic-embed-text",
|
|
99
|
+
prompt: "test text",
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
105
102
|
});
|
|
106
103
|
|
|
107
104
|
it("should handle long text", async () => {
|
|
@@ -124,7 +121,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
124
121
|
"nomic-embed-text",
|
|
125
122
|
undefined,
|
|
126
123
|
undefined,
|
|
127
|
-
"http://custom:11434"
|
|
124
|
+
"http://custom:11434"
|
|
128
125
|
);
|
|
129
126
|
|
|
130
127
|
const mockEmbedding = Array(768).fill(0.1);
|
|
@@ -139,7 +136,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
139
136
|
|
|
140
137
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
141
138
|
"http://custom:11434/api/embeddings",
|
|
142
|
-
expect.any(Object)
|
|
139
|
+
expect.any(Object)
|
|
143
140
|
);
|
|
144
141
|
});
|
|
145
142
|
|
|
@@ -150,7 +147,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
150
147
|
});
|
|
151
148
|
|
|
152
149
|
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
153
|
-
"No embedding returned from Ollama API"
|
|
150
|
+
"No embedding returned from Ollama API"
|
|
154
151
|
);
|
|
155
152
|
});
|
|
156
153
|
|
|
@@ -181,7 +178,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
181
178
|
await expect(embeddings.embed(longText)).rejects.toThrow(
|
|
182
179
|
expect.objectContaining({
|
|
183
180
|
message: expect.stringContaining("Text preview:"),
|
|
184
|
-
})
|
|
181
|
+
})
|
|
185
182
|
);
|
|
186
183
|
});
|
|
187
184
|
|
|
@@ -189,7 +186,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
189
186
|
mockFetch.mockRejectedValue("Connection refused");
|
|
190
187
|
|
|
191
188
|
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
192
|
-
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text"
|
|
189
|
+
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text"
|
|
193
190
|
);
|
|
194
191
|
});
|
|
195
192
|
|
|
@@ -198,27 +195,21 @@ describe("OllamaEmbeddings", () => {
|
|
|
198
195
|
message: "Custom error message",
|
|
199
196
|
});
|
|
200
197
|
|
|
201
|
-
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
202
|
-
"Custom error message",
|
|
203
|
-
);
|
|
198
|
+
await expect(embeddings.embed("test")).rejects.toThrow("Custom error message");
|
|
204
199
|
});
|
|
205
200
|
|
|
206
201
|
it("should handle non-Error objects in catch block", async () => {
|
|
207
202
|
mockFetch.mockRejectedValue({ code: "ERR_UNKNOWN", details: "info" });
|
|
208
203
|
|
|
209
204
|
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
210
|
-
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text"
|
|
205
|
+
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text"
|
|
211
206
|
);
|
|
212
207
|
});
|
|
213
208
|
});
|
|
214
209
|
|
|
215
210
|
describe("embedBatch", () => {
|
|
216
211
|
it("should generate embeddings for multiple texts in parallel", async () => {
|
|
217
|
-
const mockEmbeddings = [
|
|
218
|
-
Array(768).fill(0.1),
|
|
219
|
-
Array(768).fill(0.2),
|
|
220
|
-
Array(768).fill(0.3),
|
|
221
|
-
];
|
|
212
|
+
const mockEmbeddings = [Array(768).fill(0.1), Array(768).fill(0.2), Array(768).fill(0.3)];
|
|
222
213
|
|
|
223
214
|
// Mock sequential calls for each text
|
|
224
215
|
mockEmbeddings.forEach((embedding) => {
|
|
@@ -290,9 +281,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
290
281
|
})
|
|
291
282
|
.mockRejectedValueOnce(new Error("Batch API Error"));
|
|
292
283
|
|
|
293
|
-
await expect(embeddings.embedBatch(["text1", "text2"])).rejects.toThrow(
|
|
294
|
-
"Batch API Error",
|
|
295
|
-
);
|
|
284
|
+
await expect(embeddings.embedBatch(["text1", "text2"])).rejects.toThrow("Batch API Error");
|
|
296
285
|
});
|
|
297
286
|
|
|
298
287
|
it("should handle partial failures in batch", async () => {
|
|
@@ -378,14 +367,10 @@ describe("OllamaEmbeddings", () => {
|
|
|
378
367
|
});
|
|
379
368
|
|
|
380
369
|
it("should use exponential backoff with faster default delay", async () => {
|
|
381
|
-
const rateLimitEmbeddings = new OllamaEmbeddings(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
retryAttempts: 3,
|
|
386
|
-
retryDelayMs: 100,
|
|
387
|
-
},
|
|
388
|
-
);
|
|
370
|
+
const rateLimitEmbeddings = new OllamaEmbeddings("nomic-embed-text", undefined, {
|
|
371
|
+
retryAttempts: 3,
|
|
372
|
+
retryDelayMs: 100,
|
|
373
|
+
});
|
|
389
374
|
|
|
390
375
|
const mockEmbedding = Array(768).fill(0.5);
|
|
391
376
|
|
|
@@ -414,14 +399,10 @@ describe("OllamaEmbeddings", () => {
|
|
|
414
399
|
});
|
|
415
400
|
|
|
416
401
|
it("should throw error after max retries exceeded", async () => {
|
|
417
|
-
const rateLimitEmbeddings = new OllamaEmbeddings(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
retryAttempts: 2,
|
|
422
|
-
retryDelayMs: 100,
|
|
423
|
-
},
|
|
424
|
-
);
|
|
402
|
+
const rateLimitEmbeddings = new OllamaEmbeddings("nomic-embed-text", undefined, {
|
|
403
|
+
retryAttempts: 2,
|
|
404
|
+
retryDelayMs: 100,
|
|
405
|
+
});
|
|
425
406
|
|
|
426
407
|
mockFetch.mockResolvedValue({
|
|
427
408
|
ok: false,
|
|
@@ -430,7 +411,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
430
411
|
});
|
|
431
412
|
|
|
432
413
|
await expect(rateLimitEmbeddings.embed("test text")).rejects.toThrow(
|
|
433
|
-
"Ollama API rate limit exceeded after 2 retry attempts"
|
|
414
|
+
"Ollama API rate limit exceeded after 2 retry attempts"
|
|
434
415
|
);
|
|
435
416
|
|
|
436
417
|
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
@@ -473,15 +454,11 @@ describe("OllamaEmbeddings", () => {
|
|
|
473
454
|
});
|
|
474
455
|
|
|
475
456
|
it("should accept custom rate limit configuration", () => {
|
|
476
|
-
const customEmbeddings = new OllamaEmbeddings(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
retryAttempts: 5,
|
|
482
|
-
retryDelayMs: 1000,
|
|
483
|
-
},
|
|
484
|
-
);
|
|
457
|
+
const customEmbeddings = new OllamaEmbeddings("nomic-embed-text", undefined, {
|
|
458
|
+
maxRequestsPerMinute: 2000,
|
|
459
|
+
retryAttempts: 5,
|
|
460
|
+
retryDelayMs: 1000,
|
|
461
|
+
});
|
|
485
462
|
|
|
486
463
|
expect(customEmbeddings).toBeDefined();
|
|
487
464
|
});
|
|
@@ -503,9 +480,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
503
480
|
it("should handle string primitive errors", async () => {
|
|
504
481
|
mockFetch.mockRejectedValue("Network unreachable");
|
|
505
482
|
|
|
506
|
-
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
507
|
-
"Network unreachable",
|
|
508
|
-
);
|
|
483
|
+
await expect(embeddings.embed("test")).rejects.toThrow("Network unreachable");
|
|
509
484
|
});
|
|
510
485
|
|
|
511
486
|
it("should handle error objects with non-string message property", async () => {
|
|
@@ -523,9 +498,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
523
498
|
const testError = new Error("Connection timeout");
|
|
524
499
|
mockFetch.mockRejectedValue(testError);
|
|
525
500
|
|
|
526
|
-
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
527
|
-
"Connection timeout",
|
|
528
|
-
);
|
|
501
|
+
await expect(embeddings.embed("test")).rejects.toThrow("Connection timeout");
|
|
529
502
|
});
|
|
530
503
|
|
|
531
504
|
it("should handle Error instance from network error with enhanced message", async () => {
|
|
@@ -534,7 +507,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
534
507
|
mockFetch.mockRejectedValue(networkError);
|
|
535
508
|
|
|
536
509
|
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
537
|
-
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text: ECONNREFUSED. Text preview:"
|
|
510
|
+
"Failed to call Ollama API at http://localhost:11434 with model nomic-embed-text: ECONNREFUSED. Text preview:"
|
|
538
511
|
);
|
|
539
512
|
});
|
|
540
513
|
|
|
@@ -547,9 +520,7 @@ describe("OllamaEmbeddings", () => {
|
|
|
547
520
|
};
|
|
548
521
|
mockFetch.mockRejectedValue(customError);
|
|
549
522
|
|
|
550
|
-
await expect(embeddings.embed("test")).rejects.toThrow(
|
|
551
|
-
"Custom API failure",
|
|
552
|
-
);
|
|
523
|
+
await expect(embeddings.embed("test")).rejects.toThrow("Custom API failure");
|
|
553
524
|
});
|
|
554
525
|
});
|
|
555
526
|
});
|
package/src/embeddings/ollama.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Bottleneck from "bottleneck";
|
|
2
2
|
import logger from "../logger.js";
|
|
3
|
-
import { EmbeddingProvider, EmbeddingResult, RateLimitConfig } from "./base.js";
|
|
3
|
+
import type { EmbeddingProvider, EmbeddingResult, RateLimitConfig } from "./base.js";
|
|
4
4
|
|
|
5
5
|
interface OllamaError {
|
|
6
6
|
status?: number;
|
|
@@ -24,7 +24,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
24
24
|
model: string = "nomic-embed-text",
|
|
25
25
|
dimensions?: number,
|
|
26
26
|
rateLimitConfig?: RateLimitConfig,
|
|
27
|
-
baseUrl: string = "http://localhost:11434"
|
|
27
|
+
baseUrl: string = "http://localhost:11434"
|
|
28
28
|
) {
|
|
29
29
|
this.model = model;
|
|
30
30
|
this.baseUrl = baseUrl;
|
|
@@ -53,22 +53,15 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
private isOllamaError(e: unknown): e is OllamaError {
|
|
56
|
-
return (
|
|
57
|
-
typeof e === "object" && e !== null && ("status" in e || "message" in e)
|
|
58
|
-
);
|
|
56
|
+
return typeof e === "object" && e !== null && ("status" in e || "message" in e);
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
private async retryWithBackoff<T>(
|
|
62
|
-
fn: () => Promise<T>,
|
|
63
|
-
attempt: number = 0,
|
|
64
|
-
): Promise<T> {
|
|
59
|
+
private async retryWithBackoff<T>(fn: () => Promise<T>, attempt: number = 0): Promise<T> {
|
|
65
60
|
try {
|
|
66
61
|
return await fn();
|
|
67
62
|
} catch (error: unknown) {
|
|
68
63
|
// Type guard for OllamaError
|
|
69
|
-
const apiError = this.isOllamaError(error)
|
|
70
|
-
? error
|
|
71
|
-
: { status: 0, message: String(error) };
|
|
64
|
+
const apiError = this.isOllamaError(error) ? error : { status: 0, message: String(error) };
|
|
72
65
|
|
|
73
66
|
const isRateLimitError =
|
|
74
67
|
apiError.status === 429 ||
|
|
@@ -76,7 +69,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
76
69
|
apiError.message.toLowerCase().includes("rate limit"));
|
|
77
70
|
|
|
78
71
|
if (isRateLimitError && attempt < this.retryAttempts) {
|
|
79
|
-
const delayMs = this.retryDelayMs *
|
|
72
|
+
const delayMs = this.retryDelayMs * 2 ** attempt;
|
|
80
73
|
const waitTimeSeconds = (delayMs / 1000).toFixed(1);
|
|
81
74
|
this.log.warn(
|
|
82
75
|
{
|
|
@@ -84,7 +77,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
84
77
|
attempt: attempt + 1,
|
|
85
78
|
maxAttempts: this.retryAttempts,
|
|
86
79
|
},
|
|
87
|
-
"Rate limit reached, retrying"
|
|
80
|
+
"Rate limit reached, retrying"
|
|
88
81
|
);
|
|
89
82
|
|
|
90
83
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -93,7 +86,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
93
86
|
|
|
94
87
|
if (isRateLimitError) {
|
|
95
88
|
throw new Error(
|
|
96
|
-
`Ollama API rate limit exceeded after ${this.retryAttempts} retry attempts. Please try again later or reduce request frequency
|
|
89
|
+
`Ollama API rate limit exceeded after ${this.retryAttempts} retry attempts. Please try again later or reduce request frequency.`
|
|
97
90
|
);
|
|
98
91
|
}
|
|
99
92
|
|
|
@@ -116,8 +109,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
116
109
|
|
|
117
110
|
if (!response.ok) {
|
|
118
111
|
const errorBody = await response.text();
|
|
119
|
-
const textPreview =
|
|
120
|
-
text.length > 100 ? text.substring(0, 100) + "..." : text;
|
|
112
|
+
const textPreview = text.length > 100 ? `${text.substring(0, 100)}...` : text;
|
|
121
113
|
const error: OllamaError = {
|
|
122
114
|
status: response.status,
|
|
123
115
|
message: `Ollama API error (${response.status}) for model "${this.model}": ${errorBody}. Text preview: "${textPreview}"`,
|
|
@@ -134,10 +126,9 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
134
126
|
|
|
135
127
|
// For Error instances (like network errors), enhance the message
|
|
136
128
|
if (error instanceof Error) {
|
|
137
|
-
const textPreview =
|
|
138
|
-
text.length > 100 ? text.substring(0, 100) + "..." : text;
|
|
129
|
+
const textPreview = text.length > 100 ? `${text.substring(0, 100)}...` : text;
|
|
139
130
|
throw new Error(
|
|
140
|
-
`Failed to call Ollama API at ${this.baseUrl} with model ${this.model}: ${error.message}. Text preview: "${textPreview}"
|
|
131
|
+
`Failed to call Ollama API at ${this.baseUrl} with model ${this.model}: ${error.message}. Text preview: "${textPreview}"`
|
|
141
132
|
);
|
|
142
133
|
}
|
|
143
134
|
|
|
@@ -148,12 +139,11 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
148
139
|
}
|
|
149
140
|
|
|
150
141
|
// For other types, create a descriptive error message
|
|
151
|
-
const textPreview =
|
|
152
|
-
text.length > 100 ? text.substring(0, 100) + "..." : text;
|
|
142
|
+
const textPreview = text.length > 100 ? `${text.substring(0, 100)}...` : text;
|
|
153
143
|
const errorMessage = JSON.stringify(error);
|
|
154
144
|
|
|
155
145
|
throw new Error(
|
|
156
|
-
`Failed to call Ollama API at ${this.baseUrl} with model ${this.model}: ${errorMessage}. Text preview: "${textPreview}"
|
|
146
|
+
`Failed to call Ollama API at ${this.baseUrl} with model ${this.model}: ${errorMessage}. Text preview: "${textPreview}"`
|
|
157
147
|
);
|
|
158
148
|
}
|
|
159
149
|
}
|
|
@@ -171,7 +161,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
171
161
|
embedding: response.embedding,
|
|
172
162
|
dimensions: this.dimensions,
|
|
173
163
|
};
|
|
174
|
-
})
|
|
164
|
+
})
|
|
175
165
|
);
|
|
176
166
|
}
|
|
177
167
|
|
|
@@ -185,9 +175,7 @@ export class OllamaEmbeddings implements EmbeddingProvider {
|
|
|
185
175
|
for (let i = 0; i < texts.length; i += CHUNK_SIZE) {
|
|
186
176
|
const chunk = texts.slice(i, i + CHUNK_SIZE);
|
|
187
177
|
// The Bottleneck limiter will handle rate limiting and concurrency (maxConcurrent: 10)
|
|
188
|
-
const chunkResults = await Promise.all(
|
|
189
|
-
chunk.map((text) => this.embed(text)),
|
|
190
|
-
);
|
|
178
|
+
const chunkResults = await Promise.all(chunk.map((text) => this.embed(text)));
|
|
191
179
|
results.push(...chunkResults);
|
|
192
180
|
}
|
|
193
181
|
|