@devilsdev/rag-pipeline-utils 2.4.4 → 2.5.0
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/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +345 -0
- package/README.md +418 -117
- package/SECURITY.md +5 -5
- package/dist/experimental.cjs +2 -0
- package/dist/experimental.d.ts +1 -0
- package/dist/experimental.mjs +5 -0
- package/dist/index.cjs +4992 -1242
- package/dist/index.d.ts +80 -2
- package/dist/index.mjs +1 -1
- package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
- package/dist/types/agentic/iterative-retriever.d.ts +85 -0
- package/dist/types/agentic/query-planner.d.ts +145 -0
- package/dist/types/agentic/self-critique.d.ts +45 -0
- package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
- package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
- package/dist/types/ai/multimodal-processing.d.ts +32 -0
- package/dist/types/ai/multimodal.d.ts +39 -0
- package/dist/types/ai/retrieval-engine.d.ts +37 -0
- package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
- package/dist/types/chunking/chunking-engine.d.ts +70 -0
- package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
- package/dist/types/chunking/strategies/recursive.d.ts +20 -0
- package/dist/types/chunking/strategies/semantic.d.ts +19 -0
- package/dist/types/chunking/strategies/sentence.d.ts +13 -0
- package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
- package/dist/types/citation/citation-tracker.d.ts +64 -0
- package/dist/types/citation/hallucination-detector.d.ts +40 -0
- package/dist/types/citation/source-mapper.d.ts +37 -0
- package/dist/types/connectors/anthropic-connector.d.ts +92 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +89 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +86 -0
- package/dist/types/connectors/memory-retriever.d.ts +91 -0
- package/dist/types/connectors/ollama-connector.d.ts +110 -0
- package/dist/types/connectors/openai-connector.d.ts +114 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/cost/cost-calculator.d.ts +152 -0
- package/dist/types/cost/cost-tracker.d.ts +126 -0
- package/dist/types/cost/token-budget.d.ts +110 -0
- package/dist/types/dag/core/error-context.d.ts +239 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
- package/dist/types/dag/core/topology-validation.d.ts +226 -0
- package/dist/types/dag/dag-engine.d.ts +156 -0
- package/dist/types/dag/dag-node.d.ts +37 -0
- package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
- package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
- package/dist/types/debug/execution-tracer.d.ts +57 -0
- package/dist/types/debug/trace-inspector.d.ts +31 -0
- package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
- package/dist/types/enterprise/data-governance.d.ts +203 -0
- package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
- package/dist/types/enterprise/sso-integration.d.ts +420 -0
- package/dist/types/evaluate/context-metrics.d.ts +55 -0
- package/dist/types/evaluate/faithfulness.d.ts +41 -0
- package/dist/types/evaluate/groundedness.d.ts +60 -0
- package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
- package/dist/types/evaluate/relevance.d.ts +21 -0
- package/dist/types/evaluate/scoring.d.ts +123 -0
- package/dist/types/experimental.d.ts +15 -0
- package/dist/types/graph/entity-extractor.d.ts +84 -0
- package/dist/types/graph/graph-index.d.ts +49 -0
- package/dist/types/graph/graph-retriever.d.ts +81 -0
- package/dist/types/graph/knowledge-graph.d.ts +129 -0
- package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
- package/dist/types/guardrails/injection-detector.d.ts +47 -0
- package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
- package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
- package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
- package/dist/types/mcp/mcp-server.d.ts +85 -0
- package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
- package/dist/types/reranker/cascade-reranker.d.ts +40 -0
- package/dist/types/reranker/embedding-reranker.d.ts +77 -0
- package/dist/types/reranker/scoring-reranker.d.ts +41 -0
- package/dist/types/retrieval/bm25-search.d.ts +43 -0
- package/dist/types/retrieval/file-vector-store.d.ts +87 -0
- package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
- package/dist/types/retrieval/rank-fusion.d.ts +10 -0
- package/dist/types/security/jwt-config.d.ts +80 -0
- package/dist/types/security/jwt-validator.d.ts +195 -0
- package/dist/types/security/plugin-sandbox.d.ts +158 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
- package/dist/types/security/validator.d.ts +5 -0
- package/dist/types/streaming/embedding-stream.d.ts +65 -0
- package/dist/types/streaming/sse-adapter.d.ts +59 -0
- package/dist/types/streaming/stream-router.d.ts +88 -0
- package/dist/types/streaming/streaming-embedder.d.ts +99 -0
- package/dist/types/streaming/websocket-adapter.d.ts +71 -0
- package/dist/types/templates/scaffold.d.ts +55 -0
- package/dist/types/templates/template-registry.d.ts +63 -0
- package/dist/types/utils/embed-query.d.ts +20 -0
- package/dist/types/utils/logger.d.ts +50 -0
- package/dist/types/utils/rate-limiter.d.ts +66 -0
- package/dist/types/utils/retry.d.ts +27 -0
- package/dist/types/utils/run-abort.d.ts +34 -0
- package/dist/types/utils/secure-logging.d.ts +51 -0
- package/dist/types/utils/structured-logger.d.ts +126 -0
- package/package.json +53 -29
- package/src/agentic/agentic-pipeline.js +75 -7
- package/src/agentic/iterative-retriever.js +47 -10
- package/src/agentic/query-planner.js +329 -78
- package/src/citation/citation-tracker.js +6 -4
- package/src/citation/hallucination-detector.js +2 -2
- package/src/citation/source-mapper.js +15 -28
- package/src/cli/commands/ai.js +101 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
- package/src/cli/commands/plugin-hub/local-commands.js +143 -0
- package/src/cli/commands/plugin-hub.js +96 -36
- package/src/cli/diagnostics/config-checker.js +76 -63
- package/src/cli/diagnostics/environment-checker.js +62 -31
- package/src/cli/diagnostics/plugin-checker.js +36 -89
- package/src/cli/doctor-command.js +39 -5
- package/src/cli/enhanced-cli-commands.js +72 -217
- package/src/cli/handlers/ai-handler.js +364 -0
- package/src/cli/handlers/diagnostics-handler.js +78 -31
- package/src/cli/handlers/ingest-handler.js +28 -22
- package/src/cli/handlers/init-handler.js +148 -25
- package/src/cli/handlers/query-handler.js +41 -26
- package/src/cli/handlers/run-handler.js +16 -167
- package/src/cli/handlers/serve-handler.js +60 -0
- package/src/cli/services/bench-stats.js +54 -0
- package/src/cli/services/benchmark.js +161 -0
- package/src/cli/services/feedback.js +155 -0
- package/src/cli/services/rag-service.js +322 -0
- package/src/cli/shared/cli-utils.js +24 -7
- package/src/cli/shared/config-context.js +21 -0
- package/src/cli/wizard/config-builder.js +81 -0
- package/src/cli/wizard/init-wizard.js +617 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/connectors/anthropic-connector.js +93 -55
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +88 -32
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +43 -5
- package/src/connectors/memory-retriever.js +108 -15
- package/src/connectors/ollama-connector.js +83 -49
- package/src/connectors/openai-connector.js +108 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +209 -60
- package/src/core/plugin-registry.js +87 -11
- package/src/cost/cost-calculator.js +205 -32
- package/src/dev/dev-server.js +166 -11
- package/src/dev/hot-reload.js +40 -1
- package/src/ecosystem/plugin-checks.js +181 -0
- package/src/ecosystem/plugin-hub.js +50 -125
- package/src/ecosystem/plugin-installer.js +298 -0
- package/src/ecosystem/plugin-probe.js +127 -0
- package/src/ecosystem/plugin-scanner.js +185 -0
- package/src/ecosystem/tar-extract.js +114 -0
- package/src/enterprise/audit-logging.js +158 -29
- package/src/enterprise/sso-integration.js +715 -228
- package/src/evaluate/context-metrics.js +28 -16
- package/src/evaluate/evaluator.js +3 -3
- package/src/evaluate/faithfulness.js +18 -14
- package/src/evaluate/groundedness.js +43 -12
- package/src/evaluate/pipeline-evaluator.js +35 -8
- package/src/evaluate/scoring.js +251 -3
- package/src/experimental.js +71 -0
- package/src/guardrails/guardrails-pipeline.js +69 -7
- package/src/guardrails/injection-detector.js +283 -0
- package/src/guardrails/post-generation-guard.js +19 -0
- package/src/guardrails/pre-retrieval-guard.js +36 -36
- package/src/guardrails/retrieval-guard.js +58 -8
- package/src/index.js +36 -1
- package/src/loader/document-reader.js +91 -0
- package/src/reranker/cascade-reranker.js +3 -0
- package/src/reranker/embedding-reranker.js +136 -21
- package/src/reranker/llm-reranker.js +79 -12
- package/src/reranker/scoring-reranker.js +3 -0
- package/src/retrieval/bm25-search.js +12 -6
- package/src/retrieval/file-vector-store.js +212 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +134 -30
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/utils/deprecate.js +69 -0
- package/src/utils/embed-query.js +33 -0
- package/src/utils/input-sanitizer.js +19 -4
- package/src/utils/logger.js +34 -4
- package/src/utils/run-abort.js +102 -0
- package/src/ai/federated-learning.js +0 -211
- package/src/ai/federation.js +0 -71
- package/src/ai/index.js +0 -22
- package/src/ai/model-trainer.js +0 -217
- package/src/ai/model-training.js +0 -202
- package/src/ai/multimodal/multi-modal-processor.js +0 -345
- package/src/ai/orchestrator.js +0 -65
- package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
- package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
- package/src/cli/commands/ai-ml/federated-commands.js +0 -207
- package/src/cli/commands/ai-ml/index.js +0 -17
- package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
- package/src/cli/commands/ai-ml/shared.js +0 -51
- package/src/cli/commands/ai-ml/training-commands.js +0 -228
- package/src/cli/commands/ai-ml.js +0 -386
- package/src/cli/commands/docs.js +0 -228
- package/src/cli/commands/dx.js +0 -538
- package/src/cli/commands/wizard.js +0 -627
- package/src/cli/input-sanitization-middleware.js +0 -356
- package/src/cli/interactive-wizard.js +0 -373
- package/src/cli/templates/development.template.json +0 -64
- package/src/cli/templates/minimal.template.json +0 -33
- package/src/cli/templates/production.template.json +0 -104
- package/src/cli/templates/testing.template.json +0 -45
- package/src/cli/wizard/configuration.js +0 -148
- package/src/cli/wizard/observability.js +0 -79
- package/src/cli/wizard/performance.js +0 -76
- package/src/cli/wizard/plugin-selection.js +0 -297
- package/src/cli/wizard/preview-save.js +0 -172
- package/src/cli/wizard/project-setup.js +0 -66
- package/src/core/pipeline-factory.js +0 -59
- package/src/dag/enhanced-dag-engine.js +0 -894
- package/src/dx/index.js +0 -40
- package/src/dx/integration-templates.js +0 -662
- package/src/dx/performance-profiler.js +0 -968
- package/src/dx/realtime-debugger.js +0 -755
- package/src/dx/visual-pipeline-builder.js +0 -578
- package/src/ingest.js +0 -59
- package/src/pipeline/pipeline.js +0 -18
- package/src/query.js +0 -117
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const { BaseConnector } = require('./base-connector');
|
|
5
|
+
const {
|
|
6
|
+
missingApiKeyError,
|
|
7
|
+
resolveApiKey,
|
|
8
|
+
throwIfAborted,
|
|
9
|
+
} = require('./connector-utils');
|
|
10
|
+
|
|
11
|
+
const API_KEY_ENV = 'COHERE_API_KEY';
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Default configuration for the Cohere connector.
|
|
15
|
+
* `apiKey` is a getter so the environment is read when a connector is
|
|
16
|
+
* constructed, not when this module is first required.
|
|
8
17
|
* @type {Object}
|
|
9
18
|
*/
|
|
10
19
|
const DEFAULT_CONFIG = {
|
|
11
|
-
apiKey
|
|
20
|
+
get apiKey() {
|
|
21
|
+
return process.env[API_KEY_ENV] || '';
|
|
22
|
+
},
|
|
12
23
|
baseURL: 'https://api.cohere.ai/v1',
|
|
13
24
|
embeddingModel: 'embed-english-v3.0',
|
|
14
25
|
rerankModel: 'rerank-english-v3.0',
|
|
15
26
|
inputType: 'search_document',
|
|
16
27
|
queryInputType: 'search_query',
|
|
28
|
+
maxRetries: 3,
|
|
29
|
+
retryDelay: 1000,
|
|
17
30
|
};
|
|
18
31
|
|
|
19
32
|
/**
|
|
@@ -35,7 +48,11 @@ class CohereConnector extends BaseConnector {
|
|
|
35
48
|
*/
|
|
36
49
|
constructor(options = {}) {
|
|
37
50
|
super({ name: 'cohere', ...options });
|
|
38
|
-
this.config = {
|
|
51
|
+
this.config = {
|
|
52
|
+
...DEFAULT_CONFIG,
|
|
53
|
+
...options,
|
|
54
|
+
apiKey: resolveApiKey(options, API_KEY_ENV),
|
|
55
|
+
};
|
|
39
56
|
this.client = axios.create({
|
|
40
57
|
baseURL: this.config.baseURL,
|
|
41
58
|
headers: {
|
|
@@ -45,37 +62,72 @@ class CohereConnector extends BaseConnector {
|
|
|
45
62
|
});
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @throws {Error} code MISSING_API_KEY when no key is configured
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
_requireApiKey() {
|
|
70
|
+
if (!this.config.apiKey) {
|
|
71
|
+
throw missingApiKeyError('Cohere', API_KEY_ENV);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
/**
|
|
49
76
|
* Connect to Cohere. Verifies the API key is present.
|
|
50
77
|
* @returns {Promise<void>}
|
|
51
78
|
* @throws {Error} If no API key is configured
|
|
52
79
|
*/
|
|
53
80
|
async connect() {
|
|
54
|
-
|
|
55
|
-
throw new Error(
|
|
56
|
-
'Cohere API key required. Set COHERE_API_KEY or pass apiKey option.',
|
|
57
|
-
);
|
|
58
|
-
}
|
|
81
|
+
this._requireApiKey();
|
|
59
82
|
await super.connect();
|
|
60
83
|
}
|
|
61
84
|
|
|
85
|
+
/**
|
|
86
|
+
* POST with the connector's retry policy and cancellation.
|
|
87
|
+
* @param {string} path
|
|
88
|
+
* @param {object} body
|
|
89
|
+
* @param {AbortSignal} [signal]
|
|
90
|
+
* @returns {Promise<object>} Response data
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
async _post(path, body, signal) {
|
|
94
|
+
throwIfAborted(signal);
|
|
95
|
+
this._requireApiKey();
|
|
96
|
+
return this.withRetry(
|
|
97
|
+
async () => {
|
|
98
|
+
const response = await this.client.post(
|
|
99
|
+
path,
|
|
100
|
+
body,
|
|
101
|
+
signal ? { signal } : undefined,
|
|
102
|
+
);
|
|
103
|
+
return response.data;
|
|
104
|
+
},
|
|
105
|
+
this.config.maxRetries,
|
|
106
|
+
this.config.retryDelay,
|
|
107
|
+
{ signal },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
62
111
|
/**
|
|
63
112
|
* Generate an embedding vector for document text.
|
|
64
113
|
* Uses the document input type by default.
|
|
65
114
|
*
|
|
66
115
|
* @param {string} text - Input text to embed
|
|
116
|
+
* @param {Object} [options={}]
|
|
117
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
67
118
|
* @returns {Promise<number[]>} Embedding vector
|
|
68
119
|
*/
|
|
69
|
-
async embed(text) {
|
|
70
|
-
|
|
71
|
-
|
|
120
|
+
async embed(text, options = {}) {
|
|
121
|
+
const data = await this._post(
|
|
122
|
+
'/embed',
|
|
123
|
+
{
|
|
72
124
|
texts: [text],
|
|
73
125
|
model: this.config.embeddingModel,
|
|
74
126
|
input_type: this.config.inputType,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
127
|
+
},
|
|
128
|
+
options.signal,
|
|
129
|
+
);
|
|
130
|
+
return data.embeddings[0];
|
|
79
131
|
}
|
|
80
132
|
|
|
81
133
|
/**
|
|
@@ -83,18 +135,21 @@ class CohereConnector extends BaseConnector {
|
|
|
83
135
|
* Uses the query input type for better retrieval accuracy.
|
|
84
136
|
*
|
|
85
137
|
* @param {string} query - Query text to embed
|
|
138
|
+
* @param {Object} [options={}]
|
|
139
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
86
140
|
* @returns {Promise<number[]>} Embedding vector
|
|
87
141
|
*/
|
|
88
|
-
async embedQuery(query) {
|
|
89
|
-
|
|
90
|
-
|
|
142
|
+
async embedQuery(query, options = {}) {
|
|
143
|
+
const data = await this._post(
|
|
144
|
+
'/embed',
|
|
145
|
+
{
|
|
91
146
|
texts: [query],
|
|
92
147
|
model: this.config.embeddingModel,
|
|
93
148
|
input_type: this.config.queryInputType,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
149
|
+
},
|
|
150
|
+
options.signal,
|
|
151
|
+
);
|
|
152
|
+
return data.embeddings[0];
|
|
98
153
|
}
|
|
99
154
|
|
|
100
155
|
/**
|
|
@@ -104,26 +159,27 @@ class CohereConnector extends BaseConnector {
|
|
|
104
159
|
* @param {Array<Object|string>} documents - Documents to rerank
|
|
105
160
|
* @param {Object} [options={}]
|
|
106
161
|
* @param {number} [options.topK] - Number of top results to return (defaults to all)
|
|
162
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
107
163
|
* @returns {Promise<Array<Object>>} Reranked documents with relevance scores
|
|
108
164
|
*/
|
|
109
165
|
async rerank(query, documents, options = {}) {
|
|
110
|
-
|
|
111
|
-
const docs = documents.map(
|
|
112
|
-
(doc) => doc.content || doc.text || String(doc),
|
|
113
|
-
);
|
|
166
|
+
const docs = documents.map((doc) => doc.content || doc.text || String(doc));
|
|
114
167
|
|
|
115
|
-
|
|
168
|
+
const data = await this._post(
|
|
169
|
+
'/rerank',
|
|
170
|
+
{
|
|
116
171
|
model: this.config.rerankModel,
|
|
117
172
|
query,
|
|
118
173
|
documents: docs,
|
|
119
174
|
top_n: options.topK || documents.length,
|
|
120
|
-
}
|
|
175
|
+
},
|
|
176
|
+
options.signal,
|
|
177
|
+
);
|
|
121
178
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
179
|
+
return data.results.map((result) => ({
|
|
180
|
+
...documents[result.index],
|
|
181
|
+
score: result.relevance_score,
|
|
182
|
+
}));
|
|
127
183
|
}
|
|
128
184
|
|
|
129
185
|
/**
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared helpers for the built-in connectors: AbortSignal handling, retry
|
|
5
|
+
* classification and backoff, and API key resolution.
|
|
6
|
+
*
|
|
7
|
+
* Everything here must run on Node 18 (no AbortSignal.any, no crypto.hash).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** HTTP status codes that are worth retrying. */
|
|
11
|
+
const RETRYABLE_STATUS = new Set([408, 409, 425, 429]);
|
|
12
|
+
|
|
13
|
+
/** Error codes produced by Node, axios and undici for transient network failures. */
|
|
14
|
+
const RETRYABLE_CODES = new Set([
|
|
15
|
+
'ECONNRESET',
|
|
16
|
+
'ECONNREFUSED',
|
|
17
|
+
'ETIMEDOUT',
|
|
18
|
+
'ECONNABORTED',
|
|
19
|
+
'EPIPE',
|
|
20
|
+
'EAI_AGAIN',
|
|
21
|
+
'ENETUNREACH',
|
|
22
|
+
'ENETDOWN',
|
|
23
|
+
'EHOSTUNREACH',
|
|
24
|
+
'ERR_NETWORK',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/** Error names (openai SDK) that signal a connection-level failure. */
|
|
28
|
+
const RETRYABLE_NAMES = new Set([
|
|
29
|
+
'APIConnectionError',
|
|
30
|
+
'APIConnectionTimeoutError',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create an AbortError for the given signal.
|
|
35
|
+
* Reuses signal.reason when it already is an AbortError.
|
|
36
|
+
*
|
|
37
|
+
* @param {AbortSignal} [signal]
|
|
38
|
+
* @returns {Error} Error with name 'AbortError' and code 'ABORT_ERR'
|
|
39
|
+
*/
|
|
40
|
+
function createAbortError(signal) {
|
|
41
|
+
const reason = signal ? signal.reason : undefined;
|
|
42
|
+
if (reason instanceof Error && reason.name === 'AbortError') {
|
|
43
|
+
return reason;
|
|
44
|
+
}
|
|
45
|
+
const err = new Error('The operation was aborted');
|
|
46
|
+
err.name = 'AbortError';
|
|
47
|
+
err.code = 'ABORT_ERR';
|
|
48
|
+
if (reason !== undefined) err.cause = reason;
|
|
49
|
+
return err;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {*} err
|
|
54
|
+
* @returns {boolean} True when err represents a cancellation.
|
|
55
|
+
*/
|
|
56
|
+
function isAbortError(err) {
|
|
57
|
+
if (!err) return false;
|
|
58
|
+
return (
|
|
59
|
+
err.name === 'AbortError' ||
|
|
60
|
+
err.code === 'ABORT_ERR' ||
|
|
61
|
+
err.name === 'CanceledError' ||
|
|
62
|
+
err.code === 'ERR_CANCELED' ||
|
|
63
|
+
err.name === 'APIUserAbortError'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Throw an AbortError when the signal is already aborted.
|
|
69
|
+
* @param {AbortSignal} [signal]
|
|
70
|
+
*/
|
|
71
|
+
function throwIfAborted(signal) {
|
|
72
|
+
if (signal && signal.aborted) {
|
|
73
|
+
throw createAbortError(signal);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Race a promise against an AbortSignal so callers reject promptly even when
|
|
79
|
+
* the underlying SDK or HTTP client ignores the signal. The abort listener is
|
|
80
|
+
* removed once the promise settles.
|
|
81
|
+
*
|
|
82
|
+
* @template T
|
|
83
|
+
* @param {Promise<T>|(() => Promise<T>)} work - Promise or function returning one
|
|
84
|
+
* @param {AbortSignal} [signal]
|
|
85
|
+
* @returns {Promise<T>}
|
|
86
|
+
*/
|
|
87
|
+
function raceAbort(work, signal) {
|
|
88
|
+
if (!signal) {
|
|
89
|
+
return typeof work === 'function' ? Promise.resolve().then(work) : work;
|
|
90
|
+
}
|
|
91
|
+
if (signal.aborted) {
|
|
92
|
+
return Promise.reject(createAbortError(signal));
|
|
93
|
+
}
|
|
94
|
+
const promise =
|
|
95
|
+
typeof work === 'function' ? Promise.resolve().then(work) : work;
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const onAbort = () => reject(createAbortError(signal));
|
|
98
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
99
|
+
promise.then(
|
|
100
|
+
(value) => {
|
|
101
|
+
signal.removeEventListener('abort', onAbort);
|
|
102
|
+
resolve(value);
|
|
103
|
+
},
|
|
104
|
+
(err) => {
|
|
105
|
+
signal.removeEventListener('abort', onAbort);
|
|
106
|
+
// Normalise SDK/axios cancellation errors to AbortError.
|
|
107
|
+
reject(
|
|
108
|
+
signal.aborted || isAbortError(err) ? createAbortError(signal) : err,
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sleep that rejects with AbortError as soon as the signal aborts.
|
|
117
|
+
* @param {number} ms
|
|
118
|
+
* @param {AbortSignal} [signal]
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
function abortableSleep(ms, signal) {
|
|
122
|
+
if (signal && signal.aborted) {
|
|
123
|
+
return Promise.reject(createAbortError(signal));
|
|
124
|
+
}
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
let onAbort = null;
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
if (signal && onAbort) signal.removeEventListener('abort', onAbort);
|
|
129
|
+
resolve();
|
|
130
|
+
}, ms);
|
|
131
|
+
if (signal) {
|
|
132
|
+
onAbort = () => {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
reject(createAbortError(signal));
|
|
135
|
+
};
|
|
136
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Extract the HTTP status from an openai SDK error or an axios error.
|
|
143
|
+
* @param {*} err
|
|
144
|
+
* @returns {number|undefined}
|
|
145
|
+
*/
|
|
146
|
+
function getErrorStatus(err) {
|
|
147
|
+
if (!err) return undefined;
|
|
148
|
+
const status =
|
|
149
|
+
(err.response && err.response.status) ?? err.status ?? err.statusCode;
|
|
150
|
+
return typeof status === 'number' ? status : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Read a header from a plain object, an AxiosHeaders instance or a fetch Headers.
|
|
155
|
+
* @param {*} headers
|
|
156
|
+
* @param {string} name - Lower-case header name
|
|
157
|
+
* @returns {string|undefined}
|
|
158
|
+
*/
|
|
159
|
+
function getHeader(headers, name) {
|
|
160
|
+
if (!headers) return undefined;
|
|
161
|
+
if (typeof headers.get === 'function') {
|
|
162
|
+
const value = headers.get(name);
|
|
163
|
+
if (value != null) return String(value);
|
|
164
|
+
}
|
|
165
|
+
for (const key of Object.keys(headers)) {
|
|
166
|
+
if (key.toLowerCase() === name && headers[key] != null) {
|
|
167
|
+
return String(headers[key]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse Retry-After / retry-after-ms from an error's response headers.
|
|
175
|
+
*
|
|
176
|
+
* @param {*} err
|
|
177
|
+
* @param {number} [now=Date.now()]
|
|
178
|
+
* @returns {number|undefined} Delay in milliseconds, or undefined when absent
|
|
179
|
+
*/
|
|
180
|
+
function getRetryAfterMs(err, now = Date.now()) {
|
|
181
|
+
if (!err) return undefined;
|
|
182
|
+
const headers = (err.response && err.response.headers) || err.headers;
|
|
183
|
+
const ms = getHeader(headers, 'retry-after-ms');
|
|
184
|
+
if (ms !== undefined && /^\d+(\.\d+)?$/.test(ms.trim())) {
|
|
185
|
+
return Math.max(0, Number(ms));
|
|
186
|
+
}
|
|
187
|
+
const value = getHeader(headers, 'retry-after');
|
|
188
|
+
if (value === undefined) return undefined;
|
|
189
|
+
const trimmed = value.trim();
|
|
190
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
191
|
+
return Math.max(0, Number(trimmed) * 1000);
|
|
192
|
+
}
|
|
193
|
+
const date = Date.parse(trimmed);
|
|
194
|
+
if (!Number.isNaN(date)) return Math.max(0, date - now);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Default retry classification: network failures and HTTP
|
|
200
|
+
* 408/409/425/429/5xx are retried; aborts, other 4xx (400, 401, 403, 404,
|
|
201
|
+
* 422, ...) and errors without a status or network code are not.
|
|
202
|
+
*
|
|
203
|
+
* @param {*} err
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
function isRetryableError(err) {
|
|
207
|
+
if (!err || isAbortError(err)) return false;
|
|
208
|
+
if (err.code === 'MISSING_API_KEY') return false;
|
|
209
|
+
const status = getErrorStatus(err);
|
|
210
|
+
if (status !== undefined) {
|
|
211
|
+
return RETRYABLE_STATUS.has(status) || (status >= 500 && status <= 599);
|
|
212
|
+
}
|
|
213
|
+
const code = err.code || (err.cause && err.cause.code);
|
|
214
|
+
if (typeof code === 'string') {
|
|
215
|
+
if (RETRYABLE_CODES.has(code) || code.startsWith('UND_ERR_')) return true;
|
|
216
|
+
}
|
|
217
|
+
if (RETRYABLE_NAMES.has(err.name)) return true;
|
|
218
|
+
// fetch() network failures surface as TypeError('fetch failed').
|
|
219
|
+
if (err.name === 'TypeError' && /fetch failed/i.test(err.message || '')) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Compute the delay before the next retry attempt.
|
|
227
|
+
* Honours Retry-After (capped at maxDelay); otherwise uses exponential backoff
|
|
228
|
+
* with full jitter: random() * min(maxDelay, baseDelay * 2^attempt).
|
|
229
|
+
*
|
|
230
|
+
* @param {*} err - The error that triggered the retry
|
|
231
|
+
* @param {number} attempt - Zero-based retry attempt
|
|
232
|
+
* @param {object} [opts]
|
|
233
|
+
* @param {number} [opts.baseDelay=1000]
|
|
234
|
+
* @param {number} [opts.maxDelay=30000]
|
|
235
|
+
* @param {() => number} [opts.random=Math.random]
|
|
236
|
+
* @returns {number} Delay in milliseconds
|
|
237
|
+
*/
|
|
238
|
+
function computeRetryDelay(err, attempt, opts = {}) {
|
|
239
|
+
const baseDelay = opts.baseDelay ?? 1000;
|
|
240
|
+
const maxDelay = opts.maxDelay ?? 30000;
|
|
241
|
+
const random = opts.random || Math.random;
|
|
242
|
+
const retryAfter = getRetryAfterMs(err);
|
|
243
|
+
if (retryAfter !== undefined) {
|
|
244
|
+
return Math.min(retryAfter, maxDelay);
|
|
245
|
+
}
|
|
246
|
+
const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
|
|
247
|
+
return Math.floor(random() * exp);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolve an API key at construction time. An explicit option (even '')
|
|
252
|
+
* wins over the environment variable.
|
|
253
|
+
*
|
|
254
|
+
* @param {object} options - Connector options
|
|
255
|
+
* @param {string} envVar - Environment variable name
|
|
256
|
+
* @returns {string}
|
|
257
|
+
*/
|
|
258
|
+
function resolveApiKey(options, envVar) {
|
|
259
|
+
if (options && options.apiKey !== undefined) return options.apiKey || '';
|
|
260
|
+
return process.env[envVar] || '';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Error thrown when a provider call is attempted without an API key.
|
|
265
|
+
* @param {string} provider - Display name, e.g. 'OpenAI'
|
|
266
|
+
* @param {string} envVar - Environment variable name
|
|
267
|
+
* @returns {Error}
|
|
268
|
+
*/
|
|
269
|
+
function missingApiKeyError(provider, envVar) {
|
|
270
|
+
const err = new Error(
|
|
271
|
+
`${provider} API key required. Set ${envVar} or pass the apiKey option.`,
|
|
272
|
+
);
|
|
273
|
+
err.code = 'MISSING_API_KEY';
|
|
274
|
+
return err;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Build request options carrying the signal only when one was supplied, so
|
|
279
|
+
* calls without a signal look exactly as before.
|
|
280
|
+
* @param {AbortSignal} [signal]
|
|
281
|
+
* @returns {object}
|
|
282
|
+
*/
|
|
283
|
+
function signalOptions(signal) {
|
|
284
|
+
return signal ? { signal } : {};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
createAbortError,
|
|
289
|
+
isAbortError,
|
|
290
|
+
throwIfAborted,
|
|
291
|
+
raceAbort,
|
|
292
|
+
abortableSleep,
|
|
293
|
+
getErrorStatus,
|
|
294
|
+
getRetryAfterMs,
|
|
295
|
+
isRetryableError,
|
|
296
|
+
computeRetryDelay,
|
|
297
|
+
resolveApiKey,
|
|
298
|
+
missingApiKeyError,
|
|
299
|
+
signalOptions,
|
|
300
|
+
RETRYABLE_STATUS,
|
|
301
|
+
RETRYABLE_CODES,
|
|
302
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { BaseConnector } = require('./base-connector');
|
|
4
4
|
const { tokenize } = require('../evaluate/scoring');
|
|
5
|
+
const { throwIfAborted } = require('./connector-utils');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Default configuration for the local TF-IDF embedder.
|
|
@@ -61,7 +62,9 @@ class LocalEmbedder extends BaseConnector {
|
|
|
61
62
|
const idfEntries = [];
|
|
62
63
|
for (const [token, freq] of docFreq) {
|
|
63
64
|
if (freq < this.config.minDocFrequency) continue;
|
|
64
|
-
|
|
65
|
+
// Smoothed IDF stays positive; log(N / (df + 1)) is <= 0 for every
|
|
66
|
+
// token in small corpora (N <= 2), which produced all-zero vectors.
|
|
67
|
+
const idfValue = Math.log(1 + this.docCount / freq);
|
|
65
68
|
idfEntries.push([token, idfValue]);
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -86,11 +89,16 @@ class LocalEmbedder extends BaseConnector {
|
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
91
|
* Compute a TF-IDF vector for the given text.
|
|
89
|
-
* The vector length equals the vocabulary size.
|
|
92
|
+
* The vector length equals the vocabulary size. Synchronous; when
|
|
93
|
+
* options.signal is already aborted it throws an AbortError (which an
|
|
94
|
+
* awaiting caller sees as a rejection).
|
|
90
95
|
* @param {string} text - Text to embed
|
|
96
|
+
* @param {Object} [options={}]
|
|
97
|
+
* @param {AbortSignal} [options.signal] - Cancellation signal
|
|
91
98
|
* @returns {number[]} L2-normalized TF-IDF vector
|
|
92
99
|
*/
|
|
93
|
-
embed(text) {
|
|
100
|
+
embed(text, options = {}) {
|
|
101
|
+
throwIfAborted(options && options.signal);
|
|
94
102
|
const vocabSize = this.vocabulary.size;
|
|
95
103
|
|
|
96
104
|
if (vocabSize === 0) {
|
|
@@ -137,10 +145,40 @@ class LocalEmbedder extends BaseConnector {
|
|
|
137
145
|
* Compute a TF-IDF vector for a query string.
|
|
138
146
|
* Alias for {@link embed}.
|
|
139
147
|
* @param {string} query - Query text to embed
|
|
148
|
+
* @param {Object} [options={}] - Same as embed()
|
|
140
149
|
* @returns {number[]} L2-normalized TF-IDF vector
|
|
141
150
|
*/
|
|
142
|
-
embedQuery(query) {
|
|
143
|
-
return this.embed(query);
|
|
151
|
+
embedQuery(query, options = {}) {
|
|
152
|
+
return this.embed(query, options);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Serializable vocabulary and IDF weights, so a trained embedder can be
|
|
157
|
+
* restored in another process (e.g. by FileVectorStore-backed CLI queries).
|
|
158
|
+
*
|
|
159
|
+
* @returns {{docCount: number, vocabulary: Array<[string, number]>, idf: Array<[string, number]>}}
|
|
160
|
+
*/
|
|
161
|
+
getState() {
|
|
162
|
+
return {
|
|
163
|
+
docCount: this.docCount,
|
|
164
|
+
vocabulary: [...this.vocabulary],
|
|
165
|
+
idf: [...this.idf],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Restore state produced by getState().
|
|
171
|
+
* @param {{docCount: number, vocabulary: Array<[string, number]>, idf: Array<[string, number]>}} state
|
|
172
|
+
*/
|
|
173
|
+
setState(state) {
|
|
174
|
+
this.docCount = state.docCount || 0;
|
|
175
|
+
this.vocabulary = new Map(state.vocabulary || []);
|
|
176
|
+
this.idf = new Map(state.idf || []);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @returns {number} Vector length produced by embed() once trained */
|
|
180
|
+
get dimensions() {
|
|
181
|
+
return this.vocabulary.size;
|
|
144
182
|
}
|
|
145
183
|
|
|
146
184
|
/**
|