@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,51 +1,174 @@
1
1
  /**
2
2
  * Init command handler
3
- * Handles RAG pipeline configuration initialization
3
+ * Writes a .ragrc.json whose pipeline names built-in components (or custom
4
+ * plugins checked by the setup wizard), so the result works with
5
+ * `ingest` / `query` immediately.
4
6
  */
5
7
 
6
8
  const fs = require("fs/promises");
7
- const { logger } = require("../../utils/logger.js");
9
+ const { buildInitConfig } = require("../wizard/config-builder.js");
10
+ const { runInitWizard, ask } = require("../wizard/init-wizard.js");
11
+ const {
12
+ buildComponents: defaultBuildComponents,
13
+ } = require("../../config/component-factory");
14
+
15
+ /**
16
+ * Starting points for `init --template <name>`. "local" needs no API keys or
17
+ * services; it has no llm stage, so `query` prints the retrieved passages.
18
+ * In interactive mode the template only sets the wizard's defaults.
19
+ */
20
+ const TEMPLATES = {
21
+ local: { embedder: "local", llm: null },
22
+ openai: { embedder: "openai", llm: "openai" },
23
+ ollama: { embedder: "ollama", llm: "ollama" },
24
+ anthropic: { embedder: "local", llm: "anthropic" },
25
+ };
26
+
27
+ /**
28
+ * inquirer prompt function. With custom streams it uses a prompt module
29
+ * bound to them (inquirer@8 reads `{input, output}` from createPromptModule,
30
+ * not from prompt()'s second argument, which holds pre-filled answers).
31
+ */
32
+ function inquirerPrompt({ input, output } = {}) {
33
+ const inquirer = require("inquirer");
34
+ if (!input && !output) return (questions) => inquirer.prompt(questions);
35
+ const promptModule = inquirer.createPromptModule({ input, output });
36
+ return (questions) => promptModule(questions);
37
+ }
38
+
39
+ /** Grace period before the CLI forces an exit after a connection test. */
40
+ const EXIT_GRACE_MS = 1000;
41
+
42
+ /**
43
+ * Last-resort exit for `rag-pipeline init` after a connection test ran.
44
+ *
45
+ * The test aborts each provider call on timeout (connectors that honour
46
+ * `options.signal` cancel their request), but a custom plugin or a provider
47
+ * SDK may still hold a socket or timer open, which would keep the process
48
+ * alive after "Configuration written". The exit timer is unref'd: when
49
+ * nothing is left running the process ends normally and the timer never
50
+ * fires; otherwise it exits with the current exit code after the grace
51
+ * period. Only the CLI command calls this, never the handler itself.
52
+ *
53
+ * @param {{connectionTested?: boolean}|undefined} result - handleInit result
54
+ * @param {object} [options]
55
+ * @param {number} [options.graceMs=1000]
56
+ * @param {Function} [options.exit=process.exit]
57
+ * @returns {NodeJS.Timeout|null} The scheduled timer, or null
58
+ */
59
+ function exitAfterConnectionTest(result, options = {}) {
60
+ if (!result || !result.connectionTested) return null;
61
+ const { graceMs = EXIT_GRACE_MS, exit = (code) => process.exit(code) } =
62
+ options;
63
+ const timer = setTimeout(() => exit(process.exitCode ?? 0), graceMs);
64
+ if (typeof timer.unref === "function") timer.unref();
65
+ return timer;
66
+ }
8
67
 
9
68
  /**
10
69
  * Handle init command
11
70
  * @param {object} globalOptions - Global CLI options from program.opts()
12
71
  * @param {object} options - Command-specific options
13
- * @param {Function} createBasicConfigFn - Function to create basic config
72
+ * @param {object} [deps] - Injectable dependencies for tests
73
+ * @param {Function} [deps.prompt] - inquirer-style prompt([question]) => answers
74
+ * @param {object} [deps.env] - Environment for the API key check
75
+ * @param {Function} [deps.buildComponents] - Used by the connection test
76
+ * @param {Function} [deps.log] - Wizard output (console.log)
77
+ * @param {number} [deps.timeoutMs] - Connection test timeout
78
+ * @param {import("stream").Readable} [deps.input] - Prompt input (process.stdin)
79
+ * @param {import("stream").Writable} [deps.output] - Prompt output (process.stdout)
80
+ * @returns {Promise<{written: boolean, connectionTested: boolean}>}
81
+ * connectionTested is true when the connection test built a provider
14
82
  */
15
- async function handleInit(globalOptions, options, createBasicConfigFn) {
83
+ async function handleInit(globalOptions, options, deps = {}) {
84
+ const outcome = { written: false, connectionTested: false };
16
85
  try {
86
+ const templateName = options.template || "local";
87
+ const template = TEMPLATES[templateName];
88
+ if (!template) {
89
+ throw new Error(
90
+ `Unknown template "${templateName}". Available: ${Object.keys(TEMPLATES).join(", ")}`,
91
+ );
92
+ }
93
+
94
+ // commander sets options.interactive=false for --no-interactive; only
95
+ // prompt when a person is at the terminal.
96
+ const interactive =
97
+ options.interactive !== false &&
98
+ Boolean((deps.input || process.stdin).isTTY);
99
+
17
100
  if (globalOptions.dryRun) {
18
- console.log("🧪 Dry run: Would initialize RAG pipeline configuration");
101
+ console.log("🧪 Dry run: would write configuration");
19
102
  console.log(`Output file: ${options.output}`);
20
- console.log(`Interactive mode: ${!options.noInteractive}`);
21
- return;
103
+ console.log(`Template: ${templateName}`);
104
+ console.log(`Interactive: ${interactive}`);
105
+ return outcome;
22
106
  }
23
107
 
24
- // Check if config already exists
25
- if (!options.force) {
26
- try {
27
- await fs.access(options.output);
28
- console.log(`❌ Configuration file already exists: ${options.output}`);
29
- console.log(
30
- "Use --force to overwrite or choose a different output path.",
108
+ const prompt =
109
+ deps.prompt || inquirerPrompt({ input: deps.input, output: deps.output });
110
+ const exists = await fs
111
+ .access(options.output)
112
+ .then(() => true)
113
+ .catch(() => false);
114
+ if (exists && !options.force) {
115
+ if (!interactive) {
116
+ throw new Error(
117
+ `Configuration file already exists: ${options.output}. Use --force to overwrite.`,
31
118
  );
32
- return;
33
- } catch (error) {
34
- // File doesn't exist, which is good
119
+ }
120
+ const overwrite = await ask(prompt, {
121
+ type: "confirm",
122
+ name: "overwrite",
123
+ message: `${options.output} already exists. Replace it?`,
124
+ default: false,
125
+ });
126
+ if (!overwrite) {
127
+ console.log("Nothing written.");
128
+ return outcome;
35
129
  }
36
130
  }
37
131
 
38
- if (!options.noInteractive) {
39
- console.log("🧙‍♂️ Starting interactive configuration wizard...\n");
40
- // Mock interactive wizard
41
- await createBasicConfigFn(options.output);
132
+ let config;
133
+ if (interactive) {
134
+ const result = await runInitWizard({
135
+ prompt,
136
+ defaults: template,
137
+ outputPath: options.output,
138
+ overwrite: exists,
139
+ env: deps.env || process.env,
140
+ // Only the connection test builds components; note that it did, so
141
+ // the CLI can make sure no provider request keeps the process alive.
142
+ buildComponents: (...args) => {
143
+ outcome.connectionTested = true;
144
+ return (deps.buildComponents || defaultBuildComponents)(...args);
145
+ },
146
+ log: deps.log || console.log,
147
+ timeoutMs: deps.timeoutMs,
148
+ });
149
+ if (!result) {
150
+ console.log("Nothing written.");
151
+ return outcome;
152
+ }
153
+ config = result.config;
42
154
  } else {
43
- await createBasicConfigFn(options.output);
155
+ config = buildInitConfig(template);
44
156
  }
157
+
158
+ await fs.writeFile(options.output, JSON.stringify(config, null, 2) + "\n");
159
+ console.log(`✅ Configuration written: ${options.output}`);
160
+ console.log("Next: rag-pipeline ingest <file-or-directory>");
161
+ outcome.written = true;
45
162
  } catch (error) {
46
- logger.error("❌ Initialization failed:", error.message);
47
- process.exit(1);
163
+ console.error(`❌ Initialization failed: ${error.message}`);
164
+ process.exitCode = 1;
48
165
  }
166
+ return outcome;
49
167
  }
50
168
 
51
- module.exports = { handleInit };
169
+ module.exports = {
170
+ handleInit,
171
+ exitAfterConnectionTest,
172
+ buildInitConfig,
173
+ TEMPLATES,
174
+ };
@@ -1,45 +1,223 @@
1
1
  /**
2
2
  * Query command handler
3
- * Handles RAG pipeline queries
3
+ * Answers a question from the ingested index. Without an llm stage it prints
4
+ * the retrieved passages instead of an answer.
5
+ *
6
+ * Options: --top-k, --json, --streaming, --timeout, --trace (per-stage
7
+ * timings), --explain (passage scores and the passages supporting each
8
+ * answer sentence), --stats (latency, estimated tokens and cost). The global
9
+ * --quiet prints only the answer or the passages.
4
10
  */
5
11
 
6
- const { logger } = require("../../utils/logger.js");
12
+ const { loadCliConfig } = require("../shared/config-context.js");
13
+ const { query } = require("../services/rag-service.js");
14
+ const { CostCalculator } = require("../../cost/cost-calculator.js");
15
+
16
+ const STAGES = ["embed", "retrieve", "rerank", "generate"];
17
+
18
+ function sourceLabel(doc) {
19
+ return doc.metadata?.source || doc.id;
20
+ }
21
+
22
+ function printPassages(results, { quiet } = {}) {
23
+ // A score of 0 means no overlap with the question at all.
24
+ const matches = results.filter((doc) => !(doc.score <= 0));
25
+ if (matches.length === 0) {
26
+ if (!quiet) console.log("No matching passages found.");
27
+ return;
28
+ }
29
+ matches.forEach((doc, i) => {
30
+ const score = typeof doc.score === "number" ? doc.score.toFixed(3) : "-";
31
+ console.log(`\n[${i + 1}] ${sourceLabel(doc)} (score ${score})`);
32
+ console.log(doc.content);
33
+ });
34
+ }
35
+
36
+ /** Sum the recorded timings per stage. */
37
+ function stageTimings(timings) {
38
+ const totals = {};
39
+ for (const { stage, ms } of timings)
40
+ totals[stage] = (totals[stage] || 0) + ms;
41
+ return totals;
42
+ }
43
+
44
+ function printTrace(result) {
45
+ const totals = stageTimings(result.timings);
46
+ console.log("\nTrace");
47
+ for (const stage of STAGES) {
48
+ const ms = totals[stage];
49
+ console.log(
50
+ ` ${stage.padEnd(9)} ${ms === undefined ? "-" : `${ms.toFixed(1)} ms`}`,
51
+ );
52
+ }
53
+ console.log(` ${"total".padEnd(9)} ${result.durationMs.toFixed(1)} ms`);
54
+ }
55
+
56
+ /** Rough token count: about 4 characters per token for English text. */
57
+ const estimateTokens = (text) => Math.ceil(String(text || "").length / 4);
58
+
59
+ function computeStats(result, question, llmModel) {
60
+ const context = result.results.map((d) => d.content || "").join("\n");
61
+ const stats = {
62
+ durationMs: Math.round(result.durationMs),
63
+ passages: result.results.length,
64
+ answerChars: result.answer ? result.answer.length : 0,
65
+ };
66
+ if (result.answer !== null) {
67
+ stats.estimatedInputTokens = estimateTokens(`${question}\n${context}`);
68
+ stats.estimatedOutputTokens = estimateTokens(result.answer);
69
+ const calculator = new CostCalculator();
70
+ // Only price models in the table; unknown models would print a warning.
71
+ if (llmModel && calculator.getModelPricing(llmModel)) {
72
+ const cost = calculator.estimate(
73
+ llmModel,
74
+ stats.estimatedInputTokens,
75
+ stats.estimatedOutputTokens,
76
+ );
77
+ stats.estimatedCostUsd = Number(cost.totalCost.toFixed(6));
78
+ stats.model = cost.model;
79
+ }
80
+ }
81
+ return stats;
82
+ }
83
+
84
+ function printStats(stats) {
85
+ console.log("\nStats");
86
+ console.log(` latency ${stats.durationMs} ms`);
87
+ console.log(` passages ${stats.passages}`);
88
+ if (stats.estimatedInputTokens !== undefined) {
89
+ console.log(
90
+ ` tokens ~${stats.estimatedInputTokens} in, ~${stats.estimatedOutputTokens} out (estimated)`,
91
+ );
92
+ }
93
+ if (stats.estimatedCostUsd !== undefined) {
94
+ console.log(
95
+ ` cost ~$${stats.estimatedCostUsd} (${stats.model}, estimated)`,
96
+ );
97
+ }
98
+ }
99
+
100
+ /** Passage rank and score, and the passages that support each sentence. */
101
+ function explanation(result) {
102
+ const passages = result.results.map((doc, i) => ({
103
+ rank: i + 1,
104
+ source: sourceLabel(doc),
105
+ score: typeof doc.score === "number" ? doc.score : null,
106
+ }));
107
+ if (!result.citations) return { passages };
108
+ const rankOf = new Map(result.results.map((d, i) => [String(d.id), i + 1]));
109
+ const sentences = (result.citations.citations || []).map((c) => ({
110
+ sentence: c.sentence,
111
+ supportedBy: (c.sources || []).map((s) => ({
112
+ rank: rankOf.get(String(s.docId)) ?? s.docIndex + 1,
113
+ source: sourceLabel(result.results[s.docIndex] || { id: s.docId }),
114
+ support: s.score,
115
+ })),
116
+ }));
117
+ return {
118
+ passages,
119
+ sentences,
120
+ groundedness: result.citations.groundednessScore,
121
+ };
122
+ }
123
+
124
+ function printExplanation(explained) {
125
+ console.log("\nExplanation");
126
+ console.log(" Passages, in ranked order:");
127
+ for (const p of explained.passages) {
128
+ const score = p.score === null ? "-" : p.score.toFixed(3);
129
+ console.log(` [${p.rank}] ${p.source} score ${score}`);
130
+ }
131
+ if (!explained.sentences) return;
132
+ console.log(" Answer sentences:");
133
+ for (const s of explained.sentences) {
134
+ const support = s.supportedBy.length
135
+ ? s.supportedBy
136
+ .map((p) => `[${p.rank}] ${p.source} (${p.support.toFixed(2)})`)
137
+ .join(", ")
138
+ : "not supported by any passage";
139
+ console.log(` "${s.sentence}"`);
140
+ console.log(` -> ${support}`);
141
+ }
142
+ console.log(` Groundedness: ${explained.groundedness.toFixed(2)}`);
143
+ }
7
144
 
8
145
  /**
9
- * Handle query command
10
146
  * @param {object} globalOptions - Global CLI options from program.opts()
11
- * @param {string} prompt - Query prompt
147
+ * @param {string} question - Query text
12
148
  * @param {object} options - Command-specific options
13
149
  */
14
- async function handleQuery(globalOptions, prompt, options) {
150
+ async function handleQuery(globalOptions, question, options = {}) {
15
151
  try {
152
+ const { config, configDir } = loadCliConfig(globalOptions);
153
+ const topK = options.topK || 3;
154
+ const quiet = Boolean(globalOptions.quiet);
155
+
16
156
  if (globalOptions.dryRun) {
17
- console.log("🧪 Dry run: Would execute query");
157
+ console.log(`🧪 Dry run: would query the index for: ${question}`);
158
+ return;
159
+ }
160
+
161
+ const hasLlm = (config.pipeline || []).some((s) => s.stage === "llm");
162
+ const streaming = Boolean(options.streaming && hasLlm && !options.json);
163
+ if (options.streaming && !hasLlm && !quiet && !options.json) {
164
+ console.log("--streaming needs an llm stage; showing passages instead.");
165
+ }
166
+
167
+ const result = await query({
168
+ config,
169
+ configDir,
170
+ question,
171
+ topK,
172
+ timeout: options.timeout,
173
+ ...(streaming && { onToken: (t) => process.stdout.write(t) }),
174
+ });
175
+
176
+ const llmStage = (config.pipeline || []).find((s) => s.stage === "llm");
177
+ const stats = options.stats
178
+ ? computeStats(result, question, llmStage?.config?.model)
179
+ : undefined;
180
+ const explained = options.explain ? explanation(result) : undefined;
181
+
182
+ if (options.json) {
183
+ const { timings, durationMs, ...rest } = result;
18
184
  console.log(
19
- `Prompt: ${prompt.substring(0, 100)}${prompt.length > 100 ? "..." : ""}`,
185
+ JSON.stringify(
186
+ {
187
+ ...rest,
188
+ ...(options.trace && {
189
+ trace: { stages: stageTimings(timings), totalMs: durationMs },
190
+ }),
191
+ ...(stats && { stats }),
192
+ ...(explained && { explanation: explained }),
193
+ },
194
+ null,
195
+ 2,
196
+ ),
20
197
  );
21
- console.log(`Streaming: ${options.stream ? "enabled" : "disabled"}`);
22
-
23
- if (options.explain) {
24
- console.log("🔍 Query Explanation:");
25
- console.log("Processing steps:");
26
- console.log(" 1. Text embedding generation");
27
- console.log(" 2. Vector similarity search");
28
- console.log(" 3. Context retrieval");
29
- console.log(" 4. Language model generation");
30
- }
31
198
  return;
32
199
  }
33
200
 
34
- console.log(
35
- `🤔 Processing query: ${prompt.substring(0, 100)}${prompt.length > 100 ? "..." : ""}`,
36
- );
37
- console.log("\n📝 Response:");
38
- console.log("Mock response to query");
201
+ if (result.answer === null) {
202
+ if (!quiet)
203
+ console.log("No llm stage configured; showing retrieved passages.");
204
+ printPassages(result.results, { quiet });
205
+ } else {
206
+ if (streaming) process.stdout.write("\n");
207
+ else console.log(result.answer);
208
+ if (!quiet) {
209
+ console.log("\nSources:");
210
+ printPassages(result.results);
211
+ }
212
+ }
213
+
214
+ if (options.explain) printExplanation(explained);
215
+ if (options.trace) printTrace(result);
216
+ if (options.stats) printStats(stats);
39
217
  } catch (error) {
40
- logger.error("❌ Query processing failed:", error.message);
41
- process.exit(1);
218
+ console.error(`❌ Query failed: ${error.message}`);
219
+ process.exitCode = 1;
42
220
  }
43
221
  }
44
222
 
45
- module.exports = { handleQuery };
223
+ module.exports = { handleQuery, explanation, computeStats };
@@ -1,180 +1,29 @@
1
1
  /**
2
2
  * Run command handler
3
- * Handles pipeline execution with streaming, abort, and progress support
3
+ * Convenience wrapper: optionally ingest, then optionally query, in one call.
4
4
  */
5
5
 
6
- const { logger } = require("../../utils/logger.js");
7
- const { createProgressBar } = require("../shared/cli-utils.js");
6
+ const { handleIngest } = require("./ingest-handler.js");
7
+ const { handleQuery } = require("./query-handler.js");
8
8
 
9
9
  /**
10
- * Handle run command
11
10
  * @param {object} globalOptions - Global CLI options from program.opts()
12
- * @param {object} options - Command-specific options
11
+ * @param {object} options - { ingest?: string, query?: string, topK?: number, json?: boolean }
13
12
  */
14
- async function handleRun(globalOptions, options) {
15
- if (globalOptions.dryRun) {
16
- logger.info("🧪 Dry run: Would execute pipeline operation");
17
- if (options.ingest) logger.info(`Would ingest: ${options.ingest}`);
18
- if (options.query)
19
- logger.info(`Would query: ${options.query.substring(0, 100)}...`);
20
- logger.info(`Streaming: ${options.stream ? "enabled" : "disabled"}`);
21
- logger.info(`Parallel: ${options.parallel ? "enabled" : "disabled"}`);
22
- if (options.abortTimeout)
23
- logger.info(`Abort timeout: ${options.abortTimeout}ms`);
13
+ async function handleRun(globalOptions, options = {}) {
14
+ if (!options.ingest && !options.query) {
15
+ console.error(
16
+ "❌ Nothing to do: pass --ingest <path> and/or --query <text>",
17
+ );
18
+ process.exitCode = 1;
24
19
  return;
25
20
  }
26
-
27
- try {
28
- // Load configuration
29
- const { loadRagConfig } = require("../../config/load-config.js");
30
- const config = await loadRagConfig(globalOptions.config);
31
-
32
- // Create AbortController for timeout handling
33
- const abortController = new AbortController();
34
- let timeoutId;
35
-
36
- if (options.abortTimeout) {
37
- timeoutId = setTimeout(() => {
38
- logger.warn(
39
- `Operation aborted after ${options.abortTimeout}ms timeout`,
40
- );
41
- abortController.abort();
42
- }, options.abortTimeout);
43
- }
44
-
45
- // Setup SIGINT handling for clean shutdown
46
- const sigintHandler = () => {
47
- logger.info("Received SIGINT, aborting operation...");
48
- abortController.abort();
49
- if (timeoutId) clearTimeout(timeoutId);
50
- };
51
- process.once("SIGINT", sigintHandler);
52
-
53
- try {
54
- // Create pipeline with performance options
55
- const { createRagPipeline } = require("../../core/create-pipeline.js");
56
- const pipeline = createRagPipeline(config.pipeline[0], {
57
- useParallelProcessing: options.parallel,
58
- useStreamingSafeguards: options.stream,
59
- useLogging: true,
60
- useRetry: true,
61
- });
62
-
63
- // Execute operation based on options
64
- if (options.ingest) {
65
- logger.info(`Starting ingestion of: ${options.ingest}`);
66
-
67
- if (options.stream) {
68
- let lastProgressTime = Date.now();
69
- for await (const progress of pipeline.ingestStream(options.ingest)) {
70
- if (abortController.signal.aborted) {
71
- logger.info("Ingestion aborted");
72
- break;
73
- }
74
-
75
- const now = Date.now();
76
- if (progress.type === "chunk_processed") {
77
- // Show progress ticks every 500ms or every 10 chunks
78
- if (
79
- now - lastProgressTime > 500 ||
80
- progress.progress.processed % 10 === 0
81
- ) {
82
- const percentage = Math.round(
83
- (progress.progress.processed / progress.progress.total) * 100,
84
- );
85
- const progressBar = createProgressBar(percentage, 20);
86
- process.stdout.write(
87
- `\r${progressBar} ${percentage}% (${progress.progress.processed}/${progress.progress.total}) `,
88
- );
89
- lastProgressTime = now;
90
- }
91
- } else if (progress.type === "ingest_complete") {
92
- process.stdout.write("\n");
93
- logger.info(
94
- `✅ Ingestion complete: ${progress.summary.processedChunks}/${progress.summary.totalChunks} chunks processed`,
95
- );
96
- if (progress.summary.failedChunks > 0) {
97
- logger.warn(
98
- `⚠️ ${progress.summary.failedChunks} chunks failed`,
99
- );
100
- }
101
- } else if (progress.type === "chunk_failed") {
102
- process.stdout.write("✗");
103
- }
104
- }
105
- } else {
106
- // Non-streaming ingestion with progress callback
107
- let currentStage = "";
108
- const progressCallback = (progress) => {
109
- if (progress.stage !== currentStage) {
110
- if (currentStage) process.stdout.write("\n");
111
- currentStage = progress.stage;
112
- logger.info(
113
- `${progress.stage}: ${progress.message || "Processing..."}`,
114
- );
115
- }
116
- if (
117
- progress.completed !== undefined &&
118
- progress.total !== undefined
119
- ) {
120
- const percentage = Math.round(
121
- (progress.completed / progress.total) * 100,
122
- );
123
- const progressBar = createProgressBar(percentage, 20);
124
- process.stdout.write(
125
- `\r${progressBar} ${percentage}% (${progress.completed}/${progress.total}) `,
126
- );
127
- }
128
- };
129
-
130
- const pipelineWithProgress = createRagPipeline(config.pipeline[0], {
131
- useParallelProcessing: options.parallel,
132
- useStreamingSafeguards: false,
133
- useLogging: true,
134
- useRetry: true,
135
- onProgress: progressCallback,
136
- });
137
-
138
- await pipelineWithProgress.ingest(options.ingest);
139
- process.stdout.write("\n");
140
- logger.info("✅ Ingestion completed successfully");
141
- }
142
- }
143
-
144
- if (options.query) {
145
- logger.info(`Executing query: ${options.query.substring(0, 100)}...`);
146
-
147
- if (options.stream) {
148
- process.stdout.write("\nResponse: ");
149
- for await (const token of pipeline.queryStream(options.query)) {
150
- if (abortController.signal.aborted) {
151
- logger.info("\nQuery aborted");
152
- break;
153
- }
154
- process.stdout.write(token);
155
- }
156
- process.stdout.write("\n\n");
157
- } else {
158
- const response = await pipeline.query(options.query);
159
- logger.info("Query completed successfully");
160
- console.log("\nResponse:", response);
161
- }
162
- }
163
-
164
- if (!options.ingest && !options.query) {
165
- logger.error(
166
- "No operation specified. Use --ingest <file> or --query <prompt>",
167
- );
168
- process.exit(1);
169
- }
170
- } finally {
171
- // Cleanup
172
- process.removeListener("SIGINT", sigintHandler);
173
- if (timeoutId) clearTimeout(timeoutId);
174
- }
175
- } catch (error) {
176
- logger.error("Pipeline operation failed", { error: error.message });
177
- process.exit(1);
21
+ if (options.ingest) {
22
+ await handleIngest(globalOptions, options.ingest, options);
23
+ if (process.exitCode) return;
24
+ }
25
+ if (options.query) {
26
+ await handleQuery(globalOptions, options.query, options);
178
27
  }
179
28
  }
180
29