@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.
Files changed (258) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +431 -0
  3. package/README.md +418 -117
  4. package/SECURITY.md +5 -5
  5. package/dist/experimental.cjs +2 -0
  6. package/dist/experimental.d.ts +1 -0
  7. package/dist/experimental.mjs +5 -0
  8. package/dist/index.cjs +6442 -1355
  9. package/dist/index.d.ts +129 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/testing.cjs +2 -0
  12. package/dist/testing.d.ts +1 -0
  13. package/dist/testing.mjs +5 -0
  14. package/dist/types/adapters/ai-sdk.d.ts +53 -0
  15. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  16. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  17. package/dist/types/agentic/query-planner.d.ts +145 -0
  18. package/dist/types/agentic/self-critique.d.ts +45 -0
  19. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  20. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  21. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  22. package/dist/types/ai/multimodal.d.ts +39 -0
  23. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  24. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  25. package/dist/types/cache/component-keys.d.ts +7 -0
  26. package/dist/types/cache/memory-cache.d.ts +54 -0
  27. package/dist/types/cache/pipeline-cache.d.ts +66 -0
  28. package/dist/types/cache/redis-cache.d.ts +46 -0
  29. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  30. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  31. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  32. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  33. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  34. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  35. package/dist/types/citation/citation-tracker.d.ts +64 -0
  36. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  37. package/dist/types/citation/source-mapper.d.ts +37 -0
  38. package/dist/types/connectors/anthropic-connector.d.ts +97 -0
  39. package/dist/types/connectors/base-connector.d.ts +87 -0
  40. package/dist/types/connectors/cohere-connector.d.ts +94 -0
  41. package/dist/types/connectors/connector-utils.d.ts +110 -0
  42. package/dist/types/connectors/local-embedder.d.ts +95 -0
  43. package/dist/types/connectors/memory-retriever.d.ts +99 -0
  44. package/dist/types/connectors/ollama-connector.d.ts +115 -0
  45. package/dist/types/connectors/openai-connector.d.ts +129 -0
  46. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  47. package/dist/types/core/create-pipeline.d.ts +8 -0
  48. package/dist/types/core/plugin-registry.d.ts +136 -0
  49. package/dist/types/core/stage-contracts.d.ts +36 -0
  50. package/dist/types/core/validate-plugin.d.ts +45 -0
  51. package/dist/types/cost/cost-calculator.d.ts +152 -0
  52. package/dist/types/cost/cost-tracker.d.ts +126 -0
  53. package/dist/types/cost/token-budget.d.ts +110 -0
  54. package/dist/types/dag/core/error-context.d.ts +239 -0
  55. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  56. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  57. package/dist/types/dag/dag-engine.d.ts +156 -0
  58. package/dist/types/dag/dag-node.d.ts +37 -0
  59. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  60. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  61. package/dist/types/debug/execution-tracer.d.ts +57 -0
  62. package/dist/types/debug/trace-inspector.d.ts +31 -0
  63. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  64. package/dist/types/enterprise/data-governance.d.ts +203 -0
  65. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  66. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  67. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  68. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  69. package/dist/types/evaluate/groundedness.d.ts +60 -0
  70. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  71. package/dist/types/evaluate/relevance.d.ts +21 -0
  72. package/dist/types/evaluate/scoring.d.ts +123 -0
  73. package/dist/types/evaluate/semantic-similarity.d.ts +14 -0
  74. package/dist/types/experimental.d.ts +15 -0
  75. package/dist/types/graph/entity-extractor.d.ts +84 -0
  76. package/dist/types/graph/graph-index.d.ts +49 -0
  77. package/dist/types/graph/graph-retriever.d.ts +81 -0
  78. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  79. package/dist/types/grounding/check-answer.d.ts +101 -0
  80. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  81. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  82. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  83. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  84. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  85. package/dist/types/mcp/mcp-server.d.ts +85 -0
  86. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  87. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  88. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  89. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  90. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  91. package/dist/types/retrieval/file-vector-store.d.ts +99 -0
  92. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  93. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  94. package/dist/types/security/jwt-config.d.ts +80 -0
  95. package/dist/types/security/jwt-validator.d.ts +195 -0
  96. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  97. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  98. package/dist/types/security/validator.d.ts +5 -0
  99. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  100. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  101. package/dist/types/streaming/stream-router.d.ts +88 -0
  102. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  103. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  104. package/dist/types/templates/scaffold.d.ts +55 -0
  105. package/dist/types/templates/template-registry.d.ts +63 -0
  106. package/dist/types/testing.d.ts +168 -0
  107. package/dist/types/utils/embed-query.d.ts +20 -0
  108. package/dist/types/utils/logger.d.ts +50 -0
  109. package/dist/types/utils/rate-limiter.d.ts +66 -0
  110. package/dist/types/utils/retry.d.ts +27 -0
  111. package/dist/types/utils/run-abort.d.ts +34 -0
  112. package/dist/types/utils/secure-logging.d.ts +51 -0
  113. package/dist/types/utils/structured-logger.d.ts +126 -0
  114. package/package.json +72 -33
  115. package/src/adapters/ai-sdk.js +215 -0
  116. package/src/agentic/agentic-pipeline.js +75 -7
  117. package/src/agentic/iterative-retriever.js +47 -10
  118. package/src/agentic/query-planner.js +329 -78
  119. package/src/cache/component-keys.js +38 -0
  120. package/src/cache/memory-cache.js +87 -0
  121. package/src/cache/pipeline-cache.js +260 -0
  122. package/src/cache/redis-cache.js +83 -0
  123. package/src/citation/citation-tracker.js +6 -4
  124. package/src/citation/hallucination-detector.js +2 -2
  125. package/src/citation/source-mapper.js +15 -28
  126. package/src/cli/commands/ai.js +106 -0
  127. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  128. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  129. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  130. package/src/cli/commands/plugin-hub.js +114 -49
  131. package/src/cli/diagnostics/config-checker.js +76 -63
  132. package/src/cli/diagnostics/environment-checker.js +62 -31
  133. package/src/cli/diagnostics/plugin-checker.js +36 -89
  134. package/src/cli/doctor-command.js +39 -5
  135. package/src/cli/enhanced-cli-commands.js +104 -233
  136. package/src/cli/handlers/ai-handler.js +364 -0
  137. package/src/cli/handlers/diagnostics-handler.js +78 -31
  138. package/src/cli/handlers/ingest-handler.js +28 -22
  139. package/src/cli/handlers/init-handler.js +148 -25
  140. package/src/cli/handlers/query-handler.js +203 -25
  141. package/src/cli/handlers/run-handler.js +16 -167
  142. package/src/cli/handlers/serve-handler.js +60 -0
  143. package/src/cli/services/bench-stats.js +54 -0
  144. package/src/cli/services/benchmark.js +161 -0
  145. package/src/cli/services/feedback.js +155 -0
  146. package/src/cli/services/instrument.js +83 -0
  147. package/src/cli/services/rag-service.js +361 -0
  148. package/src/cli/shared/cli-utils.js +24 -7
  149. package/src/cli/shared/config-context.js +21 -0
  150. package/src/cli/wizard/config-builder.js +81 -0
  151. package/src/cli/wizard/init-wizard.js +602 -0
  152. package/src/config/component-factory.js +125 -0
  153. package/src/config/enhanced-ragrc-schema.js +0 -2
  154. package/src/config/load-config.js +29 -10
  155. package/src/config/load-plugin-config.js +46 -2
  156. package/src/config/validate-schema.js +4 -1
  157. package/src/connectors/anthropic-connector.js +115 -57
  158. package/src/connectors/base-connector.js +74 -19
  159. package/src/connectors/cohere-connector.js +108 -34
  160. package/src/connectors/connector-utils.js +302 -0
  161. package/src/connectors/local-embedder.js +63 -5
  162. package/src/connectors/memory-retriever.js +125 -14
  163. package/src/connectors/ollama-connector.js +112 -51
  164. package/src/connectors/openai-connector.js +153 -44
  165. package/src/connectors/rag-prompt.js +88 -0
  166. package/src/core/create-pipeline.js +307 -61
  167. package/src/core/performance/parallel-processor.js +62 -1
  168. package/src/core/plugin-registry.js +125 -26
  169. package/src/core/stage-contracts.js +36 -0
  170. package/src/core/validate-plugin.js +85 -0
  171. package/src/cost/cost-calculator.js +205 -32
  172. package/src/debug/execution-tracer.js +14 -4
  173. package/src/dev/dev-server.js +173 -11
  174. package/src/dev/hot-reload.js +44 -4
  175. package/src/ecosystem/plugin-checks.js +181 -0
  176. package/src/ecosystem/plugin-hub.js +50 -125
  177. package/src/ecosystem/plugin-installer.js +298 -0
  178. package/src/ecosystem/plugin-probe.js +127 -0
  179. package/src/ecosystem/plugin-scanner.js +185 -0
  180. package/src/ecosystem/tar-extract.js +114 -0
  181. package/src/enterprise/audit-logging.js +158 -29
  182. package/src/enterprise/sso-integration.js +716 -228
  183. package/src/evaluate/context-metrics.js +28 -16
  184. package/src/evaluate/evaluator.js +3 -3
  185. package/src/evaluate/faithfulness.js +18 -14
  186. package/src/evaluate/groundedness.js +43 -12
  187. package/src/evaluate/pipeline-evaluator.js +35 -8
  188. package/src/evaluate/scoring.js +251 -3
  189. package/src/evaluate/semantic-similarity.js +79 -0
  190. package/src/experimental.js +71 -0
  191. package/src/grounding/check-answer.js +173 -0
  192. package/src/guardrails/guardrails-pipeline.js +69 -7
  193. package/src/guardrails/injection-detector.js +283 -0
  194. package/src/guardrails/post-generation-guard.js +19 -0
  195. package/src/guardrails/pre-retrieval-guard.js +36 -36
  196. package/src/guardrails/retrieval-guard.js +58 -8
  197. package/src/index.js +58 -1
  198. package/src/loader/csv-loader.js +3 -2
  199. package/src/loader/document-reader.js +91 -0
  200. package/src/loader/html-loader.js +3 -1
  201. package/src/loader/markdown-loader.js +3 -2
  202. package/src/reranker/cascade-reranker.js +3 -0
  203. package/src/reranker/embedding-reranker.js +136 -21
  204. package/src/reranker/llm-reranker.js +79 -12
  205. package/src/reranker/scoring-reranker.js +3 -0
  206. package/src/retrieval/bm25-search.js +12 -6
  207. package/src/retrieval/file-vector-store.js +241 -0
  208. package/src/retrieval/hybrid-retriever.js +22 -19
  209. package/src/security/jwt-config.js +13 -2
  210. package/src/security/jwt-validator.js +139 -34
  211. package/src/security/plugin-sandbox.js +18 -1
  212. package/src/security/plugin-signature-verifier.js +245 -70
  213. package/src/testing.js +254 -0
  214. package/src/utils/deprecate.js +69 -0
  215. package/src/utils/embed-query.js +33 -0
  216. package/src/utils/input-sanitizer.js +19 -4
  217. package/src/utils/logger.js +34 -4
  218. package/src/utils/run-abort.js +102 -0
  219. package/src/ai/federated-learning.js +0 -211
  220. package/src/ai/federation.js +0 -71
  221. package/src/ai/index.js +0 -22
  222. package/src/ai/model-trainer.js +0 -217
  223. package/src/ai/model-training.js +0 -202
  224. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  225. package/src/ai/orchestrator.js +0 -65
  226. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  227. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  228. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  229. package/src/cli/commands/ai-ml/index.js +0 -17
  230. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  231. package/src/cli/commands/ai-ml/shared.js +0 -51
  232. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  233. package/src/cli/commands/ai-ml.js +0 -386
  234. package/src/cli/commands/docs.js +0 -228
  235. package/src/cli/commands/dx.js +0 -538
  236. package/src/cli/commands/wizard.js +0 -627
  237. package/src/cli/input-sanitization-middleware.js +0 -356
  238. package/src/cli/interactive-wizard.js +0 -373
  239. package/src/cli/templates/development.template.json +0 -64
  240. package/src/cli/templates/minimal.template.json +0 -33
  241. package/src/cli/templates/production.template.json +0 -104
  242. package/src/cli/templates/testing.template.json +0 -45
  243. package/src/cli/wizard/configuration.js +0 -148
  244. package/src/cli/wizard/observability.js +0 -79
  245. package/src/cli/wizard/performance.js +0 -76
  246. package/src/cli/wizard/plugin-selection.js +0 -297
  247. package/src/cli/wizard/preview-save.js +0 -172
  248. package/src/cli/wizard/project-setup.js +0 -66
  249. package/src/core/pipeline-factory.js +0 -59
  250. package/src/dag/enhanced-dag-engine.js +0 -894
  251. package/src/dx/index.js +0 -40
  252. package/src/dx/integration-templates.js +0 -662
  253. package/src/dx/performance-profiler.js +0 -968
  254. package/src/dx/realtime-debugger.js +0 -755
  255. package/src/dx/visual-pipeline-builder.js +0 -578
  256. package/src/ingest.js +0 -59
  257. package/src/pipeline/pipeline.js +0 -18
  258. package/src/query.js +0 -117
@@ -1,19 +1,35 @@
1
1
  'use strict';
2
2
 
3
- const axios = require('axios');
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: process.env.COHERE_API_KEY || '',
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 = { ...DEFAULT_CONFIG, ...options };
39
- this.client = axios.create({
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
- if (!this.config.apiKey) {
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
- return this.withRetry(async () => {
71
- const response = await this.client.post('/embed', {
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
- return response.data.embeddings[0];
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
- return this.withRetry(async () => {
90
- const response = await this.client.post('/embed', {
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
- return response.data.embeddings[0];
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
- return this.withRetry(async () => {
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
- const response = await this.client.post('/rerank', {
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
- return response.data.results.map((result) => ({
123
- ...documents[result.index],
124
- score: result.relevance_score,
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
- const idfValue = Math.log(this.docCount / (freq + 1));
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
  /**