@devilsdev/rag-pipeline-utils 2.4.5 → 2.6.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 +431 -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 +6442 -1355
- package/dist/index.d.ts +129 -2
- package/dist/index.mjs +1 -1
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.mjs +5 -0
- package/dist/types/adapters/ai-sdk.d.ts +53 -0
- 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/cache/component-keys.d.ts +7 -0
- package/dist/types/cache/memory-cache.d.ts +54 -0
- package/dist/types/cache/pipeline-cache.d.ts +66 -0
- package/dist/types/cache/redis-cache.d.ts +46 -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 +97 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +94 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +95 -0
- package/dist/types/connectors/memory-retriever.d.ts +99 -0
- package/dist/types/connectors/ollama-connector.d.ts +115 -0
- package/dist/types/connectors/openai-connector.d.ts +129 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/core/create-pipeline.d.ts +8 -0
- package/dist/types/core/plugin-registry.d.ts +136 -0
- package/dist/types/core/stage-contracts.d.ts +36 -0
- package/dist/types/core/validate-plugin.d.ts +45 -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/evaluate/semantic-similarity.d.ts +14 -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/grounding/check-answer.d.ts +101 -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 +99 -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/testing.d.ts +168 -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 +72 -33
- package/src/adapters/ai-sdk.js +215 -0
- 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/cache/component-keys.js +38 -0
- package/src/cache/memory-cache.js +87 -0
- package/src/cache/pipeline-cache.js +260 -0
- package/src/cache/redis-cache.js +83 -0
- 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 +106 -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 +114 -49
- 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 +104 -233
- 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 +203 -25
- 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/instrument.js +83 -0
- package/src/cli/services/rag-service.js +361 -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 +602 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/enhanced-ragrc-schema.js +0 -2
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/config/validate-schema.js +4 -1
- package/src/connectors/anthropic-connector.js +115 -57
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +108 -34
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +63 -5
- package/src/connectors/memory-retriever.js +125 -14
- package/src/connectors/ollama-connector.js +112 -51
- package/src/connectors/openai-connector.js +153 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +307 -61
- package/src/core/performance/parallel-processor.js +62 -1
- package/src/core/plugin-registry.js +125 -26
- package/src/core/stage-contracts.js +36 -0
- package/src/core/validate-plugin.js +85 -0
- package/src/cost/cost-calculator.js +205 -32
- package/src/debug/execution-tracer.js +14 -4
- package/src/dev/dev-server.js +173 -11
- package/src/dev/hot-reload.js +44 -4
- 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 +716 -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/evaluate/semantic-similarity.js +79 -0
- package/src/experimental.js +71 -0
- package/src/grounding/check-answer.js +173 -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 +58 -1
- package/src/loader/csv-loader.js +3 -2
- package/src/loader/document-reader.js +91 -0
- package/src/loader/html-loader.js +3 -1
- package/src/loader/markdown-loader.js +3 -2
- 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 +241 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +139 -34
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/testing.js +254 -0
- 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
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// axios loads on first use, so importing the package does not pay for it
|
|
4
|
+
// (about 125 ms) unless this connector is constructed.
|
|
5
|
+
const loadAxios = () => require('axios'); // eslint-disable-line global-require
|
|
6
|
+
const { componentCacheKey } = require('../cache/component-keys');
|
|
4
7
|
const { BaseConnector } = require('./base-connector');
|
|
8
|
+
const {
|
|
9
|
+
missingApiKeyError,
|
|
10
|
+
resolveApiKey,
|
|
11
|
+
throwIfAborted,
|
|
12
|
+
} = require('./connector-utils');
|
|
13
|
+
|
|
14
|
+
const API_KEY_ENV = 'COHERE_API_KEY';
|
|
5
15
|
|
|
6
16
|
/**
|
|
7
17
|
* Default configuration for the Cohere connector.
|
|
18
|
+
* `apiKey` is a getter so the environment is read when a connector is
|
|
19
|
+
* constructed, not when this module is first required.
|
|
8
20
|
* @type {Object}
|
|
9
21
|
*/
|
|
10
22
|
const DEFAULT_CONFIG = {
|
|
11
|
-
apiKey
|
|
23
|
+
get apiKey() {
|
|
24
|
+
return process.env[API_KEY_ENV] || '';
|
|
25
|
+
},
|
|
12
26
|
baseURL: 'https://api.cohere.ai/v1',
|
|
13
27
|
embeddingModel: 'embed-english-v3.0',
|
|
14
28
|
rerankModel: 'rerank-english-v3.0',
|
|
15
29
|
inputType: 'search_document',
|
|
16
30
|
queryInputType: 'search_query',
|
|
31
|
+
maxRetries: 3,
|
|
32
|
+
retryDelay: 1000,
|
|
17
33
|
};
|
|
18
34
|
|
|
19
35
|
/**
|
|
@@ -24,6 +40,21 @@ const DEFAULT_CONFIG = {
|
|
|
24
40
|
* @extends BaseConnector
|
|
25
41
|
*/
|
|
26
42
|
class CohereConnector extends BaseConnector {
|
|
43
|
+
/**
|
|
44
|
+
* Cache identity for createRagPipeline({ cache }).
|
|
45
|
+
* @param {'embedder'} role
|
|
46
|
+
* @returns {string|null}
|
|
47
|
+
*/
|
|
48
|
+
getCacheKey(role) {
|
|
49
|
+
if (role !== 'embedder') return null;
|
|
50
|
+
const c = this.config;
|
|
51
|
+
return componentCacheKey('cohere-embed', {
|
|
52
|
+
baseURL: c.baseURL,
|
|
53
|
+
model: c.embeddingModel,
|
|
54
|
+
queryInputType: c.queryInputType,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
27
58
|
/**
|
|
28
59
|
* @param {Object} [options]
|
|
29
60
|
* @param {string} [options.apiKey] - Cohere API key
|
|
@@ -35,8 +66,12 @@ class CohereConnector extends BaseConnector {
|
|
|
35
66
|
*/
|
|
36
67
|
constructor(options = {}) {
|
|
37
68
|
super({ name: 'cohere', ...options });
|
|
38
|
-
this.config = {
|
|
39
|
-
|
|
69
|
+
this.config = {
|
|
70
|
+
...DEFAULT_CONFIG,
|
|
71
|
+
...options,
|
|
72
|
+
apiKey: resolveApiKey(options, API_KEY_ENV),
|
|
73
|
+
};
|
|
74
|
+
this.client = loadAxios().create({
|
|
40
75
|
baseURL: this.config.baseURL,
|
|
41
76
|
headers: {
|
|
42
77
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
@@ -45,37 +80,72 @@ class CohereConnector extends BaseConnector {
|
|
|
45
80
|
});
|
|
46
81
|
}
|
|
47
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @throws {Error} code MISSING_API_KEY when no key is configured
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
_requireApiKey() {
|
|
88
|
+
if (!this.config.apiKey) {
|
|
89
|
+
throw missingApiKeyError('Cohere', API_KEY_ENV);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
48
93
|
/**
|
|
49
94
|
* Connect to Cohere. Verifies the API key is present.
|
|
50
95
|
* @returns {Promise<void>}
|
|
51
96
|
* @throws {Error} If no API key is configured
|
|
52
97
|
*/
|
|
53
98
|
async connect() {
|
|
54
|
-
|
|
55
|
-
throw new Error(
|
|
56
|
-
'Cohere API key required. Set COHERE_API_KEY or pass apiKey option.',
|
|
57
|
-
);
|
|
58
|
-
}
|
|
99
|
+
this._requireApiKey();
|
|
59
100
|
await super.connect();
|
|
60
101
|
}
|
|
61
102
|
|
|
103
|
+
/**
|
|
104
|
+
* POST with the connector's retry policy and cancellation.
|
|
105
|
+
* @param {string} path
|
|
106
|
+
* @param {object} body
|
|
107
|
+
* @param {AbortSignal} [signal]
|
|
108
|
+
* @returns {Promise<object>} Response data
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
async _post(path, body, signal) {
|
|
112
|
+
throwIfAborted(signal);
|
|
113
|
+
this._requireApiKey();
|
|
114
|
+
return this.withRetry(
|
|
115
|
+
async () => {
|
|
116
|
+
const response = await this.client.post(
|
|
117
|
+
path,
|
|
118
|
+
body,
|
|
119
|
+
signal ? { signal } : undefined,
|
|
120
|
+
);
|
|
121
|
+
return response.data;
|
|
122
|
+
},
|
|
123
|
+
this.config.maxRetries,
|
|
124
|
+
this.config.retryDelay,
|
|
125
|
+
{ signal },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
62
129
|
/**
|
|
63
130
|
* Generate an embedding vector for document text.
|
|
64
131
|
* Uses the document input type by default.
|
|
65
132
|
*
|
|
66
133
|
* @param {string} text - Input text to embed
|
|
134
|
+
* @param {Object} [options={}]
|
|
135
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
67
136
|
* @returns {Promise<number[]>} Embedding vector
|
|
68
137
|
*/
|
|
69
|
-
async embed(text) {
|
|
70
|
-
|
|
71
|
-
|
|
138
|
+
async embed(text, options = {}) {
|
|
139
|
+
const data = await this._post(
|
|
140
|
+
'/embed',
|
|
141
|
+
{
|
|
72
142
|
texts: [text],
|
|
73
143
|
model: this.config.embeddingModel,
|
|
74
144
|
input_type: this.config.inputType,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
145
|
+
},
|
|
146
|
+
options.signal,
|
|
147
|
+
);
|
|
148
|
+
return data.embeddings[0];
|
|
79
149
|
}
|
|
80
150
|
|
|
81
151
|
/**
|
|
@@ -83,18 +153,21 @@ class CohereConnector extends BaseConnector {
|
|
|
83
153
|
* Uses the query input type for better retrieval accuracy.
|
|
84
154
|
*
|
|
85
155
|
* @param {string} query - Query text to embed
|
|
156
|
+
* @param {Object} [options={}]
|
|
157
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
86
158
|
* @returns {Promise<number[]>} Embedding vector
|
|
87
159
|
*/
|
|
88
|
-
async embedQuery(query) {
|
|
89
|
-
|
|
90
|
-
|
|
160
|
+
async embedQuery(query, options = {}) {
|
|
161
|
+
const data = await this._post(
|
|
162
|
+
'/embed',
|
|
163
|
+
{
|
|
91
164
|
texts: [query],
|
|
92
165
|
model: this.config.embeddingModel,
|
|
93
166
|
input_type: this.config.queryInputType,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
167
|
+
},
|
|
168
|
+
options.signal,
|
|
169
|
+
);
|
|
170
|
+
return data.embeddings[0];
|
|
98
171
|
}
|
|
99
172
|
|
|
100
173
|
/**
|
|
@@ -104,26 +177,27 @@ class CohereConnector extends BaseConnector {
|
|
|
104
177
|
* @param {Array<Object|string>} documents - Documents to rerank
|
|
105
178
|
* @param {Object} [options={}]
|
|
106
179
|
* @param {number} [options.topK] - Number of top results to return (defaults to all)
|
|
180
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
107
181
|
* @returns {Promise<Array<Object>>} Reranked documents with relevance scores
|
|
108
182
|
*/
|
|
109
183
|
async rerank(query, documents, options = {}) {
|
|
110
|
-
|
|
111
|
-
const docs = documents.map(
|
|
112
|
-
(doc) => doc.content || doc.text || String(doc),
|
|
113
|
-
);
|
|
184
|
+
const docs = documents.map((doc) => doc.content || doc.text || String(doc));
|
|
114
185
|
|
|
115
|
-
|
|
186
|
+
const data = await this._post(
|
|
187
|
+
'/rerank',
|
|
188
|
+
{
|
|
116
189
|
model: this.config.rerankModel,
|
|
117
190
|
query,
|
|
118
191
|
documents: docs,
|
|
119
192
|
top_n: options.topK || documents.length,
|
|
120
|
-
}
|
|
193
|
+
},
|
|
194
|
+
options.signal,
|
|
195
|
+
);
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
197
|
+
return data.results.map((result) => ({
|
|
198
|
+
...documents[result.index],
|
|
199
|
+
score: result.relevance_score,
|
|
200
|
+
}));
|
|
127
201
|
}
|
|
128
202
|
|
|
129
203
|
/**
|
|
@@ -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
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const { digest } = require('../cache/component-keys');
|
|
2
3
|
|
|
3
4
|
const { BaseConnector } = require('./base-connector');
|
|
4
5
|
const { tokenize } = require('../evaluate/scoring');
|
|
6
|
+
const { throwIfAborted } = require('./connector-utils');
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Default configuration for the local TF-IDF embedder.
|
|
@@ -41,6 +43,7 @@ class LocalEmbedder extends BaseConnector {
|
|
|
41
43
|
* @param {Array<string|{ content?: string, text?: string }>} documents - Training documents
|
|
42
44
|
*/
|
|
43
45
|
train(documents) {
|
|
46
|
+
this._fingerprint = null; // the cache key changes with the training
|
|
44
47
|
const docs = documents.map((doc) => {
|
|
45
48
|
if (typeof doc === 'string') return doc;
|
|
46
49
|
return doc.content || doc.text || String(doc);
|
|
@@ -61,7 +64,9 @@ class LocalEmbedder extends BaseConnector {
|
|
|
61
64
|
const idfEntries = [];
|
|
62
65
|
for (const [token, freq] of docFreq) {
|
|
63
66
|
if (freq < this.config.minDocFrequency) continue;
|
|
64
|
-
|
|
67
|
+
// Smoothed IDF stays positive; log(N / (df + 1)) is <= 0 for every
|
|
68
|
+
// token in small corpora (N <= 2), which produced all-zero vectors.
|
|
69
|
+
const idfValue = Math.log(1 + this.docCount / freq);
|
|
65
70
|
idfEntries.push([token, idfValue]);
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -86,11 +91,16 @@ class LocalEmbedder extends BaseConnector {
|
|
|
86
91
|
|
|
87
92
|
/**
|
|
88
93
|
* Compute a TF-IDF vector for the given text.
|
|
89
|
-
* The vector length equals the vocabulary size.
|
|
94
|
+
* The vector length equals the vocabulary size. Synchronous; when
|
|
95
|
+
* options.signal is already aborted it throws an AbortError (which an
|
|
96
|
+
* awaiting caller sees as a rejection).
|
|
90
97
|
* @param {string} text - Text to embed
|
|
98
|
+
* @param {Object} [options={}]
|
|
99
|
+
* @param {AbortSignal} [options.signal] - Cancellation signal
|
|
91
100
|
* @returns {number[]} L2-normalized TF-IDF vector
|
|
92
101
|
*/
|
|
93
|
-
embed(text) {
|
|
102
|
+
embed(text, options = {}) {
|
|
103
|
+
throwIfAborted(options && options.signal);
|
|
94
104
|
const vocabSize = this.vocabulary.size;
|
|
95
105
|
|
|
96
106
|
if (vocabSize === 0) {
|
|
@@ -137,10 +147,58 @@ class LocalEmbedder extends BaseConnector {
|
|
|
137
147
|
* Compute a TF-IDF vector for a query string.
|
|
138
148
|
* Alias for {@link embed}.
|
|
139
149
|
* @param {string} query - Query text to embed
|
|
150
|
+
* @param {Object} [options={}] - Same as embed()
|
|
140
151
|
* @returns {number[]} L2-normalized TF-IDF vector
|
|
141
152
|
*/
|
|
142
|
-
embedQuery(query) {
|
|
143
|
-
return this.embed(query);
|
|
153
|
+
embedQuery(query, options = {}) {
|
|
154
|
+
return this.embed(query, options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Serializable vocabulary and IDF weights, so a trained embedder can be
|
|
159
|
+
* restored in another process (e.g. by FileVectorStore-backed CLI queries).
|
|
160
|
+
*
|
|
161
|
+
* @returns {{docCount: number, vocabulary: Array<[string, number]>, idf: Array<[string, number]>}}
|
|
162
|
+
*/
|
|
163
|
+
getState() {
|
|
164
|
+
return {
|
|
165
|
+
docCount: this.docCount,
|
|
166
|
+
vocabulary: [...this.vocabulary],
|
|
167
|
+
idf: [...this.idf],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Restore state produced by getState().
|
|
173
|
+
* @param {{docCount: number, vocabulary: Array<[string, number]>, idf: Array<[string, number]>}} state
|
|
174
|
+
*/
|
|
175
|
+
setState(state) {
|
|
176
|
+
this._fingerprint = null;
|
|
177
|
+
this.docCount = state.docCount || 0;
|
|
178
|
+
this.vocabulary = new Map(state.vocabulary || []);
|
|
179
|
+
this.idf = new Map(state.idf || []);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Cache identity for createRagPipeline({ cache }): a fingerprint of the
|
|
184
|
+
* trained vocabulary and IDF weights, so retraining invalidates cached
|
|
185
|
+
* query vectors (also across processes sharing a Redis cache).
|
|
186
|
+
* @param {'embedder'} role
|
|
187
|
+
* @returns {string|null}
|
|
188
|
+
*/
|
|
189
|
+
getCacheKey(role) {
|
|
190
|
+
if (role !== 'embedder') return null;
|
|
191
|
+
if (!this._fingerprint) {
|
|
192
|
+
this._fingerprint = digest(
|
|
193
|
+
JSON.stringify([this.docCount, [...this.vocabulary], [...this.idf]]),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return `local-embedder|${this._fingerprint}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @returns {number} Vector length produced by embed() once trained */
|
|
200
|
+
get dimensions() {
|
|
201
|
+
return this.vocabulary.size;
|
|
144
202
|
}
|
|
145
203
|
|
|
146
204
|
/**
|