@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.
- package/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +431 -0
- package/README.md +418 -117
- package/SECURITY.md +5 -5
- package/dist/experimental.cjs +2 -0
- package/dist/experimental.d.ts +1 -0
- package/dist/experimental.mjs +5 -0
- package/dist/index.cjs +6442 -1355
- package/dist/index.d.ts +129 -2
- package/dist/index.mjs +1 -1
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.mjs +5 -0
- package/dist/types/adapters/ai-sdk.d.ts +53 -0
- package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
- package/dist/types/agentic/iterative-retriever.d.ts +85 -0
- package/dist/types/agentic/query-planner.d.ts +145 -0
- package/dist/types/agentic/self-critique.d.ts +45 -0
- package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
- package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
- package/dist/types/ai/multimodal-processing.d.ts +32 -0
- package/dist/types/ai/multimodal.d.ts +39 -0
- package/dist/types/ai/retrieval-engine.d.ts +37 -0
- package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
- package/dist/types/cache/component-keys.d.ts +7 -0
- package/dist/types/cache/memory-cache.d.ts +54 -0
- package/dist/types/cache/pipeline-cache.d.ts +66 -0
- package/dist/types/cache/redis-cache.d.ts +46 -0
- package/dist/types/chunking/chunking-engine.d.ts +70 -0
- package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
- package/dist/types/chunking/strategies/recursive.d.ts +20 -0
- package/dist/types/chunking/strategies/semantic.d.ts +19 -0
- package/dist/types/chunking/strategies/sentence.d.ts +13 -0
- package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
- package/dist/types/citation/citation-tracker.d.ts +64 -0
- package/dist/types/citation/hallucination-detector.d.ts +40 -0
- package/dist/types/citation/source-mapper.d.ts +37 -0
- package/dist/types/connectors/anthropic-connector.d.ts +97 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +94 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +95 -0
- package/dist/types/connectors/memory-retriever.d.ts +99 -0
- package/dist/types/connectors/ollama-connector.d.ts +115 -0
- package/dist/types/connectors/openai-connector.d.ts +129 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/core/create-pipeline.d.ts +8 -0
- package/dist/types/core/plugin-registry.d.ts +136 -0
- package/dist/types/core/stage-contracts.d.ts +36 -0
- package/dist/types/core/validate-plugin.d.ts +45 -0
- package/dist/types/cost/cost-calculator.d.ts +152 -0
- package/dist/types/cost/cost-tracker.d.ts +126 -0
- package/dist/types/cost/token-budget.d.ts +110 -0
- package/dist/types/dag/core/error-context.d.ts +239 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
- package/dist/types/dag/core/topology-validation.d.ts +226 -0
- package/dist/types/dag/dag-engine.d.ts +156 -0
- package/dist/types/dag/dag-node.d.ts +37 -0
- package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
- package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
- package/dist/types/debug/execution-tracer.d.ts +57 -0
- package/dist/types/debug/trace-inspector.d.ts +31 -0
- package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
- package/dist/types/enterprise/data-governance.d.ts +203 -0
- package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
- package/dist/types/enterprise/sso-integration.d.ts +420 -0
- package/dist/types/evaluate/context-metrics.d.ts +55 -0
- package/dist/types/evaluate/faithfulness.d.ts +41 -0
- package/dist/types/evaluate/groundedness.d.ts +60 -0
- package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
- package/dist/types/evaluate/relevance.d.ts +21 -0
- package/dist/types/evaluate/scoring.d.ts +123 -0
- package/dist/types/evaluate/semantic-similarity.d.ts +14 -0
- package/dist/types/experimental.d.ts +15 -0
- package/dist/types/graph/entity-extractor.d.ts +84 -0
- package/dist/types/graph/graph-index.d.ts +49 -0
- package/dist/types/graph/graph-retriever.d.ts +81 -0
- package/dist/types/graph/knowledge-graph.d.ts +129 -0
- package/dist/types/grounding/check-answer.d.ts +101 -0
- package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
- package/dist/types/guardrails/injection-detector.d.ts +47 -0
- package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
- package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
- package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
- package/dist/types/mcp/mcp-server.d.ts +85 -0
- package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
- package/dist/types/reranker/cascade-reranker.d.ts +40 -0
- package/dist/types/reranker/embedding-reranker.d.ts +77 -0
- package/dist/types/reranker/scoring-reranker.d.ts +41 -0
- package/dist/types/retrieval/bm25-search.d.ts +43 -0
- package/dist/types/retrieval/file-vector-store.d.ts +99 -0
- package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
- package/dist/types/retrieval/rank-fusion.d.ts +10 -0
- package/dist/types/security/jwt-config.d.ts +80 -0
- package/dist/types/security/jwt-validator.d.ts +195 -0
- package/dist/types/security/plugin-sandbox.d.ts +158 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
- package/dist/types/security/validator.d.ts +5 -0
- package/dist/types/streaming/embedding-stream.d.ts +65 -0
- package/dist/types/streaming/sse-adapter.d.ts +59 -0
- package/dist/types/streaming/stream-router.d.ts +88 -0
- package/dist/types/streaming/streaming-embedder.d.ts +99 -0
- package/dist/types/streaming/websocket-adapter.d.ts +71 -0
- package/dist/types/templates/scaffold.d.ts +55 -0
- package/dist/types/templates/template-registry.d.ts +63 -0
- package/dist/types/testing.d.ts +168 -0
- package/dist/types/utils/embed-query.d.ts +20 -0
- package/dist/types/utils/logger.d.ts +50 -0
- package/dist/types/utils/rate-limiter.d.ts +66 -0
- package/dist/types/utils/retry.d.ts +27 -0
- package/dist/types/utils/run-abort.d.ts +34 -0
- package/dist/types/utils/secure-logging.d.ts +51 -0
- package/dist/types/utils/structured-logger.d.ts +126 -0
- package/package.json +72 -33
- package/src/adapters/ai-sdk.js +215 -0
- package/src/agentic/agentic-pipeline.js +75 -7
- package/src/agentic/iterative-retriever.js +47 -10
- package/src/agentic/query-planner.js +329 -78
- package/src/cache/component-keys.js +38 -0
- package/src/cache/memory-cache.js +87 -0
- package/src/cache/pipeline-cache.js +260 -0
- package/src/cache/redis-cache.js +83 -0
- package/src/citation/citation-tracker.js +6 -4
- package/src/citation/hallucination-detector.js +2 -2
- package/src/citation/source-mapper.js +15 -28
- package/src/cli/commands/ai.js +106 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
- package/src/cli/commands/plugin-hub/local-commands.js +143 -0
- package/src/cli/commands/plugin-hub.js +114 -49
- package/src/cli/diagnostics/config-checker.js +76 -63
- package/src/cli/diagnostics/environment-checker.js +62 -31
- package/src/cli/diagnostics/plugin-checker.js +36 -89
- package/src/cli/doctor-command.js +39 -5
- package/src/cli/enhanced-cli-commands.js +104 -233
- package/src/cli/handlers/ai-handler.js +364 -0
- package/src/cli/handlers/diagnostics-handler.js +78 -31
- package/src/cli/handlers/ingest-handler.js +28 -22
- package/src/cli/handlers/init-handler.js +148 -25
- package/src/cli/handlers/query-handler.js +203 -25
- package/src/cli/handlers/run-handler.js +16 -167
- package/src/cli/handlers/serve-handler.js +60 -0
- package/src/cli/services/bench-stats.js +54 -0
- package/src/cli/services/benchmark.js +161 -0
- package/src/cli/services/feedback.js +155 -0
- package/src/cli/services/instrument.js +83 -0
- package/src/cli/services/rag-service.js +361 -0
- package/src/cli/shared/cli-utils.js +24 -7
- package/src/cli/shared/config-context.js +21 -0
- package/src/cli/wizard/config-builder.js +81 -0
- package/src/cli/wizard/init-wizard.js +602 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/enhanced-ragrc-schema.js +0 -2
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/config/validate-schema.js +4 -1
- package/src/connectors/anthropic-connector.js +115 -57
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +108 -34
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +63 -5
- package/src/connectors/memory-retriever.js +125 -14
- package/src/connectors/ollama-connector.js +112 -51
- package/src/connectors/openai-connector.js +153 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +307 -61
- package/src/core/performance/parallel-processor.js +62 -1
- package/src/core/plugin-registry.js +125 -26
- package/src/core/stage-contracts.js +36 -0
- package/src/core/validate-plugin.js +85 -0
- package/src/cost/cost-calculator.js +205 -32
- package/src/debug/execution-tracer.js +14 -4
- package/src/dev/dev-server.js +173 -11
- package/src/dev/hot-reload.js +44 -4
- package/src/ecosystem/plugin-checks.js +181 -0
- package/src/ecosystem/plugin-hub.js +50 -125
- package/src/ecosystem/plugin-installer.js +298 -0
- package/src/ecosystem/plugin-probe.js +127 -0
- package/src/ecosystem/plugin-scanner.js +185 -0
- package/src/ecosystem/tar-extract.js +114 -0
- package/src/enterprise/audit-logging.js +158 -29
- package/src/enterprise/sso-integration.js +716 -228
- package/src/evaluate/context-metrics.js +28 -16
- package/src/evaluate/evaluator.js +3 -3
- package/src/evaluate/faithfulness.js +18 -14
- package/src/evaluate/groundedness.js +43 -12
- package/src/evaluate/pipeline-evaluator.js +35 -8
- package/src/evaluate/scoring.js +251 -3
- package/src/evaluate/semantic-similarity.js +79 -0
- package/src/experimental.js +71 -0
- package/src/grounding/check-answer.js +173 -0
- package/src/guardrails/guardrails-pipeline.js +69 -7
- package/src/guardrails/injection-detector.js +283 -0
- package/src/guardrails/post-generation-guard.js +19 -0
- package/src/guardrails/pre-retrieval-guard.js +36 -36
- package/src/guardrails/retrieval-guard.js +58 -8
- package/src/index.js +58 -1
- package/src/loader/csv-loader.js +3 -2
- package/src/loader/document-reader.js +91 -0
- package/src/loader/html-loader.js +3 -1
- package/src/loader/markdown-loader.js +3 -2
- package/src/reranker/cascade-reranker.js +3 -0
- package/src/reranker/embedding-reranker.js +136 -21
- package/src/reranker/llm-reranker.js +79 -12
- package/src/reranker/scoring-reranker.js +3 -0
- package/src/retrieval/bm25-search.js +12 -6
- package/src/retrieval/file-vector-store.js +241 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +139 -34
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/testing.js +254 -0
- package/src/utils/deprecate.js +69 -0
- package/src/utils/embed-query.js +33 -0
- package/src/utils/input-sanitizer.js +19 -4
- package/src/utils/logger.js +34 -4
- package/src/utils/run-abort.js +102 -0
- package/src/ai/federated-learning.js +0 -211
- package/src/ai/federation.js +0 -71
- package/src/ai/index.js +0 -22
- package/src/ai/model-trainer.js +0 -217
- package/src/ai/model-training.js +0 -202
- package/src/ai/multimodal/multi-modal-processor.js +0 -345
- package/src/ai/orchestrator.js +0 -65
- package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
- package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
- package/src/cli/commands/ai-ml/federated-commands.js +0 -207
- package/src/cli/commands/ai-ml/index.js +0 -17
- package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
- package/src/cli/commands/ai-ml/shared.js +0 -51
- package/src/cli/commands/ai-ml/training-commands.js +0 -228
- package/src/cli/commands/ai-ml.js +0 -386
- package/src/cli/commands/docs.js +0 -228
- package/src/cli/commands/dx.js +0 -538
- package/src/cli/commands/wizard.js +0 -627
- package/src/cli/input-sanitization-middleware.js +0 -356
- package/src/cli/interactive-wizard.js +0 -373
- package/src/cli/templates/development.template.json +0 -64
- package/src/cli/templates/minimal.template.json +0 -33
- package/src/cli/templates/production.template.json +0 -104
- package/src/cli/templates/testing.template.json +0 -45
- package/src/cli/wizard/configuration.js +0 -148
- package/src/cli/wizard/observability.js +0 -79
- package/src/cli/wizard/performance.js +0 -76
- package/src/cli/wizard/plugin-selection.js +0 -297
- package/src/cli/wizard/preview-save.js +0 -172
- package/src/cli/wizard/project-setup.js +0 -66
- package/src/core/pipeline-factory.js +0 -59
- package/src/dag/enhanced-dag-engine.js +0 -894
- package/src/dx/index.js +0 -40
- package/src/dx/integration-templates.js +0 -662
- package/src/dx/performance-profiler.js +0 -968
- package/src/dx/realtime-debugger.js +0 -755
- package/src/dx/visual-pipeline-builder.js +0 -578
- package/src/ingest.js +0 -59
- package/src/pipeline/pipeline.js +0 -18
- package/src/query.js +0 -117
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interactive setup wizard for `rag-pipeline init`.
|
|
5
|
+
*
|
|
6
|
+
* Every choice maps to something buildComponents() can instantiate: the
|
|
7
|
+
* built-in components in config/component-factory, or a custom plugin
|
|
8
|
+
* module that is loaded and checked for the stage's methods before it is
|
|
9
|
+
* accepted. The prompt function is injected (inquirer.prompt in the CLI) and
|
|
10
|
+
* is always called with a single question, so tests can script answers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const {
|
|
16
|
+
BUILT_INS,
|
|
17
|
+
loadPluginModule,
|
|
18
|
+
buildComponents: defaultBuildComponents,
|
|
19
|
+
} = require("../../config/component-factory");
|
|
20
|
+
const { embedQuery } = require("../../utils/embed-query");
|
|
21
|
+
const { buildInitConfig, DEFAULT_INDEX_PATH } = require("./config-builder");
|
|
22
|
+
// Methods a custom plugin must expose; shared with validatePlugin().
|
|
23
|
+
const { STAGE_CONTRACTS } = require("../../core/stage-contracts");
|
|
24
|
+
|
|
25
|
+
const CUSTOM = "custom";
|
|
26
|
+
const NONE = "none";
|
|
27
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
28
|
+
|
|
29
|
+
/** Environment variable each hosted provider reads its API key from. */
|
|
30
|
+
const API_KEY_ENV = {
|
|
31
|
+
openai: "OPENAI_API_KEY",
|
|
32
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
33
|
+
cohere: "COHERE_API_KEY",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const CONNECTOR_MODULES = {
|
|
37
|
+
openai: "../../connectors/openai-connector",
|
|
38
|
+
anthropic: "../../connectors/anthropic-connector",
|
|
39
|
+
ollama: "../../connectors/ollama-connector",
|
|
40
|
+
cohere: "../../connectors/cohere-connector",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Connector options the wizard asks for, per stage and provider. */
|
|
44
|
+
const PROVIDER_OPTIONS = {
|
|
45
|
+
embedder: {
|
|
46
|
+
openai: ["embeddingModel"],
|
|
47
|
+
ollama: ["baseURL", "embeddingModel"],
|
|
48
|
+
cohere: ["embeddingModel"],
|
|
49
|
+
},
|
|
50
|
+
llm: {
|
|
51
|
+
openai: ["model"],
|
|
52
|
+
anthropic: ["model"],
|
|
53
|
+
ollama: ["baseURL", "model"],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const OPTION_LABELS = {
|
|
58
|
+
model: "model",
|
|
59
|
+
embeddingModel: "embedding model",
|
|
60
|
+
baseURL: "server URL",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const CUSTOM_CHOICE = {
|
|
64
|
+
name: "custom plugin (path to a .js module)",
|
|
65
|
+
value: CUSTOM,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const STAGE_CHOICES = {
|
|
69
|
+
embedder: [
|
|
70
|
+
{ name: "local (TF-IDF, offline, no API key)", value: "local" },
|
|
71
|
+
{ name: "openai (OPENAI_API_KEY)", value: "openai" },
|
|
72
|
+
{ name: "ollama (local Ollama server)", value: "ollama" },
|
|
73
|
+
{ name: "cohere (COHERE_API_KEY)", value: "cohere" },
|
|
74
|
+
CUSTOM_CHOICE,
|
|
75
|
+
],
|
|
76
|
+
llm: [
|
|
77
|
+
{ name: "none (query prints the retrieved passages)", value: NONE },
|
|
78
|
+
{ name: "openai (OPENAI_API_KEY)", value: "openai" },
|
|
79
|
+
{ name: "anthropic (ANTHROPIC_API_KEY)", value: "anthropic" },
|
|
80
|
+
{ name: "ollama (local Ollama server)", value: "ollama" },
|
|
81
|
+
CUSTOM_CHOICE,
|
|
82
|
+
],
|
|
83
|
+
retriever: [
|
|
84
|
+
{
|
|
85
|
+
name: "file-store (JSON index on disk, persists between commands)",
|
|
86
|
+
value: "file-store",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "memory (in-process only, lost when the command exits)",
|
|
90
|
+
value: "memory",
|
|
91
|
+
},
|
|
92
|
+
CUSTOM_CHOICE,
|
|
93
|
+
],
|
|
94
|
+
reranker: [
|
|
95
|
+
{ name: "none", value: NONE },
|
|
96
|
+
{ name: "scoring (BM25 re-scoring, offline)", value: "scoring" },
|
|
97
|
+
CUSTOM_CHOICE,
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const STAGE_TITLES = {
|
|
102
|
+
embedder: "Embedder",
|
|
103
|
+
llm: "Language model",
|
|
104
|
+
retriever: "Retriever",
|
|
105
|
+
reranker: "Reranker (optional)",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Ask one question and return its answer. */
|
|
109
|
+
async function ask(prompt, question) {
|
|
110
|
+
const answers = await prompt([question]);
|
|
111
|
+
return answers ? answers[question.name] : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Defaults for the options the wizard asks for (never the API key). */
|
|
115
|
+
function connectorDefaults(provider) {
|
|
116
|
+
const { DEFAULT_CONFIG } = require(CONNECTOR_MODULES[provider]);
|
|
117
|
+
return {
|
|
118
|
+
model: DEFAULT_CONFIG.model,
|
|
119
|
+
embeddingModel: DEFAULT_CONFIG.embeddingModel,
|
|
120
|
+
baseURL: DEFAULT_CONFIG.baseURL,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Path written to the config: relative to its directory, forward slashes. */
|
|
125
|
+
function toConfigPath(absPath, configDir) {
|
|
126
|
+
// Segment-wise instead of path.relative(), which returns wrong results on
|
|
127
|
+
// some Windows Node releases (e.g. "..C:/..." for a file inside `from`).
|
|
128
|
+
const win = process.platform === "win32";
|
|
129
|
+
const same = (a, b) => (win ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
130
|
+
const from = path
|
|
131
|
+
.resolve(configDir)
|
|
132
|
+
.split(/[\\/]+/)
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
const to = path
|
|
135
|
+
.resolve(absPath)
|
|
136
|
+
.split(/[\\/]+/)
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
if (win && !same(from[0] || "", to[0] || "")) {
|
|
139
|
+
return path.resolve(absPath).split(path.sep).join("/"); // other drive
|
|
140
|
+
}
|
|
141
|
+
let common = 0;
|
|
142
|
+
while (
|
|
143
|
+
common < from.length &&
|
|
144
|
+
common < to.length &&
|
|
145
|
+
same(from[common], to[common])
|
|
146
|
+
) {
|
|
147
|
+
common++;
|
|
148
|
+
}
|
|
149
|
+
const rel = [
|
|
150
|
+
...Array(from.length - common).fill(".."),
|
|
151
|
+
...to.slice(common),
|
|
152
|
+
].join("/");
|
|
153
|
+
return rel.startsWith("../") ? rel : `./${rel}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Plugin name from the file name, never shadowed by a built-in. */
|
|
157
|
+
function pluginNameFor(stage, absPath) {
|
|
158
|
+
const base =
|
|
159
|
+
path
|
|
160
|
+
.basename(absPath, path.extname(absPath))
|
|
161
|
+
.replace(/[^A-Za-z0-9_-]+/g, "-")
|
|
162
|
+
.replace(/^-+|-+$/g, "") || `custom-${stage}`;
|
|
163
|
+
const builtIns = BUILT_INS[stage] || {};
|
|
164
|
+
let name = base;
|
|
165
|
+
for (let n = 2; Object.prototype.hasOwnProperty.call(builtIns, name); n++) {
|
|
166
|
+
name = n === 2 ? `${base}-custom` : `${base}-custom-${n}`;
|
|
167
|
+
}
|
|
168
|
+
return name;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Load a custom plugin the way buildComponents() will and check that it
|
|
173
|
+
* exposes the stage's methods.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} stage - embedder | llm | retriever | reranker
|
|
176
|
+
* @param {string} input - Path as typed by the user (relative to cwd)
|
|
177
|
+
* @param {object} options
|
|
178
|
+
* @param {string} options.cwd - Directory user input is resolved against
|
|
179
|
+
* @param {string} options.configDir - Directory of the output config file
|
|
180
|
+
* @returns {{ok: true, selection: object, warnings: string[]} | {ok: false, error: string}}
|
|
181
|
+
*/
|
|
182
|
+
function inspectPlugin(stage, input, { cwd, configDir }) {
|
|
183
|
+
const absPath = path.resolve(cwd, input);
|
|
184
|
+
let stat;
|
|
185
|
+
try {
|
|
186
|
+
stat = fs.statSync(absPath);
|
|
187
|
+
} catch {
|
|
188
|
+
return { ok: false, error: `File not found: ${absPath}` };
|
|
189
|
+
}
|
|
190
|
+
if (!stat.isFile()) {
|
|
191
|
+
return { ok: false, error: `Not a file: ${absPath}` };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Pick up edits when the user fixes the file and tries again.
|
|
195
|
+
try {
|
|
196
|
+
delete require.cache[require.resolve(absPath)];
|
|
197
|
+
} catch {
|
|
198
|
+
// not resolvable yet; loadPluginModule reports the real error
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let instance;
|
|
202
|
+
try {
|
|
203
|
+
instance = loadPluginModule(absPath, {}, configDir);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return { ok: false, error: `Could not load ${absPath}: ${error.message}` };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const contract = STAGE_CONTRACTS[stage];
|
|
209
|
+
const has = (method) => instance && typeof instance[method] === "function";
|
|
210
|
+
if (!contract.anyOf.some(has)) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: `${absPath} does not look like a ${stage}: it must export a class or object with ${contract.anyOf.map((m) => `${m}()`).join(" or ")}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const warnings = Object.entries(contract.recommended)
|
|
218
|
+
.filter(([method]) => !has(method))
|
|
219
|
+
.map(([method, why]) => `No ${method}() method: ${why}, so it will fail.`);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
ok: true,
|
|
223
|
+
selection: {
|
|
224
|
+
name: pluginNameFor(stage, absPath),
|
|
225
|
+
module: toConfigPath(absPath, configDir),
|
|
226
|
+
},
|
|
227
|
+
warnings,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Ask for a custom plugin path until it validates; empty input cancels. */
|
|
232
|
+
async function askCustomPlugin(ctx, stage) {
|
|
233
|
+
for (;;) {
|
|
234
|
+
const answer = await ask(ctx.prompt, {
|
|
235
|
+
type: "input",
|
|
236
|
+
name: `${stage}Plugin`,
|
|
237
|
+
message: `Path to your ${stage} module (empty to go back):`,
|
|
238
|
+
});
|
|
239
|
+
const input = String(answer || "").trim();
|
|
240
|
+
if (!input) return null;
|
|
241
|
+
|
|
242
|
+
const result = inspectPlugin(stage, input, ctx);
|
|
243
|
+
if (!result.ok) {
|
|
244
|
+
ctx.log(` ✖ ${result.error}`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
for (const warning of result.warnings) ctx.log(` ⚠ ${warning}`);
|
|
248
|
+
ctx.log(
|
|
249
|
+
` ✔ ${stage} plugin "${result.selection.name}" -> ${result.selection.module}`,
|
|
250
|
+
);
|
|
251
|
+
return result.selection;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Ask for the connector options of a built-in provider. */
|
|
256
|
+
async function askProviderOptions(ctx, stage, provider) {
|
|
257
|
+
const keys = (PROVIDER_OPTIONS[stage] || {})[provider] || [];
|
|
258
|
+
if (keys.length === 0) return undefined;
|
|
259
|
+
|
|
260
|
+
const defaults = connectorDefaults(provider);
|
|
261
|
+
const config = {};
|
|
262
|
+
for (const key of keys) {
|
|
263
|
+
// Reuse the Ollama URL typed for the embedder as the LLM's default.
|
|
264
|
+
const fallback =
|
|
265
|
+
key === "baseURL" && ctx.ollamaBaseURL
|
|
266
|
+
? ctx.ollamaBaseURL
|
|
267
|
+
: defaults[key];
|
|
268
|
+
const answer = await ask(ctx.prompt, {
|
|
269
|
+
type: "input",
|
|
270
|
+
name: `${stage}_${key}`,
|
|
271
|
+
message: `${provider} ${OPTION_LABELS[key]}:`,
|
|
272
|
+
default: fallback,
|
|
273
|
+
});
|
|
274
|
+
config[key] = String(answer ?? "").trim() || fallback;
|
|
275
|
+
if (key === "baseURL") ctx.ollamaBaseURL = config[key];
|
|
276
|
+
}
|
|
277
|
+
return config;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** One pipeline stage: pick a provider, then its options or plugin path. */
|
|
281
|
+
async function askStage(ctx, stage, defaultName) {
|
|
282
|
+
for (;;) {
|
|
283
|
+
const choice = await ask(ctx.prompt, {
|
|
284
|
+
type: "list",
|
|
285
|
+
name: stage,
|
|
286
|
+
message: `${STAGE_TITLES[stage]}:`,
|
|
287
|
+
choices: STAGE_CHOICES[stage],
|
|
288
|
+
default: defaultName,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (choice === NONE || choice === null || choice === undefined) {
|
|
292
|
+
if (stage === "embedder" || stage === "retriever") {
|
|
293
|
+
ctx.log(` ✖ A ${stage} is required.`);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (choice === CUSTOM) {
|
|
300
|
+
const selection = await askCustomPlugin(ctx, stage);
|
|
301
|
+
if (selection) return selection;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!BUILT_INS[stage] || !BUILT_INS[stage][choice]) {
|
|
306
|
+
ctx.log(` ✖ Unknown ${stage} "${choice}".`);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (stage === "retriever") return askRetrieverOptions(ctx, choice);
|
|
311
|
+
|
|
312
|
+
const config = await askProviderOptions(ctx, stage, choice);
|
|
313
|
+
return config ? { name: choice, config } : { name: choice };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function askRetrieverOptions(ctx, name) {
|
|
318
|
+
if (name === "memory") {
|
|
319
|
+
ctx.log(
|
|
320
|
+
" ⚠ The memory retriever does not persist: `ingest` and `query` run as " +
|
|
321
|
+
"separate processes, so documents ingested by one command are gone " +
|
|
322
|
+
"for the next. Use it from code or with `run --ingest ... --query ...`.",
|
|
323
|
+
);
|
|
324
|
+
return { name };
|
|
325
|
+
}
|
|
326
|
+
const answer = await ask(ctx.prompt, {
|
|
327
|
+
type: "input",
|
|
328
|
+
name: "indexPath",
|
|
329
|
+
message: "Index file (relative to the config file):",
|
|
330
|
+
default: ctx.defaults.indexPath || DEFAULT_INDEX_PATH,
|
|
331
|
+
});
|
|
332
|
+
const indexPath =
|
|
333
|
+
String(answer ?? "").trim() || ctx.defaults.indexPath || DEFAULT_INDEX_PATH;
|
|
334
|
+
return { name, config: { path: indexPath } };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Which API keys / services the chosen built-in providers need.
|
|
339
|
+
*
|
|
340
|
+
* @param {object} selection - Stage selections from the wizard
|
|
341
|
+
* @param {object} env - Environment to check (process.env in the CLI)
|
|
342
|
+
* @returns {Array<{provider: string, stages: string[], envVar?: string, set?: boolean, baseURL?: string}>}
|
|
343
|
+
*/
|
|
344
|
+
function checkEnvironment(selection, env) {
|
|
345
|
+
const byProvider = new Map();
|
|
346
|
+
for (const stage of ["embedder", "llm", "reranker"]) {
|
|
347
|
+
const sel = selection[stage];
|
|
348
|
+
if (!sel || sel.module) continue;
|
|
349
|
+
const envVar = API_KEY_ENV[sel.name];
|
|
350
|
+
if (envVar) {
|
|
351
|
+
const item = byProvider.get(sel.name) || {
|
|
352
|
+
provider: sel.name,
|
|
353
|
+
stages: [],
|
|
354
|
+
envVar,
|
|
355
|
+
set: Boolean(env[envVar]),
|
|
356
|
+
};
|
|
357
|
+
item.stages.push(stage);
|
|
358
|
+
byProvider.set(sel.name, item);
|
|
359
|
+
} else if (sel.name === "ollama") {
|
|
360
|
+
const baseURL =
|
|
361
|
+
(sel.config && sel.config.baseURL) || "http://localhost:11434";
|
|
362
|
+
const key = `ollama ${baseURL}`;
|
|
363
|
+
const item = byProvider.get(key) || {
|
|
364
|
+
provider: "ollama",
|
|
365
|
+
stages: [],
|
|
366
|
+
baseURL,
|
|
367
|
+
};
|
|
368
|
+
item.stages.push(stage);
|
|
369
|
+
byProvider.set(key, item);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return [...byProvider.values()];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function reportEnvironment(checks, log) {
|
|
376
|
+
log("\nEnvironment check:");
|
|
377
|
+
if (checks.length === 0) {
|
|
378
|
+
log(" ✔ No API keys needed for the chosen components.");
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
for (const check of checks) {
|
|
382
|
+
const used = `${check.provider} ${check.stages.join(" + ")}`;
|
|
383
|
+
if (check.envVar && check.set) {
|
|
384
|
+
log(` ✔ ${check.envVar} is set (${used})`);
|
|
385
|
+
} else if (check.envVar) {
|
|
386
|
+
log(
|
|
387
|
+
` ⚠ ${check.envVar} is not set (${used}). Set it before ingest/query:`,
|
|
388
|
+
);
|
|
389
|
+
log(` export ${check.envVar}=<your key> # bash/zsh`);
|
|
390
|
+
log(` $env:${check.envVar} = "<your key>" # PowerShell`);
|
|
391
|
+
} else {
|
|
392
|
+
log(
|
|
393
|
+
` • ${used} needs an Ollama server at ${check.baseURL} (no API key)`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
log(
|
|
398
|
+
" API keys are read from the environment and never written to the config file.",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Run fn(signal) with a time limit. On timeout the AbortSignal handed to fn
|
|
404
|
+
* is aborted (with the timeout error as its reason) so a connector that
|
|
405
|
+
* honours `options.signal` cancels its request instead of leaving a socket
|
|
406
|
+
* open; the timer is unref'd and always cleared.
|
|
407
|
+
*/
|
|
408
|
+
async function withTimeout(fn, ms, label) {
|
|
409
|
+
const controller = new AbortController();
|
|
410
|
+
let timer;
|
|
411
|
+
const timeout = new Promise((_, reject) => {
|
|
412
|
+
timer = setTimeout(() => {
|
|
413
|
+
const error = new Error(`${label} timed out after ${ms} ms`);
|
|
414
|
+
controller.abort(error);
|
|
415
|
+
reject(error);
|
|
416
|
+
}, ms);
|
|
417
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
418
|
+
});
|
|
419
|
+
try {
|
|
420
|
+
return await Promise.race([
|
|
421
|
+
Promise.resolve().then(() => fn(controller.signal)),
|
|
422
|
+
timeout,
|
|
423
|
+
]);
|
|
424
|
+
} finally {
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Instantiate the embedder and llm from the config and make one real call
|
|
431
|
+
* to each. Failures are reported, never thrown.
|
|
432
|
+
*
|
|
433
|
+
* @returns {Promise<Array<{stage: string, ok: boolean, skipped?: boolean, message: string}>>}
|
|
434
|
+
*/
|
|
435
|
+
async function testConnections(config, options) {
|
|
436
|
+
const { buildComponents, configDir, timeoutMs } = options;
|
|
437
|
+
const results = [];
|
|
438
|
+
|
|
439
|
+
for (const stage of ["embedder", "llm"]) {
|
|
440
|
+
const entry = config.pipeline.find((e) => e.stage === stage);
|
|
441
|
+
if (!entry) continue;
|
|
442
|
+
if (stage === "embedder" && entry.name === "local") {
|
|
443
|
+
results.push({
|
|
444
|
+
stage,
|
|
445
|
+
ok: true,
|
|
446
|
+
skipped: true,
|
|
447
|
+
message: "local embedder runs offline; nothing to test",
|
|
448
|
+
});
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const components = buildComponents(
|
|
454
|
+
{ ...config, pipeline: [entry] },
|
|
455
|
+
{ configDir },
|
|
456
|
+
);
|
|
457
|
+
const component = components[stage];
|
|
458
|
+
if (stage === "embedder") {
|
|
459
|
+
const vector = await withTimeout(
|
|
460
|
+
(signal) => embedQuery(component, "test", { signal }),
|
|
461
|
+
timeoutMs,
|
|
462
|
+
'embedQuery("test")',
|
|
463
|
+
);
|
|
464
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
465
|
+
throw new Error('embedQuery("test") returned no vector');
|
|
466
|
+
}
|
|
467
|
+
results.push({
|
|
468
|
+
stage,
|
|
469
|
+
ok: true,
|
|
470
|
+
message: `${entry.name} returned a ${vector.length}-dimension vector`,
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
const reply = await withTimeout(
|
|
474
|
+
(signal) => component.generate("Say OK", [], { signal }),
|
|
475
|
+
timeoutMs,
|
|
476
|
+
'generate("Say OK")',
|
|
477
|
+
);
|
|
478
|
+
const text =
|
|
479
|
+
typeof reply === "string"
|
|
480
|
+
? reply
|
|
481
|
+
: reply && (reply.text || reply.content || reply.answer);
|
|
482
|
+
results.push({
|
|
483
|
+
stage,
|
|
484
|
+
ok: true,
|
|
485
|
+
message: `${entry.name} replied${text ? `: ${String(text).trim().slice(0, 60)}` : ""}`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
results.push({
|
|
490
|
+
stage,
|
|
491
|
+
ok: false,
|
|
492
|
+
message: `${entry.name}: ${error && error.message ? error.message : error}`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return results;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Run the wizard.
|
|
501
|
+
*
|
|
502
|
+
* @param {object} options
|
|
503
|
+
* @param {Function} options.prompt - inquirer-style prompt([question]) => answers
|
|
504
|
+
* @param {object} [options.defaults] - {embedder, llm, indexPath} from --template
|
|
505
|
+
* @param {string} [options.outputPath='.ragrc.json'] - Config file to write
|
|
506
|
+
* @param {boolean} [options.overwrite=false] - The output file exists and will be replaced
|
|
507
|
+
* @param {object} [options.env=process.env] - Environment for the API key check
|
|
508
|
+
* @param {Function} [options.buildComponents] - Used by the connection test
|
|
509
|
+
* @param {Function} [options.log=console.log]
|
|
510
|
+
* @param {string} [options.cwd=process.cwd()] - Base for plugin paths typed by the user
|
|
511
|
+
* @param {number} [options.timeoutMs=15000] - Connection test timeout per call
|
|
512
|
+
* @returns {Promise<null | {config: object, selection: object, environment: Array, connection: Array|null}>}
|
|
513
|
+
* null when the user declines to write the file
|
|
514
|
+
*/
|
|
515
|
+
async function runInitWizard(options) {
|
|
516
|
+
const {
|
|
517
|
+
prompt,
|
|
518
|
+
defaults = {},
|
|
519
|
+
outputPath = ".ragrc.json",
|
|
520
|
+
overwrite = false,
|
|
521
|
+
env = process.env,
|
|
522
|
+
buildComponents = defaultBuildComponents,
|
|
523
|
+
log = console.log,
|
|
524
|
+
cwd = process.cwd(),
|
|
525
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
526
|
+
} = options;
|
|
527
|
+
if (typeof prompt !== "function") {
|
|
528
|
+
throw new Error("runInitWizard requires a prompt function");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const configDir = path.dirname(path.resolve(cwd, outputPath));
|
|
532
|
+
const ctx = { prompt, log, cwd, configDir, defaults };
|
|
533
|
+
|
|
534
|
+
log("rag-pipeline setup: choose the components for your pipeline.");
|
|
535
|
+
log(
|
|
536
|
+
"Custom plugin paths are checked now and stored relative to the config file.\n",
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const selection = {};
|
|
540
|
+
selection.embedder = await askStage(
|
|
541
|
+
ctx,
|
|
542
|
+
"embedder",
|
|
543
|
+
defaults.embedder || "local",
|
|
544
|
+
);
|
|
545
|
+
selection.llm = await askStage(ctx, "llm", defaults.llm || NONE);
|
|
546
|
+
selection.retriever = await askStage(ctx, "retriever", "file-store");
|
|
547
|
+
selection.reranker = await askStage(ctx, "reranker", NONE);
|
|
548
|
+
|
|
549
|
+
const config = buildInitConfig(selection);
|
|
550
|
+
|
|
551
|
+
const environment = checkEnvironment(selection, env);
|
|
552
|
+
reportEnvironment(environment, log);
|
|
553
|
+
|
|
554
|
+
let connection = null;
|
|
555
|
+
const wantsTest = await ask(prompt, {
|
|
556
|
+
type: "confirm",
|
|
557
|
+
name: "testConnection",
|
|
558
|
+
message: "Test the embedder/LLM now? (makes real API calls)",
|
|
559
|
+
default: false,
|
|
560
|
+
});
|
|
561
|
+
if (wantsTest) {
|
|
562
|
+
log("\nConnection test:");
|
|
563
|
+
connection = await testConnections(config, {
|
|
564
|
+
buildComponents,
|
|
565
|
+
configDir,
|
|
566
|
+
timeoutMs,
|
|
567
|
+
});
|
|
568
|
+
for (const result of connection) {
|
|
569
|
+
const mark = result.skipped ? "•" : result.ok ? "✔" : "✖";
|
|
570
|
+
log(` ${mark} ${result.stage}: ${result.message}`);
|
|
571
|
+
}
|
|
572
|
+
if (connection.some((r) => !r.ok)) {
|
|
573
|
+
log(
|
|
574
|
+
" The configuration can still be saved; fix the issue before running queries.",
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
log(`\nConfiguration preview (${outputPath}):`);
|
|
580
|
+
log(JSON.stringify(config, null, 2));
|
|
581
|
+
const confirmed = await ask(prompt, {
|
|
582
|
+
type: "confirm",
|
|
583
|
+
name: "confirmWrite",
|
|
584
|
+
message: overwrite
|
|
585
|
+
? `Overwrite ${outputPath} with this configuration?`
|
|
586
|
+
: `Write this configuration to ${outputPath}?`,
|
|
587
|
+
default: true,
|
|
588
|
+
});
|
|
589
|
+
if (!confirmed) return null;
|
|
590
|
+
|
|
591
|
+
return { config, selection, environment, connection };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
module.exports = {
|
|
595
|
+
runInitWizard,
|
|
596
|
+
inspectPlugin,
|
|
597
|
+
checkEnvironment,
|
|
598
|
+
testConnections,
|
|
599
|
+
ask,
|
|
600
|
+
API_KEY_ENV,
|
|
601
|
+
STAGE_CONTRACTS,
|
|
602
|
+
};
|