@devilsdev/rag-pipeline-utils 2.4.4 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,364 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Handlers for `rag-pipeline ai ...`. Everything printed here is computed from
5
+ * the configured pipeline, the index or the given files; failures set
6
+ * process.exitCode = 1 and explain why on stderr.
7
+ */
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const fg = require("fast-glob");
12
+
13
+ const { loadCliConfig } = require("../shared/config-context.js");
14
+ const {
15
+ retrain,
16
+ retrieve,
17
+ loadComponents,
18
+ } = require("../services/rag-service.js");
19
+ const { runBenchmark } = require("../services/benchmark.js");
20
+ const {
21
+ feedbackPath,
22
+ loadFeedback,
23
+ addFeedback,
24
+ applyFeedback,
25
+ } = require("../services/feedback.js");
26
+ const { readDocuments } = require("../../loader/document-reader.js");
27
+
28
+ function fail(prefix, error) {
29
+ console.error(`${prefix}: ${error.message}`);
30
+ process.exitCode = 1;
31
+ }
32
+
33
+ const fmt = (ms) => `${ms.toFixed(2)} ms`;
34
+ const signed = (x) => `${x >= 0 ? "+" : "-"}${Math.abs(x).toFixed(3)}`;
35
+
36
+ /**
37
+ * `ai train embedding [path]`
38
+ */
39
+ async function handleTrainEmbedding(globalOptions, inputPath) {
40
+ try {
41
+ const { config, configDir } = loadCliConfig(globalOptions);
42
+ if (globalOptions.dryRun) {
43
+ console.log(
44
+ `Dry run: would fit the local embedder on ${inputPath || "the indexed chunks"} ` +
45
+ "and re-embed the index.",
46
+ );
47
+ return;
48
+ }
49
+ const r = await retrain({ config, configDir, inputPath });
50
+ const source =
51
+ r.trainedOn === "index"
52
+ ? `${r.trainingDocuments} indexed chunk(s)`
53
+ : `${r.trainingDocuments} chunk(s) read from ${r.trainedOn}`;
54
+ console.log(`Trained the local TF-IDF embedder on ${source}.`);
55
+ console.log(
56
+ `Vocabulary: ${r.vocabularySize} term(s)` +
57
+ (r.previousVocabularySize !== null
58
+ ? ` (previously ${r.previousVocabularySize})`
59
+ : ""),
60
+ );
61
+ console.log(
62
+ `Re-embedded ${r.reembedded} indexed chunk(s) with the new model.`,
63
+ );
64
+ if (r.zeroVectors > 0) {
65
+ console.log(
66
+ `Warning: ${r.zeroVectors} indexed chunk(s) share no term with the new ` +
67
+ "vocabulary and can no longer be retrieved.",
68
+ );
69
+ }
70
+ if (r.trainedOn !== "index") {
71
+ console.log(
72
+ "Note: the next `ingest` refits the embedder on the indexed chunks.",
73
+ );
74
+ }
75
+ } catch (error) {
76
+ fail("Training failed", error);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * `ai benchmark`
82
+ */
83
+ async function handleBenchmark(globalOptions, options = {}) {
84
+ try {
85
+ const { config, configDir } = loadCliConfig(globalOptions);
86
+ const report = await runBenchmark({
87
+ config,
88
+ configDir,
89
+ queriesFile: options.queries,
90
+ runs: options.runs || 3,
91
+ topK: options.topK || 3,
92
+ });
93
+
94
+ if (options.json) {
95
+ console.log(JSON.stringify(report, null, 2));
96
+ return;
97
+ }
98
+
99
+ const env = report.environment;
100
+ const n = report.queries.length;
101
+ console.log(
102
+ `Benchmark: ${n} quer${n === 1 ? "y" : "ies"} (${report.queriesSource}) ` +
103
+ `x ${report.runs} run(s), top-k ${report.topK}`,
104
+ );
105
+ console.log(
106
+ `Environment: Node ${env.node} (${env.platform}), ${env.chunks} indexed chunk(s), ` +
107
+ `embedder ${env.embedder}` +
108
+ (env.dimensions ? ` (${env.dimensions} dims)` : "") +
109
+ `, retriever ${env.retriever}, reranker ${env.reranker || "none"}, ` +
110
+ `llm ${env.llm || "none"}`,
111
+ );
112
+ console.log(`Setup (load config and index): ${fmt(report.setupMs)}`);
113
+ console.log(`Warm-up query (excluded): ${fmt(report.warmupMs)}`);
114
+ console.log("");
115
+ const labels = {
116
+ embed: "embed query",
117
+ retrieve: "retrieve",
118
+ rerank: "rerank",
119
+ endToEnd: report.includesGeneration
120
+ ? "end-to-end (incl. llm)"
121
+ : "end-to-end",
122
+ };
123
+ const header = ["stage", "samples", "p50", "p95", "mean", "max"];
124
+ const rows = Object.entries(report.stages).map(([stage, s]) => [
125
+ labels[stage],
126
+ String(s.count),
127
+ fmt(s.p50),
128
+ fmt(s.p95),
129
+ fmt(s.mean),
130
+ fmt(s.max),
131
+ ]);
132
+ const widths = header.map((h, i) =>
133
+ Math.max(h.length, ...rows.map((r) => r[i].length)),
134
+ );
135
+ const line = (cells) =>
136
+ cells
137
+ .map((c, i) => c.padEnd(widths[i]))
138
+ .join(" ")
139
+ .trimEnd();
140
+ console.log(line(header));
141
+ rows.forEach((r) => console.log(line(r)));
142
+ console.log("");
143
+ console.log(
144
+ `Results per query: ${report.results.min === report.results.max ? report.results.min : `${report.results.min}-${report.results.max}`}`,
145
+ );
146
+ console.log("Percentiles are nearest-rank over the measured samples.");
147
+ } catch (error) {
148
+ fail("Benchmark failed", error);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * `ai feedback <question> <docId> --rating up|down`
154
+ */
155
+ async function handleFeedback(globalOptions, question, docId, options = {}) {
156
+ try {
157
+ const { config, configDir } = loadCliConfig(globalOptions);
158
+ const components = loadComponents({ config, configDir });
159
+ const { retriever } = components;
160
+ if (typeof retriever.list !== "function") {
161
+ throw new Error(
162
+ `Retriever "${components.names.retriever}" cannot list documents. Use "file-store".`,
163
+ );
164
+ }
165
+ if (!retriever.list().some((doc) => doc.id === docId)) {
166
+ throw new Error(
167
+ `No indexed chunk with id "${docId}". Ids are shown by \`rag-pipeline ai search\`.`,
168
+ );
169
+ }
170
+ const file = feedbackPath(retriever, configDir);
171
+ if (globalOptions.dryRun) {
172
+ console.log(
173
+ `Dry run: would record a "${options.rating}" vote for ${docId} in ${file}`,
174
+ );
175
+ return;
176
+ }
177
+ const { total } = addFeedback(file, {
178
+ question,
179
+ docId,
180
+ rating: options.rating,
181
+ });
182
+ console.log(
183
+ `Recorded "${options.rating}" for ${docId} on "${question}" ` +
184
+ `(${total} vote(s) in ${path.relative(process.cwd(), file) || file}).`,
185
+ );
186
+ } catch (error) {
187
+ fail("Feedback failed", error);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * `ai search <question>`: retrieval re-scored with stored feedback.
193
+ */
194
+ async function handleSearch(globalOptions, question, options = {}) {
195
+ try {
196
+ const { config, configDir } = loadCliConfig(globalOptions);
197
+ const topK = options.topK || 3;
198
+ const components = loadComponents({ config, configDir });
199
+ const { retriever } = components;
200
+ const size = retriever.size ? retriever.size() : topK;
201
+ // Re-score a larger pool so a boosted chunk can move into the top-k.
202
+ const pool = Math.min(size, Math.max(topK * 4, topK + 10));
203
+ const results = await retrieve({ components, question, topK: pool });
204
+ const file = feedbackPath(retriever, configDir);
205
+ const { entries } = loadFeedback(file);
206
+ const ranked = applyFeedback(results, entries, question)
207
+ .filter((r) => r.baseScore > 0)
208
+ .slice(0, topK);
209
+
210
+ if (options.json) {
211
+ console.log(JSON.stringify({ question, results: ranked }, null, 2));
212
+ return;
213
+ }
214
+ console.log(
215
+ `${entries.length} stored feedback vote(s); retrieval only (no llm).`,
216
+ );
217
+ if (ranked.length === 0) {
218
+ console.log("No matching passages found.");
219
+ return;
220
+ }
221
+ ranked.forEach((r, i) => {
222
+ console.log(
223
+ `\n[${i + 1}] ${r.id} score ${r.finalScore.toFixed(3)} = ` +
224
+ `${r.baseScore.toFixed(3)} retrieval ${signed(r.adjustment)} feedback`,
225
+ );
226
+ for (const reason of r.reasons) {
227
+ console.log(
228
+ ` ${reason.rating} vote on "${reason.question}" ` +
229
+ `(term overlap ${reason.similarity.toFixed(2)}): ${signed(reason.contribution)}`,
230
+ );
231
+ }
232
+ console.log(r.content);
233
+ });
234
+ } catch (error) {
235
+ fail("Search failed", error);
236
+ }
237
+ }
238
+
239
+ const TEXT_TYPES = {
240
+ ".md": "markdown",
241
+ ".markdown": "markdown",
242
+ ".html": "html",
243
+ ".htm": "html",
244
+ ".csv": "csv",
245
+ ".txt": "text",
246
+ };
247
+ const IMAGE_EXTENSIONS = new Set([
248
+ ".png",
249
+ ".jpg",
250
+ ".jpeg",
251
+ ".gif",
252
+ ".webp",
253
+ ".bmp",
254
+ ".tif",
255
+ ".tiff",
256
+ ]);
257
+
258
+ function configuredLlm(globalOptions) {
259
+ try {
260
+ const { config } = loadCliConfig(globalOptions);
261
+ const entry = (config.pipeline || []).find((e) => e.stage === "llm");
262
+ return entry ? entry.name : "none";
263
+ } catch {
264
+ return "none (no readable config)";
265
+ }
266
+ }
267
+
268
+ function imageUnsupportedMessage(globalOptions) {
269
+ return (
270
+ "Image processing needs a vision-capable model, and none is available: the " +
271
+ "built-in llm connectors (openai, anthropic, ollama) only send text prompts. " +
272
+ `Configured llm: ${configuredLlm(globalOptions)}.`
273
+ );
274
+ }
275
+
276
+ async function describeTextFile(file, baseDir) {
277
+ const [doc] = await readDocuments(file, { baseDir });
278
+ return {
279
+ source: doc.source,
280
+ type: TEXT_TYPES[path.extname(file).toLowerCase()],
281
+ characters: fs.readFileSync(file, "utf8").length,
282
+ extractedCharacters: doc.chunks.reduce((n, c) => n + c.length, 0),
283
+ chunks: doc.chunks.length,
284
+ };
285
+ }
286
+
287
+ /**
288
+ * `ai multimodal process <path>`: report what the loaders extract. Does not
289
+ * modify the index.
290
+ */
291
+ async function handleMultimodalProcess(globalOptions, inputPath, options = {}) {
292
+ try {
293
+ const absolute = path.resolve(inputPath);
294
+ if (!fs.existsSync(absolute))
295
+ throw new Error(`Path not found: ${inputPath}`);
296
+
297
+ const files = fs.statSync(absolute).isDirectory()
298
+ ? (
299
+ await fg("**/*", {
300
+ cwd: absolute,
301
+ absolute: true,
302
+ ignore: ["**/node_modules/**", "**/.git/**"],
303
+ })
304
+ ).sort()
305
+ : [absolute];
306
+
307
+ const processed = [];
308
+ const skipped = [];
309
+ for (const file of files) {
310
+ const ext = path.extname(file).toLowerCase();
311
+ const rel = path.relative(process.cwd(), file).split(path.sep).join("/");
312
+ if (TEXT_TYPES[ext]) {
313
+ processed.push(await describeTextFile(file, process.cwd()));
314
+ } else if (IMAGE_EXTENSIONS.has(ext)) {
315
+ if (files.length === 1)
316
+ throw new Error(imageUnsupportedMessage(globalOptions));
317
+ skipped.push({
318
+ source: rel,
319
+ reason: "image (needs a vision-capable model)",
320
+ });
321
+ } else {
322
+ if (files.length === 1) {
323
+ throw new Error(
324
+ `Unsupported file type "${ext}". Supported text types: ${Object.keys(TEXT_TYPES).join(", ")}`,
325
+ );
326
+ }
327
+ skipped.push({ source: rel, reason: `unsupported type "${ext}"` });
328
+ }
329
+ }
330
+
331
+ if (options.json) {
332
+ console.log(JSON.stringify({ processed, skipped }, null, 2));
333
+ } else {
334
+ for (const p of processed) {
335
+ console.log(
336
+ `${p.source}: ${p.type}, ${p.characters} characters, ` +
337
+ `${p.chunks} chunk(s) (${p.extractedCharacters} characters extracted)`,
338
+ );
339
+ }
340
+ for (const s of skipped) console.log(`skipped ${s.source}: ${s.reason}`);
341
+ console.log(
342
+ `Processed ${processed.length} file(s), skipped ${skipped.length}. ` +
343
+ "The index was not modified; use `rag-pipeline ingest` to add files.",
344
+ );
345
+ }
346
+ if (processed.length === 0) {
347
+ throw new Error(
348
+ skipped.some((s) => s.reason.startsWith("image"))
349
+ ? imageUnsupportedMessage(globalOptions)
350
+ : "No supported text files found.",
351
+ );
352
+ }
353
+ } catch (error) {
354
+ fail("Processing failed", error);
355
+ }
356
+ }
357
+
358
+ module.exports = {
359
+ handleTrainEmbedding,
360
+ handleBenchmark,
361
+ handleFeedback,
362
+ handleSearch,
363
+ handleMultimodalProcess,
364
+ };
@@ -6,66 +6,82 @@
6
6
  const fs = require("fs/promises");
7
7
  const { logger } = require("../../utils/logger.js");
8
8
 
9
+ const SEVERITY_ICONS = { error: "❌", warning: "⚠️ ", info: "ℹ️ " };
10
+
11
+ function printIssues(issues) {
12
+ for (const issue of issues) {
13
+ console.log(
14
+ `${SEVERITY_ICONS[issue.severity] || "-"} [${issue.code}] ${issue.message}`,
15
+ );
16
+ if (issue.fix) console.log(` Fix: ${issue.fix}`);
17
+ }
18
+ }
19
+
9
20
  /**
10
- * Handle doctor command
21
+ * Handle doctor command: run the diagnostic checkers and print the issues.
22
+ * Exits 1 when any error-level issue is found.
23
+ *
11
24
  * @param {object} globalOptions - Global CLI options from program.opts()
12
25
  * @param {object} options - Command-specific options
13
- * @param {Function} pipelineDoctorFn - Pipeline doctor function
26
+ * @param {Function} [pipelineDoctorFn] - Injectable for tests
14
27
  */
15
28
  async function handleDoctor(globalOptions, options, pipelineDoctorFn) {
16
29
  try {
17
- const doctorOptions = {
18
- configPath: globalOptions.config,
30
+ const runDoctor =
31
+ pipelineDoctorFn || require("../doctor-command.js").runPipelineDoctor;
32
+ const report = await runDoctor({
33
+ configPath: globalOptions.config || ".ragrc.json",
19
34
  verbose: globalOptions.verbose,
20
35
  autoFix: options.autoFix,
21
- categories: options.category,
22
- };
36
+ categories: options.category || ["all"],
37
+ });
23
38
 
24
- const report = await pipelineDoctorFn(doctorOptions);
39
+ console.log("🏥 Pipeline diagnostics\n");
40
+ if (report.issues.length === 0) {
41
+ console.log("✅ No issues found");
42
+ } else {
43
+ printIssues(report.issues);
44
+ }
45
+ const { errors, warnings, info } = report.summary;
46
+ console.log(`\n${errors} error(s), ${warnings} warning(s), ${info} info`);
25
47
 
26
48
  if (options.report) {
27
49
  await fs.writeFile(options.report, JSON.stringify(report, null, 2));
28
50
  console.log(`📋 Diagnostic report saved to: ${options.report}`);
29
51
  }
52
+ if (errors > 0) process.exitCode = 1;
30
53
  } catch (error) {
31
- logger.error("❌ Doctor command failed:", error.message);
32
- process.exit(1);
54
+ console.error(`❌ Doctor command failed: ${error.message}`);
55
+ process.exitCode = 1;
33
56
  }
34
57
  }
35
58
 
36
59
  /**
37
- * Handle validate command
60
+ * Handle validate command: schema validation plus resolving every pipeline
61
+ * component. Exits 1 when the configuration has errors.
62
+ *
38
63
  * @param {object} globalOptions - Global CLI options from program.opts()
39
- * @param {object} options - Command-specific options
40
- * @param {Function} validateSchemaFn - Schema validation function
41
64
  */
42
- async function handleValidate(globalOptions, options, validateSchemaFn) {
65
+ async function handleValidate(globalOptions) {
43
66
  try {
44
- const configPath = globalOptions.config;
45
-
67
+ const configPath = globalOptions.config || ".ragrc.json";
46
68
  console.log(`🔍 Validating configuration: ${configPath}`);
47
69
 
48
- // Load and validate configuration
49
- const configContent = await fs.readFile(configPath, "utf-8");
50
- const config = JSON.parse(configContent);
70
+ const { checkConfiguration } = require("../diagnostics/config-checker.js");
71
+ const issues = await checkConfiguration({ configPath });
72
+ const errors = issues.filter((issue) => issue.severity === "error");
51
73
 
52
- const validation = validateSchemaFn(config);
53
-
54
- if (validation.valid) {
74
+ if (errors.length === 0) {
55
75
  console.log("✅ Configuration is valid");
56
-
57
- if (validation.legacy) {
58
- console.log("⚠️ Using legacy format - consider upgrading");
59
- }
76
+ printIssues(issues);
60
77
  } else {
61
78
  console.log("❌ Configuration validation failed:");
62
- validation.errors?.forEach((error) => {
63
- console.log(` ${error.instancePath || "root"}: ${error.message}`);
64
- });
79
+ printIssues(issues);
80
+ process.exitCode = 1;
65
81
  }
66
82
  } catch (error) {
67
- logger.error("❌ Validation failed:", error.message);
68
- process.exit(1);
83
+ console.error(`❌ Validation failed: ${error.message}`);
84
+ process.exitCode = 1;
69
85
  }
70
86
  }
71
87
 
@@ -97,13 +113,44 @@ async function handleInfo(globalOptions, options) {
97
113
  );
98
114
  console.log("Configuration:");
99
115
  console.log(` File: ${globalOptions.config}`);
100
- console.log(` Format: ${config.plugins ? "Enhanced" : "Legacy"}`);
116
+ for (const entry of Array.isArray(config.pipeline)
117
+ ? config.pipeline
118
+ : []) {
119
+ console.log(` ${entry.stage}: ${entry.name}`);
120
+ }
101
121
  console.log("");
102
122
  } catch (error) {
103
123
  console.log("Configuration: Not found or invalid");
104
124
  console.log("");
105
125
  }
106
126
  }
127
+
128
+ // Available components: built-ins plus custom plugins from the config
129
+ if ((!options.system && !options.config) || options.plugins) {
130
+ const { BUILT_INS } = require("../../config/component-factory.js");
131
+ console.log("Built-in components:");
132
+ for (const [stage, names] of Object.entries(BUILT_INS)) {
133
+ console.log(` ${stage}: ${Object.keys(names).join(", ")}`);
134
+ }
135
+ let custom = {};
136
+ try {
137
+ custom =
138
+ JSON.parse(await fs.readFile(globalOptions.config, "utf-8"))
139
+ .plugins || {};
140
+ } catch {
141
+ // no or invalid config: built-ins only
142
+ }
143
+ const entries = Object.entries(custom).flatMap(([stage, plugins]) =>
144
+ Object.entries(plugins || {}).map(
145
+ ([name, modulePath]) => ` ${stage}/${name}: ${modulePath}`,
146
+ ),
147
+ );
148
+ if (entries.length > 0) {
149
+ console.log("Custom plugins:");
150
+ entries.forEach((line) => console.log(line));
151
+ }
152
+ console.log("");
153
+ }
107
154
  } catch (error) {
108
155
  logger.error("❌ Failed to get system information:", error.message);
109
156
  process.exit(1);
@@ -1,41 +1,47 @@
1
1
  /**
2
2
  * Ingest command handler
3
- * Handles document ingestion into the pipeline
3
+ * Loads documents, embeds their chunks and stores them in the configured
4
+ * retriever (file-store persists them for later `query` runs).
4
5
  */
5
6
 
6
- const { logger } = require("../../utils/logger.js");
7
+ const { loadCliConfig } = require("../shared/config-context.js");
8
+ const { ingest } = require("../services/rag-service.js");
7
9
 
8
10
  /**
9
- * Handle ingest command
10
11
  * @param {object} globalOptions - Global CLI options from program.opts()
11
- * @param {string} file - Document file path
12
+ * @param {string} inputPath - File or directory to ingest
12
13
  * @param {object} options - Command-specific options
13
14
  */
14
- async function handleIngest(globalOptions, file, options) {
15
+ async function handleIngest(globalOptions, inputPath, options = {}) {
15
16
  try {
16
- if (globalOptions.dryRun) {
17
- console.log("🧪 Dry run: Would ingest document");
18
- console.log(`File: ${file}`);
19
- console.log(
20
- `Parallel processing: ${options.parallel ? "enabled" : "disabled"}`,
21
- );
22
- console.log(`Streaming: ${options.streaming ? "enabled" : "disabled"}`);
23
- console.log(`Tracing: ${options.trace ? "enabled" : "disabled"}`);
17
+ const { config, configDir } = loadCliConfig(globalOptions);
24
18
 
25
- if (options.preview) {
26
- console.log("📄 Document Preview:");
27
- }
28
- if (options.validate) {
29
- console.log("File validation passed");
19
+ if (globalOptions.dryRun) {
20
+ const { readDocuments } = require("../../loader/document-reader.js");
21
+ const documents = await readDocuments(inputPath, { baseDir: configDir });
22
+ console.log(`🧪 Dry run: would ingest ${documents.length} file(s)`);
23
+ for (const doc of documents) {
24
+ console.log(` ${doc.source} (${doc.chunks.length} chunks)`);
30
25
  }
31
26
  return;
32
27
  }
33
28
 
34
- console.log(`📄 Ingesting document: ${file}`);
35
- console.log("✅ Document ingested successfully!");
29
+ const summary = await ingest({
30
+ config,
31
+ configDir,
32
+ inputPath,
33
+ rebuild: Boolean(options.rebuild),
34
+ });
35
+ console.log(
36
+ `✅ Ingested ${summary.files} file(s), ${summary.chunks} chunk(s). ` +
37
+ `Index now holds ${summary.totalChunks} chunk(s).`,
38
+ );
39
+ if (globalOptions.verbose) {
40
+ for (const source of summary.sources) console.log(` ${source}`);
41
+ }
36
42
  } catch (error) {
37
- logger.error("❌ Document ingestion failed:", error.message);
38
- process.exit(1);
43
+ console.error(`❌ Ingestion failed: ${error.message}`);
44
+ process.exitCode = 1;
39
45
  }
40
46
  }
41
47