@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,6 +1,8 @@
1
1
  'use strict';
2
+ const { randomBytes } = require('crypto');
2
3
 
3
4
  const { BaseConnector } = require('./base-connector');
5
+ const { throwIfAborted } = require('./connector-utils');
4
6
 
5
7
  /**
6
8
  * Default configuration for the in-memory retriever.
@@ -8,32 +10,101 @@ const { BaseConnector } = require('./base-connector');
8
10
  */
9
11
  const DEFAULT_CONFIG = {
10
12
  maxDocuments: 100000,
13
+ // 'error' rejects a store() that would exceed maxDocuments; 'fifo' evicts
14
+ // the oldest documents (insertion order) to make room.
15
+ eviction: 'error',
11
16
  };
12
17
 
13
18
  /**
14
19
  * In-memory vector store with cosine similarity retrieval.
15
20
  * Stores document vectors and retrieves the most similar ones to a query vector.
21
+ * All vectors in one store must have the same dimension.
16
22
  * @extends BaseConnector
17
23
  */
18
24
  class MemoryRetriever extends BaseConnector {
19
25
  /**
20
26
  * @param {Object} options
21
27
  * @param {number} [options.maxDocuments=100000] - Maximum number of documents to store
28
+ * @param {'error'|'fifo'} [options.eviction='error'] - What to do when a store()
29
+ * would exceed maxDocuments: throw (default) or evict the oldest documents
22
30
  */
23
31
  constructor(options = {}) {
24
32
  super({ name: 'memory-retriever', ...options });
25
33
  this.config = { ...DEFAULT_CONFIG, ...options };
34
+ if (!['error', 'fifo'].includes(this.config.eviction)) {
35
+ throw new Error(
36
+ `MemoryRetriever: unknown eviction "${this.config.eviction}" (use "error" or "fifo")`,
37
+ );
38
+ }
26
39
  /** @type {Map<string, { vector: number[], metadata?: Object, content?: string }>} */
27
40
  this.documents = new Map();
41
+ /** @type {number|null} Vector dimension, fixed by the first stored vector */
42
+ this.dimensions = null;
28
43
  this.connected = true;
44
+ // Cache identity: unique per instance, bumped on every change, so a
45
+ // shared cache never returns another store's or an older state's results.
46
+ this._cacheId = randomBytes(8).toString('hex');
47
+ this._revision = 0;
48
+ }
49
+
50
+ /**
51
+ * Cache identity for createRagPipeline({ cache }).
52
+ * @param {'retriever'} role
53
+ * @returns {string|null}
54
+ */
55
+ getCacheKey(role) {
56
+ return role === 'retriever'
57
+ ? `memory|${this._cacheId}|${this._revision}`
58
+ : null;
29
59
  }
30
60
 
31
61
  /**
32
- * Store one or more vector documents.
62
+ * Store one or more vector documents. The batch is validated before
63
+ * anything is inserted, so a rejected call leaves the store unchanged.
64
+ *
33
65
  * @param {Array<{ id: string, vector: number[], metadata?: Object, content?: string }>} vectors
34
66
  * @returns {Promise<void>}
67
+ * @throws {Error} When a vector's dimension differs from the store's, or
68
+ * (eviction 'error') when the store would exceed maxDocuments
35
69
  */
36
70
  async store(vectors) {
71
+ let dimensions = this.dimensions;
72
+ const newIds = new Set();
73
+ for (const doc of vectors) {
74
+ const length = doc && doc.vector ? doc.vector.length : undefined;
75
+ if (typeof length !== 'number') {
76
+ throw new Error(
77
+ `MemoryRetriever.store(): document "${doc && doc.id}" has no vector`,
78
+ );
79
+ }
80
+ if (dimensions === null) {
81
+ dimensions = length;
82
+ } else if (length !== dimensions) {
83
+ throw new Error(
84
+ `MemoryRetriever.store(): document "${doc.id}" has ${length} dimensions ` +
85
+ `but the store holds ${dimensions}-dimension vectors. Use the same embedder for all documents.`,
86
+ );
87
+ }
88
+ if (!this.documents.has(doc.id)) newIds.add(doc.id);
89
+ }
90
+
91
+ const { maxDocuments, eviction } = this.config;
92
+ const projected = this.documents.size + newIds.size;
93
+ if (projected > maxDocuments) {
94
+ if (eviction !== 'fifo') {
95
+ throw new Error(
96
+ `MemoryRetriever.store(): storing ${newIds.size} new document(s) would bring the ` +
97
+ `total to ${projected}, exceeding maxDocuments (${maxDocuments}). Raise ` +
98
+ 'maxDocuments, call clear(), or construct with { eviction: \'fifo\' }.',
99
+ );
100
+ }
101
+ if (newIds.size > maxDocuments) {
102
+ throw new Error(
103
+ `MemoryRetriever.store(): batch of ${newIds.size} new documents exceeds maxDocuments (${maxDocuments})`,
104
+ );
105
+ }
106
+ }
107
+
37
108
  for (const doc of vectors) {
38
109
  this.documents.set(doc.id, {
39
110
  vector: doc.vector,
@@ -41,32 +112,63 @@ class MemoryRetriever extends BaseConnector {
41
112
  content: doc.content || '',
42
113
  });
43
114
  }
115
+ if (vectors.length > 0) this.dimensions = dimensions;
44
116
 
45
- if (this.documents.size > this.config.maxDocuments) {
46
- this.emit('warning', {
47
- message: `Document count (${this.documents.size}) exceeds maxDocuments (${this.config.maxDocuments})`,
48
- });
117
+ let evicted = 0;
118
+ if (this.documents.size > maxDocuments) {
119
+ // FIFO eviction: Map iteration follows insertion order. Never evict a
120
+ // document from the current batch.
121
+ for (const id of this.documents.keys()) {
122
+ if (this.documents.size <= maxDocuments) break;
123
+ if (newIds.has(id)) continue;
124
+ this.documents.delete(id);
125
+ evicted++;
126
+ }
127
+ this.emit('evicted', { count: evicted, total: this.documents.size });
49
128
  }
50
129
 
130
+ this._revision += 1;
51
131
  this.emit('stored', { count: vectors.length, total: this.documents.size });
52
132
  }
53
133
 
54
134
  /**
55
135
  * Retrieve the top-k most similar documents to a query vector.
56
- * @param {number[]|{ query?: string, queryVector: number[], topK?: number }} queryOrObj
57
- * Either a raw vector array or an object with queryVector and optional topK.
136
+ * @param {number[]|{ query?: string, queryVector: number[], topK?: number, signal?: AbortSignal }} queryOrObj
137
+ * Either a raw vector array or an object with queryVector, optional topK
138
+ * and optional AbortSignal.
58
139
  * @param {number} [k=10] - Number of results to return (used when queryOrObj is an array)
59
140
  * @returns {Promise<Array<{ id: string, score: number, metadata: Object, content: string }>>}
141
+ * @throws {Error} When the query vector's dimension differs from the stored vectors
60
142
  */
61
143
  async retrieve(queryOrObj, k = 10) {
62
144
  let queryVector;
63
145
  let topK = k;
146
+ let signal;
64
147
 
65
148
  if (Array.isArray(queryOrObj)) {
66
149
  queryVector = queryOrObj;
67
150
  } else {
68
- queryVector = queryOrObj.queryVector;
69
- topK = queryOrObj.topK || k;
151
+ queryVector = queryOrObj?.queryVector;
152
+ topK = queryOrObj?.topK || k;
153
+ signal = queryOrObj?.signal;
154
+ }
155
+
156
+ throwIfAborted(signal);
157
+
158
+ if (!Array.isArray(queryVector) && !ArrayBuffer.isView(queryVector)) {
159
+ throw new Error(
160
+ 'MemoryRetriever.retrieve(): queryVector is required. Pass an ' +
161
+ '`embedder` to createRagPipeline or supply `queryVector` to run().',
162
+ );
163
+ }
164
+
165
+ if (this.dimensions !== null && this.documents.size > 0) {
166
+ if (queryVector.length !== this.dimensions) {
167
+ throw new Error(
168
+ `Query vector has ${queryVector.length} dimensions but the store holds ` +
169
+ `${this.dimensions}-dimension vectors. Use the same embedder for queries and documents.`,
170
+ );
171
+ }
70
172
  }
71
173
 
72
174
  const results = [];
@@ -83,23 +185,29 @@ class MemoryRetriever extends BaseConnector {
83
185
 
84
186
  results.sort((a, b) => b.score - a.score);
85
187
 
188
+ throwIfAborted(signal);
86
189
  return results.slice(0, topK);
87
190
  }
88
191
 
89
192
  /**
90
- * Compute cosine similarity between two vectors.
193
+ * Compute cosine similarity between two vectors of equal length.
91
194
  * @param {number[]} a - First vector
92
195
  * @param {number[]} b - Second vector
93
196
  * @returns {number} Cosine similarity in the range [-1, 1], or 0 for zero vectors
197
+ * @throws {Error} When the vectors have different lengths
94
198
  * @private
95
199
  */
96
200
  _cosineSimilarity(a, b) {
97
- const len = Math.min(a.length, b.length);
201
+ if (a.length !== b.length) {
202
+ throw new Error(
203
+ `Cannot compare vectors of different dimensions (${a.length} vs ${b.length})`,
204
+ );
205
+ }
98
206
  let dot = 0;
99
207
  let magA = 0;
100
208
  let magB = 0;
101
209
 
102
- for (let i = 0; i < len; i++) {
210
+ for (let i = 0; i < a.length; i++) {
103
211
  dot += a[i] * b[i];
104
212
  magA += a[i] * a[i];
105
213
  magB += b[i] * b[i];
@@ -112,21 +220,24 @@ class MemoryRetriever extends BaseConnector {
112
220
  }
113
221
 
114
222
  /**
115
- * Remove all stored documents.
223
+ * Remove all stored documents and reset the vector dimension.
116
224
  * @returns {Promise<void>}
117
225
  */
118
226
  async clear() {
119
227
  this.documents.clear();
228
+ this.dimensions = null;
229
+ this._revision += 1;
120
230
  }
121
231
 
122
232
  /**
123
233
  * Get statistics about the store.
124
- * @returns {{ documentCount: number, maxDocuments: number }}
234
+ * @returns {{ documentCount: number, maxDocuments: number, dimensions: number|null }}
125
235
  */
126
236
  getStats() {
127
237
  return {
128
238
  documentCount: this.documents.size,
129
239
  maxDocuments: this.config.maxDocuments,
240
+ dimensions: this.dimensions,
130
241
  };
131
242
  }
132
243
 
@@ -1,7 +1,16 @@
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
4
6
  const { BaseConnector } = require('./base-connector');
7
+ const {
8
+ raceAbort,
9
+ signalOptions,
10
+ throwIfAborted,
11
+ } = require('./connector-utils');
12
+ const { buildRagPrompt } = require('./rag-prompt');
13
+ const { componentCacheKey } = require('../cache/component-keys');
5
14
 
6
15
  /**
7
16
  * Default configuration for the Ollama connector.
@@ -13,14 +22,41 @@ const DEFAULT_CONFIG = {
13
22
  embeddingModel: 'nomic-embed-text',
14
23
  timeout: 60000,
15
24
  temperature: 0.7,
25
+ maxRetries: 3,
26
+ retryDelay: 1000,
16
27
  };
17
28
 
18
29
  /**
19
30
  * Connector for Ollama local LLM server.
20
31
  * Supports text generation (sync and streaming) and embeddings.
32
+ * Ollama needs no API key.
21
33
  * @extends BaseConnector
22
34
  */
23
35
  class OllamaConnector extends BaseConnector {
36
+ /**
37
+ * Cache identity for createRagPipeline({ cache }).
38
+ * @param {'embedder'|'llm'} role
39
+ * @returns {string|null}
40
+ */
41
+ getCacheKey(role) {
42
+ const c = this.config;
43
+ if (role === 'embedder') {
44
+ return componentCacheKey('ollama-embed', {
45
+ baseURL: c.baseURL,
46
+ model: c.embeddingModel,
47
+ });
48
+ }
49
+ if (role === 'llm') {
50
+ return componentCacheKey('ollama', {
51
+ baseURL: c.baseURL,
52
+ model: c.model,
53
+ temperature: c.temperature,
54
+ systemPrompt: c.systemPrompt,
55
+ });
56
+ }
57
+ return null;
58
+ }
59
+
24
60
  /**
25
61
  * @param {Object} options
26
62
  * @param {string} [options.baseURL='http://localhost:11434'] - Ollama server URL
@@ -28,11 +64,14 @@ class OllamaConnector extends BaseConnector {
28
64
  * @param {string} [options.embeddingModel='nomic-embed-text'] - Default embedding model
29
65
  * @param {number} [options.timeout=60000] - Request timeout in ms
30
66
  * @param {number} [options.temperature=0.7] - Generation temperature
67
+ * @param {number} [options.maxRetries=3] - Retries for transient failures
68
+ * @param {number} [options.retryDelay=1000] - Base backoff delay in ms
69
+ * @param {string} [options.systemPrompt] - System prompt used when RAG context is supplied
31
70
  */
32
71
  constructor(options = {}) {
33
72
  super({ name: 'ollama', ...options });
34
73
  this.config = { ...DEFAULT_CONFIG, ...options };
35
- this.client = axios.create({
74
+ this.client = loadAxios().create({
36
75
  baseURL: this.config.baseURL,
37
76
  timeout: this.config.timeout,
38
77
  });
@@ -67,6 +106,46 @@ class OllamaConnector extends BaseConnector {
67
106
  }
68
107
  }
69
108
 
109
+ /**
110
+ * POST with the connector's retry policy and cancellation.
111
+ * @private
112
+ */
113
+ _post(path, body, signal) {
114
+ throwIfAborted(signal);
115
+ return this.withRetry(
116
+ async () => {
117
+ const response = await this.client.post(
118
+ path,
119
+ body,
120
+ signal ? { signal } : undefined,
121
+ );
122
+ return response.data;
123
+ },
124
+ this.config.maxRetries,
125
+ this.config.retryDelay,
126
+ { signal },
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Build the /api/generate body. Retrieved context goes into the prompt as
132
+ * delimited, untrusted reference data; instructions go in `system`.
133
+ * @private
134
+ */
135
+ _buildBody(query, context, options, stream) {
136
+ const prompt = buildRagPrompt(query, context, {
137
+ systemPrompt: this.config.systemPrompt,
138
+ });
139
+ const body = {
140
+ model: options.model || this.config.model,
141
+ prompt: prompt.user,
142
+ stream,
143
+ options: { temperature: options.temperature ?? this.config.temperature },
144
+ };
145
+ if (prompt.system) body.system = prompt.system;
146
+ return body;
147
+ }
148
+
70
149
  /**
71
150
  * Generate a response for the given query with optional context documents.
72
151
  * @param {string} query - The user query
@@ -74,24 +153,13 @@ class OllamaConnector extends BaseConnector {
74
153
  * @param {Object} [options={}] - Override options
75
154
  * @param {string} [options.model] - Override the generation model
76
155
  * @param {number} [options.temperature] - Override temperature
156
+ * @param {AbortSignal} [options.signal] - Cancels the request
77
157
  * @returns {Promise<string>} Generated response text
78
158
  */
79
159
  async generate(query, context = [], options = {}) {
80
- const model = options.model || this.config.model;
81
- const temperature = options.temperature ?? this.config.temperature;
82
- const prompt = this._buildPrompt(query, context);
83
-
84
- const result = await this.withRetry(async () => {
85
- const response = await this.client.post('/api/generate', {
86
- model,
87
- prompt,
88
- stream: false,
89
- options: { temperature },
90
- });
91
- return response.data.response;
92
- });
93
-
94
- return result;
160
+ const body = this._buildBody(query, context, options, false);
161
+ const data = await this._post('/api/generate', body, options.signal);
162
+ return data.response;
95
163
  }
96
164
 
97
165
  /**
@@ -101,31 +169,31 @@ class OllamaConnector extends BaseConnector {
101
169
  * @param {Object} [options={}] - Override options
102
170
  * @param {string} [options.model] - Override the generation model
103
171
  * @param {number} [options.temperature] - Override temperature
172
+ * @param {AbortSignal} [options.signal] - Cancels the stream
104
173
  * @yields {{ token: string, done: boolean }}
105
174
  */
106
175
  async *generateStream(query, context = [], options = {}) {
107
- const model = options.model || this.config.model;
108
- const temperature = options.temperature ?? this.config.temperature;
109
- const prompt = this._buildPrompt(query, context);
176
+ const { signal } = options;
177
+ throwIfAborted(signal);
178
+ const body = this._buildBody(query, context, options, true);
110
179
 
111
180
  let response;
112
181
  try {
113
- response = await this.client.post(
114
- '/api/generate',
115
- {
116
- model,
117
- prompt,
118
- stream: true,
119
- options: { temperature },
120
- },
121
- { responseType: 'stream' },
182
+ response = await raceAbort(
183
+ this.client.post('/api/generate', body, {
184
+ responseType: 'stream',
185
+ ...signalOptions(signal),
186
+ }),
187
+ signal,
122
188
  );
123
189
  } catch (err) {
190
+ if (err && err.name === 'AbortError') throw err;
124
191
  throw new Error(`Ollama streaming request failed: ${err.message}`);
125
192
  }
126
193
 
127
194
  let buffer = '';
128
195
  for await (const chunk of response.data) {
196
+ throwIfAborted(signal);
129
197
  buffer += chunk.toString();
130
198
  const lines = buffer.split('\n');
131
199
  // Keep the last potentially incomplete line in the buffer
@@ -157,48 +225,41 @@ class OllamaConnector extends BaseConnector {
157
225
  /**
158
226
  * Generate an embedding vector for the given text.
159
227
  * @param {string} text - Text to embed
228
+ * @param {Object} [options={}]
229
+ * @param {AbortSignal} [options.signal] - Cancels the request
160
230
  * @returns {Promise<number[]>} Embedding vector
161
231
  */
162
- async embed(text) {
163
- const result = await this.withRetry(async () => {
164
- const response = await this.client.post('/api/embeddings', {
165
- model: this.config.embeddingModel,
166
- prompt: text,
167
- });
168
- return response.data.embedding;
169
- });
170
-
171
- return result;
232
+ async embed(text, options = {}) {
233
+ const data = await this._post(
234
+ '/api/embeddings',
235
+ { model: this.config.embeddingModel, prompt: text },
236
+ options.signal,
237
+ );
238
+ return data.embedding;
172
239
  }
173
240
 
174
241
  /**
175
242
  * Generate an embedding vector for a query string.
176
243
  * Alias for {@link embed}.
177
244
  * @param {string} query - Query text to embed
245
+ * @param {Object} [options={}] - Same as embed()
178
246
  * @returns {Promise<number[]>} Embedding vector
179
247
  */
180
- async embedQuery(query) {
181
- return this.embed(query);
248
+ async embedQuery(query, options = {}) {
249
+ return this.embed(query, options);
182
250
  }
183
251
 
184
252
  /**
185
253
  * Build a prompt string incorporating context documents.
254
+ * Kept for backward compatibility; context is delimited and labelled as
255
+ * untrusted reference data.
186
256
  * @param {string} query - The user query
187
257
  * @param {Array<Object|string>} context - Context documents
188
258
  * @returns {string} Formatted prompt
189
259
  * @private
190
260
  */
191
261
  _buildPrompt(query, context) {
192
- if (!context || context.length === 0) return query;
193
-
194
- const contextText = context
195
- .map((doc) => {
196
- const text = doc.content || doc.text || String(doc);
197
- return text;
198
- })
199
- .join('\n\n');
200
-
201
- return `Context:\n${contextText}\n\nQuestion: ${query}\n\nAnswer:`;
262
+ return buildRagPrompt(query, context).user;
202
263
  }
203
264
  }
204
265