@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,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,44 +1,59 @@
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.
4
5
  */
5
6
 
6
- const { logger } = require("../../utils/logger.js");
7
+ const { loadCliConfig } = require("../shared/config-context.js");
8
+ const { query } = require("../services/rag-service.js");
9
+
10
+ function printPassages(results) {
11
+ // A score of 0 means no overlap with the question at all.
12
+ const matches = results.filter((doc) => !(doc.score <= 0));
13
+ if (matches.length === 0) {
14
+ console.log("No matching passages found.");
15
+ return;
16
+ }
17
+ matches.forEach((doc, i) => {
18
+ const source = doc.metadata?.source || doc.id;
19
+ const score = typeof doc.score === "number" ? doc.score.toFixed(3) : "-";
20
+ console.log(`\n[${i + 1}] ${source} (score ${score})`);
21
+ console.log(doc.content);
22
+ });
23
+ }
7
24
 
8
25
  /**
9
- * Handle query command
10
26
  * @param {object} globalOptions - Global CLI options from program.opts()
11
- * @param {string} prompt - Query prompt
27
+ * @param {string} question - Query text
12
28
  * @param {object} options - Command-specific options
13
29
  */
14
- async function handleQuery(globalOptions, prompt, options) {
30
+ async function handleQuery(globalOptions, question, options = {}) {
15
31
  try {
32
+ const { config, configDir } = loadCliConfig(globalOptions);
33
+ const topK = options.topK || 3;
34
+
16
35
  if (globalOptions.dryRun) {
17
- console.log("🧪 Dry run: Would execute query");
18
- console.log(
19
- `Prompt: ${prompt.substring(0, 100)}${prompt.length > 100 ? "..." : ""}`,
20
- );
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
- }
36
+ console.log(`🧪 Dry run: would query the index for: ${question}`);
31
37
  return;
32
38
  }
33
39
 
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");
40
+ const result = await query({ config, configDir, question, topK });
41
+
42
+ if (options.json) {
43
+ console.log(JSON.stringify(result, null, 2));
44
+ return;
45
+ }
46
+
47
+ if (result.answer === null) {
48
+ console.log("No llm stage configured; showing retrieved passages.");
49
+ } else {
50
+ console.log(result.answer);
51
+ console.log("\nSources:");
52
+ }
53
+ printPassages(result.results);
39
54
  } catch (error) {
40
- logger.error("❌ Query processing failed:", error.message);
41
- process.exit(1);
55
+ console.error(`❌ Query failed: ${error.message}`);
56
+ process.exitCode = 1;
42
57
  }
43
58
  }
44
59
 
@@ -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
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Serve command handler
3
+ * Runs DevServer: a local HTTP API (GET /health, POST /query) over the
4
+ * pipeline in .ragrc.json, reloading custom plugin modules on change.
5
+ */
6
+
7
+ const path = require("path");
8
+ const { loadCliConfig } = require("../shared/config-context.js");
9
+
10
+ /**
11
+ * @param {object} globalOptions - Global CLI options from program.opts()
12
+ * @param {object} options - { port, host }
13
+ * @returns {Promise<object|undefined>} The started server (for tests)
14
+ */
15
+ async function handleServe(globalOptions, options = {}) {
16
+ try {
17
+ // Validate the config up front so a broken file fails at startup.
18
+ const { config, configPath, configDir } = loadCliConfig(globalOptions);
19
+ const pluginFiles = Object.values(config.plugins || {})
20
+ .flatMap((group) => Object.values(group || {}))
21
+ .filter((p) => typeof p === "string")
22
+ .map((p) => path.resolve(configDir, p));
23
+
24
+ const { DevServer } = require("../../dev/dev-server.js");
25
+ const server = new DevServer({
26
+ port: options.port,
27
+ host: options.host,
28
+ configPath,
29
+ watchPaths: pluginFiles,
30
+ verbose: false,
31
+ });
32
+ await server.start();
33
+
34
+ console.log(`🚀 Serving ${path.basename(configPath)} at ${server.url}`);
35
+ console.log(` GET ${server.url}/health`);
36
+ console.log(` POST ${server.url}/query {"question": "...", "topK": 3}`);
37
+ if (
38
+ !["127.0.0.1", "localhost", "::1"].includes(options.host || "127.0.0.1")
39
+ ) {
40
+ console.warn(
41
+ `⚠️ Listening on ${options.host}: the API has no authentication and anyone who can reach this address can query your index.`,
42
+ );
43
+ }
44
+ console.log("Press Ctrl+C to stop.");
45
+
46
+ const shutdown = async () => {
47
+ await server.stop();
48
+ process.exit(0);
49
+ };
50
+ process.once("SIGINT", shutdown);
51
+ process.once("SIGTERM", shutdown);
52
+ return server;
53
+ } catch (error) {
54
+ console.error(`❌ Server failed to start: ${error.message}`);
55
+ process.exitCode = 1;
56
+ return undefined;
57
+ }
58
+ }
59
+
60
+ module.exports = { handleServe };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Summary statistics for latency samples measured by `rag-pipeline ai
5
+ * benchmark`. Pure functions so the math can be unit-tested.
6
+ */
7
+
8
+ /**
9
+ * Nearest-rank percentile: the smallest sample such that at least p% of the
10
+ * samples are less than or equal to it. Always returns an observed value.
11
+ *
12
+ * @param {number[]} samples - Unsorted samples (not modified)
13
+ * @param {number} p - Percentile in (0, 100]
14
+ * @returns {number}
15
+ */
16
+ function percentile(samples, p) {
17
+ if (!Array.isArray(samples) || samples.length === 0) {
18
+ throw new Error("percentile() needs at least one sample");
19
+ }
20
+ if (!(p > 0 && p <= 100)) {
21
+ throw new Error(`Percentile must be in (0, 100], got ${p}`);
22
+ }
23
+ const sorted = [...samples].sort((a, b) => a - b);
24
+ const rank = Math.ceil((p / 100) * sorted.length);
25
+ return sorted[rank - 1];
26
+ }
27
+
28
+ /**
29
+ * @param {number[]} samples
30
+ * @returns {number} Arithmetic mean
31
+ */
32
+ function mean(samples) {
33
+ if (!Array.isArray(samples) || samples.length === 0) {
34
+ throw new Error("mean() needs at least one sample");
35
+ }
36
+ return samples.reduce((sum, x) => sum + x, 0) / samples.length;
37
+ }
38
+
39
+ /**
40
+ * @param {number[]} samples - Latencies in milliseconds
41
+ * @returns {{count: number, mean: number, p50: number, p95: number, min: number, max: number}}
42
+ */
43
+ function summarize(samples) {
44
+ return {
45
+ count: samples.length,
46
+ mean: mean(samples),
47
+ p50: percentile(samples, 50),
48
+ p95: percentile(samples, 95),
49
+ min: Math.min(...samples),
50
+ max: Math.max(...samples),
51
+ };
52
+ }
53
+
54
+ module.exports = { percentile, mean, summarize };