@devilsdev/rag-pipeline-utils 2.4.5 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +345 -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 +4992 -1242
  9. package/dist/index.d.ts +80 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  12. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  13. package/dist/types/agentic/query-planner.d.ts +145 -0
  14. package/dist/types/agentic/self-critique.d.ts +45 -0
  15. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  16. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  17. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  18. package/dist/types/ai/multimodal.d.ts +39 -0
  19. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  20. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  21. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  22. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  23. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  24. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  25. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  26. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  27. package/dist/types/citation/citation-tracker.d.ts +64 -0
  28. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  29. package/dist/types/citation/source-mapper.d.ts +37 -0
  30. package/dist/types/connectors/anthropic-connector.d.ts +92 -0
  31. package/dist/types/connectors/base-connector.d.ts +87 -0
  32. package/dist/types/connectors/cohere-connector.d.ts +89 -0
  33. package/dist/types/connectors/connector-utils.d.ts +110 -0
  34. package/dist/types/connectors/local-embedder.d.ts +86 -0
  35. package/dist/types/connectors/memory-retriever.d.ts +91 -0
  36. package/dist/types/connectors/ollama-connector.d.ts +110 -0
  37. package/dist/types/connectors/openai-connector.d.ts +114 -0
  38. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  39. package/dist/types/cost/cost-calculator.d.ts +152 -0
  40. package/dist/types/cost/cost-tracker.d.ts +126 -0
  41. package/dist/types/cost/token-budget.d.ts +110 -0
  42. package/dist/types/dag/core/error-context.d.ts +239 -0
  43. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  44. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  45. package/dist/types/dag/dag-engine.d.ts +156 -0
  46. package/dist/types/dag/dag-node.d.ts +37 -0
  47. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  48. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  49. package/dist/types/debug/execution-tracer.d.ts +57 -0
  50. package/dist/types/debug/trace-inspector.d.ts +31 -0
  51. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  52. package/dist/types/enterprise/data-governance.d.ts +203 -0
  53. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  54. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  55. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  56. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  57. package/dist/types/evaluate/groundedness.d.ts +60 -0
  58. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  59. package/dist/types/evaluate/relevance.d.ts +21 -0
  60. package/dist/types/evaluate/scoring.d.ts +123 -0
  61. package/dist/types/experimental.d.ts +15 -0
  62. package/dist/types/graph/entity-extractor.d.ts +84 -0
  63. package/dist/types/graph/graph-index.d.ts +49 -0
  64. package/dist/types/graph/graph-retriever.d.ts +81 -0
  65. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  66. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  67. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  68. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  69. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  70. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  71. package/dist/types/mcp/mcp-server.d.ts +85 -0
  72. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  73. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  74. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  75. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  76. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  77. package/dist/types/retrieval/file-vector-store.d.ts +87 -0
  78. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  79. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  80. package/dist/types/security/jwt-config.d.ts +80 -0
  81. package/dist/types/security/jwt-validator.d.ts +195 -0
  82. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  83. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  84. package/dist/types/security/validator.d.ts +5 -0
  85. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  86. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  87. package/dist/types/streaming/stream-router.d.ts +88 -0
  88. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  89. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  90. package/dist/types/templates/scaffold.d.ts +55 -0
  91. package/dist/types/templates/template-registry.d.ts +63 -0
  92. package/dist/types/utils/embed-query.d.ts +20 -0
  93. package/dist/types/utils/logger.d.ts +50 -0
  94. package/dist/types/utils/rate-limiter.d.ts +66 -0
  95. package/dist/types/utils/retry.d.ts +27 -0
  96. package/dist/types/utils/run-abort.d.ts +34 -0
  97. package/dist/types/utils/secure-logging.d.ts +51 -0
  98. package/dist/types/utils/structured-logger.d.ts +126 -0
  99. package/package.json +53 -29
  100. package/src/agentic/agentic-pipeline.js +75 -7
  101. package/src/agentic/iterative-retriever.js +47 -10
  102. package/src/agentic/query-planner.js +329 -78
  103. package/src/citation/citation-tracker.js +6 -4
  104. package/src/citation/hallucination-detector.js +2 -2
  105. package/src/citation/source-mapper.js +15 -28
  106. package/src/cli/commands/ai.js +101 -0
  107. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  108. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  109. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  110. package/src/cli/commands/plugin-hub.js +96 -36
  111. package/src/cli/diagnostics/config-checker.js +76 -63
  112. package/src/cli/diagnostics/environment-checker.js +62 -31
  113. package/src/cli/diagnostics/plugin-checker.js +36 -89
  114. package/src/cli/doctor-command.js +39 -5
  115. package/src/cli/enhanced-cli-commands.js +72 -217
  116. package/src/cli/handlers/ai-handler.js +364 -0
  117. package/src/cli/handlers/diagnostics-handler.js +78 -31
  118. package/src/cli/handlers/ingest-handler.js +28 -22
  119. package/src/cli/handlers/init-handler.js +148 -25
  120. package/src/cli/handlers/query-handler.js +41 -26
  121. package/src/cli/handlers/run-handler.js +16 -167
  122. package/src/cli/handlers/serve-handler.js +60 -0
  123. package/src/cli/services/bench-stats.js +54 -0
  124. package/src/cli/services/benchmark.js +161 -0
  125. package/src/cli/services/feedback.js +155 -0
  126. package/src/cli/services/rag-service.js +322 -0
  127. package/src/cli/shared/cli-utils.js +24 -7
  128. package/src/cli/shared/config-context.js +21 -0
  129. package/src/cli/wizard/config-builder.js +81 -0
  130. package/src/cli/wizard/init-wizard.js +617 -0
  131. package/src/config/component-factory.js +125 -0
  132. package/src/config/load-config.js +29 -10
  133. package/src/config/load-plugin-config.js +46 -2
  134. package/src/connectors/anthropic-connector.js +93 -55
  135. package/src/connectors/base-connector.js +74 -19
  136. package/src/connectors/cohere-connector.js +88 -32
  137. package/src/connectors/connector-utils.js +302 -0
  138. package/src/connectors/local-embedder.js +43 -5
  139. package/src/connectors/memory-retriever.js +108 -15
  140. package/src/connectors/ollama-connector.js +83 -49
  141. package/src/connectors/openai-connector.js +108 -44
  142. package/src/connectors/rag-prompt.js +88 -0
  143. package/src/core/create-pipeline.js +209 -60
  144. package/src/core/plugin-registry.js +87 -11
  145. package/src/cost/cost-calculator.js +205 -32
  146. package/src/dev/dev-server.js +166 -11
  147. package/src/dev/hot-reload.js +40 -1
  148. package/src/ecosystem/plugin-checks.js +181 -0
  149. package/src/ecosystem/plugin-hub.js +50 -125
  150. package/src/ecosystem/plugin-installer.js +298 -0
  151. package/src/ecosystem/plugin-probe.js +127 -0
  152. package/src/ecosystem/plugin-scanner.js +185 -0
  153. package/src/ecosystem/tar-extract.js +114 -0
  154. package/src/enterprise/audit-logging.js +158 -29
  155. package/src/enterprise/sso-integration.js +715 -228
  156. package/src/evaluate/context-metrics.js +28 -16
  157. package/src/evaluate/evaluator.js +3 -3
  158. package/src/evaluate/faithfulness.js +18 -14
  159. package/src/evaluate/groundedness.js +43 -12
  160. package/src/evaluate/pipeline-evaluator.js +35 -8
  161. package/src/evaluate/scoring.js +251 -3
  162. package/src/experimental.js +71 -0
  163. package/src/guardrails/guardrails-pipeline.js +69 -7
  164. package/src/guardrails/injection-detector.js +283 -0
  165. package/src/guardrails/post-generation-guard.js +19 -0
  166. package/src/guardrails/pre-retrieval-guard.js +36 -36
  167. package/src/guardrails/retrieval-guard.js +58 -8
  168. package/src/index.js +36 -1
  169. package/src/loader/document-reader.js +91 -0
  170. package/src/reranker/cascade-reranker.js +3 -0
  171. package/src/reranker/embedding-reranker.js +136 -21
  172. package/src/reranker/llm-reranker.js +79 -12
  173. package/src/reranker/scoring-reranker.js +3 -0
  174. package/src/retrieval/bm25-search.js +12 -6
  175. package/src/retrieval/file-vector-store.js +212 -0
  176. package/src/retrieval/hybrid-retriever.js +22 -19
  177. package/src/security/jwt-config.js +13 -2
  178. package/src/security/jwt-validator.js +134 -30
  179. package/src/security/plugin-sandbox.js +18 -1
  180. package/src/security/plugin-signature-verifier.js +245 -70
  181. package/src/utils/deprecate.js +69 -0
  182. package/src/utils/embed-query.js +33 -0
  183. package/src/utils/input-sanitizer.js +19 -4
  184. package/src/utils/logger.js +34 -4
  185. package/src/utils/run-abort.js +102 -0
  186. package/src/ai/federated-learning.js +0 -211
  187. package/src/ai/federation.js +0 -71
  188. package/src/ai/index.js +0 -22
  189. package/src/ai/model-trainer.js +0 -217
  190. package/src/ai/model-training.js +0 -202
  191. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  192. package/src/ai/orchestrator.js +0 -65
  193. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  194. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  195. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  196. package/src/cli/commands/ai-ml/index.js +0 -17
  197. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  198. package/src/cli/commands/ai-ml/shared.js +0 -51
  199. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  200. package/src/cli/commands/ai-ml.js +0 -386
  201. package/src/cli/commands/docs.js +0 -228
  202. package/src/cli/commands/dx.js +0 -538
  203. package/src/cli/commands/wizard.js +0 -627
  204. package/src/cli/input-sanitization-middleware.js +0 -356
  205. package/src/cli/interactive-wizard.js +0 -373
  206. package/src/cli/templates/development.template.json +0 -64
  207. package/src/cli/templates/minimal.template.json +0 -33
  208. package/src/cli/templates/production.template.json +0 -104
  209. package/src/cli/templates/testing.template.json +0 -45
  210. package/src/cli/wizard/configuration.js +0 -148
  211. package/src/cli/wizard/observability.js +0 -79
  212. package/src/cli/wizard/performance.js +0 -76
  213. package/src/cli/wizard/plugin-selection.js +0 -297
  214. package/src/cli/wizard/preview-save.js +0 -172
  215. package/src/cli/wizard/project-setup.js +0 -66
  216. package/src/core/pipeline-factory.js +0 -59
  217. package/src/dag/enhanced-dag-engine.js +0 -894
  218. package/src/dx/index.js +0 -40
  219. package/src/dx/integration-templates.js +0 -662
  220. package/src/dx/performance-profiler.js +0 -968
  221. package/src/dx/realtime-debugger.js +0 -755
  222. package/src/dx/visual-pipeline-builder.js +0 -578
  223. package/src/ingest.js +0 -59
  224. package/src/pipeline/pipeline.js +0 -18
  225. package/src/query.js +0 -117
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { BaseConnector } = require('./base-connector');
4
+ const { throwIfAborted } = require('./connector-utils');
4
5
 
5
6
  /**
6
7
  * Default configuration for the in-memory retriever.
@@ -8,32 +9,86 @@ const { BaseConnector } = require('./base-connector');
8
9
  */
9
10
  const DEFAULT_CONFIG = {
10
11
  maxDocuments: 100000,
12
+ // 'error' rejects a store() that would exceed maxDocuments; 'fifo' evicts
13
+ // the oldest documents (insertion order) to make room.
14
+ eviction: 'error',
11
15
  };
12
16
 
13
17
  /**
14
18
  * In-memory vector store with cosine similarity retrieval.
15
19
  * Stores document vectors and retrieves the most similar ones to a query vector.
20
+ * All vectors in one store must have the same dimension.
16
21
  * @extends BaseConnector
17
22
  */
18
23
  class MemoryRetriever extends BaseConnector {
19
24
  /**
20
25
  * @param {Object} options
21
26
  * @param {number} [options.maxDocuments=100000] - Maximum number of documents to store
27
+ * @param {'error'|'fifo'} [options.eviction='error'] - What to do when a store()
28
+ * would exceed maxDocuments: throw (default) or evict the oldest documents
22
29
  */
23
30
  constructor(options = {}) {
24
31
  super({ name: 'memory-retriever', ...options });
25
32
  this.config = { ...DEFAULT_CONFIG, ...options };
33
+ if (!['error', 'fifo'].includes(this.config.eviction)) {
34
+ throw new Error(
35
+ `MemoryRetriever: unknown eviction "${this.config.eviction}" (use "error" or "fifo")`,
36
+ );
37
+ }
26
38
  /** @type {Map<string, { vector: number[], metadata?: Object, content?: string }>} */
27
39
  this.documents = new Map();
40
+ /** @type {number|null} Vector dimension, fixed by the first stored vector */
41
+ this.dimensions = null;
28
42
  this.connected = true;
29
43
  }
30
44
 
31
45
  /**
32
- * Store one or more vector documents.
46
+ * Store one or more vector documents. The batch is validated before
47
+ * anything is inserted, so a rejected call leaves the store unchanged.
48
+ *
33
49
  * @param {Array<{ id: string, vector: number[], metadata?: Object, content?: string }>} vectors
34
50
  * @returns {Promise<void>}
51
+ * @throws {Error} When a vector's dimension differs from the store's, or
52
+ * (eviction 'error') when the store would exceed maxDocuments
35
53
  */
36
54
  async store(vectors) {
55
+ let dimensions = this.dimensions;
56
+ const newIds = new Set();
57
+ for (const doc of vectors) {
58
+ const length = doc && doc.vector ? doc.vector.length : undefined;
59
+ if (typeof length !== 'number') {
60
+ throw new Error(
61
+ `MemoryRetriever.store(): document "${doc && doc.id}" has no vector`,
62
+ );
63
+ }
64
+ if (dimensions === null) {
65
+ dimensions = length;
66
+ } else if (length !== dimensions) {
67
+ throw new Error(
68
+ `MemoryRetriever.store(): document "${doc.id}" has ${length} dimensions ` +
69
+ `but the store holds ${dimensions}-dimension vectors. Use the same embedder for all documents.`,
70
+ );
71
+ }
72
+ if (!this.documents.has(doc.id)) newIds.add(doc.id);
73
+ }
74
+
75
+ const { maxDocuments, eviction } = this.config;
76
+ const projected = this.documents.size + newIds.size;
77
+ if (projected > maxDocuments) {
78
+ if (eviction !== 'fifo') {
79
+ throw new Error(
80
+ `MemoryRetriever.store(): storing ${newIds.size} new document(s) would bring the ` +
81
+ `total to ${projected}, exceeding maxDocuments (${maxDocuments}). Raise ` +
82
+ 'maxDocuments, call clear(), or construct with { eviction: \'fifo\' }.',
83
+ );
84
+ }
85
+ if (newIds.size > maxDocuments) {
86
+ throw new Error(
87
+ `MemoryRetriever.store(): batch of ${newIds.size} new documents exceeds maxDocuments (${maxDocuments})`,
88
+ );
89
+ }
90
+ }
91
+
37
92
  for (const doc of vectors) {
38
93
  this.documents.set(doc.id, {
39
94
  vector: doc.vector,
@@ -41,11 +96,19 @@ class MemoryRetriever extends BaseConnector {
41
96
  content: doc.content || '',
42
97
  });
43
98
  }
44
-
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
- });
99
+ if (vectors.length > 0) this.dimensions = dimensions;
100
+
101
+ let evicted = 0;
102
+ if (this.documents.size > maxDocuments) {
103
+ // FIFO eviction: Map iteration follows insertion order. Never evict a
104
+ // document from the current batch.
105
+ for (const id of this.documents.keys()) {
106
+ if (this.documents.size <= maxDocuments) break;
107
+ if (newIds.has(id)) continue;
108
+ this.documents.delete(id);
109
+ evicted++;
110
+ }
111
+ this.emit('evicted', { count: evicted, total: this.documents.size });
49
112
  }
50
113
 
51
114
  this.emit('stored', { count: vectors.length, total: this.documents.size });
@@ -53,20 +116,42 @@ class MemoryRetriever extends BaseConnector {
53
116
 
54
117
  /**
55
118
  * 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.
119
+ * @param {number[]|{ query?: string, queryVector: number[], topK?: number, signal?: AbortSignal }} queryOrObj
120
+ * Either a raw vector array or an object with queryVector, optional topK
121
+ * and optional AbortSignal.
58
122
  * @param {number} [k=10] - Number of results to return (used when queryOrObj is an array)
59
123
  * @returns {Promise<Array<{ id: string, score: number, metadata: Object, content: string }>>}
124
+ * @throws {Error} When the query vector's dimension differs from the stored vectors
60
125
  */
61
126
  async retrieve(queryOrObj, k = 10) {
62
127
  let queryVector;
63
128
  let topK = k;
129
+ let signal;
64
130
 
65
131
  if (Array.isArray(queryOrObj)) {
66
132
  queryVector = queryOrObj;
67
133
  } else {
68
- queryVector = queryOrObj.queryVector;
69
- topK = queryOrObj.topK || k;
134
+ queryVector = queryOrObj?.queryVector;
135
+ topK = queryOrObj?.topK || k;
136
+ signal = queryOrObj?.signal;
137
+ }
138
+
139
+ throwIfAborted(signal);
140
+
141
+ if (!Array.isArray(queryVector) && !ArrayBuffer.isView(queryVector)) {
142
+ throw new Error(
143
+ 'MemoryRetriever.retrieve(): queryVector is required. Pass an ' +
144
+ '`embedder` to createRagPipeline or supply `queryVector` to run().',
145
+ );
146
+ }
147
+
148
+ if (this.dimensions !== null && this.documents.size > 0) {
149
+ if (queryVector.length !== this.dimensions) {
150
+ throw new Error(
151
+ `Query vector has ${queryVector.length} dimensions but the store holds ` +
152
+ `${this.dimensions}-dimension vectors. Use the same embedder for queries and documents.`,
153
+ );
154
+ }
70
155
  }
71
156
 
72
157
  const results = [];
@@ -83,23 +168,29 @@ class MemoryRetriever extends BaseConnector {
83
168
 
84
169
  results.sort((a, b) => b.score - a.score);
85
170
 
171
+ throwIfAborted(signal);
86
172
  return results.slice(0, topK);
87
173
  }
88
174
 
89
175
  /**
90
- * Compute cosine similarity between two vectors.
176
+ * Compute cosine similarity between two vectors of equal length.
91
177
  * @param {number[]} a - First vector
92
178
  * @param {number[]} b - Second vector
93
179
  * @returns {number} Cosine similarity in the range [-1, 1], or 0 for zero vectors
180
+ * @throws {Error} When the vectors have different lengths
94
181
  * @private
95
182
  */
96
183
  _cosineSimilarity(a, b) {
97
- const len = Math.min(a.length, b.length);
184
+ if (a.length !== b.length) {
185
+ throw new Error(
186
+ `Cannot compare vectors of different dimensions (${a.length} vs ${b.length})`,
187
+ );
188
+ }
98
189
  let dot = 0;
99
190
  let magA = 0;
100
191
  let magB = 0;
101
192
 
102
- for (let i = 0; i < len; i++) {
193
+ for (let i = 0; i < a.length; i++) {
103
194
  dot += a[i] * b[i];
104
195
  magA += a[i] * a[i];
105
196
  magB += b[i] * b[i];
@@ -112,21 +203,23 @@ class MemoryRetriever extends BaseConnector {
112
203
  }
113
204
 
114
205
  /**
115
- * Remove all stored documents.
206
+ * Remove all stored documents and reset the vector dimension.
116
207
  * @returns {Promise<void>}
117
208
  */
118
209
  async clear() {
119
210
  this.documents.clear();
211
+ this.dimensions = null;
120
212
  }
121
213
 
122
214
  /**
123
215
  * Get statistics about the store.
124
- * @returns {{ documentCount: number, maxDocuments: number }}
216
+ * @returns {{ documentCount: number, maxDocuments: number, dimensions: number|null }}
125
217
  */
126
218
  getStats() {
127
219
  return {
128
220
  documentCount: this.documents.size,
129
221
  maxDocuments: this.config.maxDocuments,
222
+ dimensions: this.dimensions,
130
223
  };
131
224
  }
132
225
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  const axios = require('axios');
4
4
  const { BaseConnector } = require('./base-connector');
5
+ const {
6
+ raceAbort,
7
+ signalOptions,
8
+ throwIfAborted,
9
+ } = require('./connector-utils');
10
+ const { buildRagPrompt } = require('./rag-prompt');
5
11
 
6
12
  /**
7
13
  * Default configuration for the Ollama connector.
@@ -13,11 +19,14 @@ const DEFAULT_CONFIG = {
13
19
  embeddingModel: 'nomic-embed-text',
14
20
  timeout: 60000,
15
21
  temperature: 0.7,
22
+ maxRetries: 3,
23
+ retryDelay: 1000,
16
24
  };
17
25
 
18
26
  /**
19
27
  * Connector for Ollama local LLM server.
20
28
  * Supports text generation (sync and streaming) and embeddings.
29
+ * Ollama needs no API key.
21
30
  * @extends BaseConnector
22
31
  */
23
32
  class OllamaConnector extends BaseConnector {
@@ -28,6 +37,9 @@ class OllamaConnector extends BaseConnector {
28
37
  * @param {string} [options.embeddingModel='nomic-embed-text'] - Default embedding model
29
38
  * @param {number} [options.timeout=60000] - Request timeout in ms
30
39
  * @param {number} [options.temperature=0.7] - Generation temperature
40
+ * @param {number} [options.maxRetries=3] - Retries for transient failures
41
+ * @param {number} [options.retryDelay=1000] - Base backoff delay in ms
42
+ * @param {string} [options.systemPrompt] - System prompt used when RAG context is supplied
31
43
  */
32
44
  constructor(options = {}) {
33
45
  super({ name: 'ollama', ...options });
@@ -67,6 +79,46 @@ class OllamaConnector extends BaseConnector {
67
79
  }
68
80
  }
69
81
 
82
+ /**
83
+ * POST with the connector's retry policy and cancellation.
84
+ * @private
85
+ */
86
+ _post(path, body, signal) {
87
+ throwIfAborted(signal);
88
+ return this.withRetry(
89
+ async () => {
90
+ const response = await this.client.post(
91
+ path,
92
+ body,
93
+ signal ? { signal } : undefined,
94
+ );
95
+ return response.data;
96
+ },
97
+ this.config.maxRetries,
98
+ this.config.retryDelay,
99
+ { signal },
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Build the /api/generate body. Retrieved context goes into the prompt as
105
+ * delimited, untrusted reference data; instructions go in `system`.
106
+ * @private
107
+ */
108
+ _buildBody(query, context, options, stream) {
109
+ const prompt = buildRagPrompt(query, context, {
110
+ systemPrompt: this.config.systemPrompt,
111
+ });
112
+ const body = {
113
+ model: options.model || this.config.model,
114
+ prompt: prompt.user,
115
+ stream,
116
+ options: { temperature: options.temperature ?? this.config.temperature },
117
+ };
118
+ if (prompt.system) body.system = prompt.system;
119
+ return body;
120
+ }
121
+
70
122
  /**
71
123
  * Generate a response for the given query with optional context documents.
72
124
  * @param {string} query - The user query
@@ -74,24 +126,13 @@ class OllamaConnector extends BaseConnector {
74
126
  * @param {Object} [options={}] - Override options
75
127
  * @param {string} [options.model] - Override the generation model
76
128
  * @param {number} [options.temperature] - Override temperature
129
+ * @param {AbortSignal} [options.signal] - Cancels the request
77
130
  * @returns {Promise<string>} Generated response text
78
131
  */
79
132
  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;
133
+ const body = this._buildBody(query, context, options, false);
134
+ const data = await this._post('/api/generate', body, options.signal);
135
+ return data.response;
95
136
  }
96
137
 
97
138
  /**
@@ -101,31 +142,31 @@ class OllamaConnector extends BaseConnector {
101
142
  * @param {Object} [options={}] - Override options
102
143
  * @param {string} [options.model] - Override the generation model
103
144
  * @param {number} [options.temperature] - Override temperature
145
+ * @param {AbortSignal} [options.signal] - Cancels the stream
104
146
  * @yields {{ token: string, done: boolean }}
105
147
  */
106
148
  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);
149
+ const { signal } = options;
150
+ throwIfAborted(signal);
151
+ const body = this._buildBody(query, context, options, true);
110
152
 
111
153
  let response;
112
154
  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' },
155
+ response = await raceAbort(
156
+ this.client.post('/api/generate', body, {
157
+ responseType: 'stream',
158
+ ...signalOptions(signal),
159
+ }),
160
+ signal,
122
161
  );
123
162
  } catch (err) {
163
+ if (err && err.name === 'AbortError') throw err;
124
164
  throw new Error(`Ollama streaming request failed: ${err.message}`);
125
165
  }
126
166
 
127
167
  let buffer = '';
128
168
  for await (const chunk of response.data) {
169
+ throwIfAborted(signal);
129
170
  buffer += chunk.toString();
130
171
  const lines = buffer.split('\n');
131
172
  // Keep the last potentially incomplete line in the buffer
@@ -157,48 +198,41 @@ class OllamaConnector extends BaseConnector {
157
198
  /**
158
199
  * Generate an embedding vector for the given text.
159
200
  * @param {string} text - Text to embed
201
+ * @param {Object} [options={}]
202
+ * @param {AbortSignal} [options.signal] - Cancels the request
160
203
  * @returns {Promise<number[]>} Embedding vector
161
204
  */
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;
205
+ async embed(text, options = {}) {
206
+ const data = await this._post(
207
+ '/api/embeddings',
208
+ { model: this.config.embeddingModel, prompt: text },
209
+ options.signal,
210
+ );
211
+ return data.embedding;
172
212
  }
173
213
 
174
214
  /**
175
215
  * Generate an embedding vector for a query string.
176
216
  * Alias for {@link embed}.
177
217
  * @param {string} query - Query text to embed
218
+ * @param {Object} [options={}] - Same as embed()
178
219
  * @returns {Promise<number[]>} Embedding vector
179
220
  */
180
- async embedQuery(query) {
181
- return this.embed(query);
221
+ async embedQuery(query, options = {}) {
222
+ return this.embed(query, options);
182
223
  }
183
224
 
184
225
  /**
185
226
  * Build a prompt string incorporating context documents.
227
+ * Kept for backward compatibility; context is delimited and labelled as
228
+ * untrusted reference data.
186
229
  * @param {string} query - The user query
187
230
  * @param {Array<Object|string>} context - Context documents
188
231
  * @returns {string} Formatted prompt
189
232
  * @private
190
233
  */
191
234
  _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:`;
235
+ return buildRagPrompt(query, context).user;
202
236
  }
203
237
  }
204
238