@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.
- package/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +345 -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 +4992 -1242
- package/dist/index.d.ts +80 -2
- package/dist/index.mjs +1 -1
- 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/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 +92 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +89 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +86 -0
- package/dist/types/connectors/memory-retriever.d.ts +91 -0
- package/dist/types/connectors/ollama-connector.d.ts +110 -0
- package/dist/types/connectors/openai-connector.d.ts +114 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -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/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/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 +87 -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/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 +53 -29
- 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/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 +101 -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 +96 -36
- 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 +72 -217
- 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 +41 -26
- 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/rag-service.js +322 -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 +617 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/connectors/anthropic-connector.js +93 -55
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +88 -32
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +43 -5
- package/src/connectors/memory-retriever.js +108 -15
- package/src/connectors/ollama-connector.js +83 -49
- package/src/connectors/openai-connector.js +108 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +209 -60
- package/src/core/plugin-registry.js +87 -11
- package/src/cost/cost-calculator.js +205 -32
- package/src/dev/dev-server.js +166 -11
- package/src/dev/hot-reload.js +40 -1
- 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 +715 -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/experimental.js +71 -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 +36 -1
- package/src/loader/document-reader.js +91 -0
- 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 +212 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +134 -30
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- 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,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `rag-pipeline ai benchmark`: time real queries against the configured
|
|
5
|
+
* pipeline and index. Every number reported is measured here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const { embedQuery } = require("../../utils/embed-query");
|
|
10
|
+
const { loadComponents, query } = require("./rag-service");
|
|
11
|
+
const { summarize } = require("./bench-stats");
|
|
12
|
+
|
|
13
|
+
const SAMPLE_QUERIES = 5;
|
|
14
|
+
const SAMPLE_WORDS = 8;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read queries from a file: a JSON array of strings, or one query per line.
|
|
18
|
+
* @param {string} file
|
|
19
|
+
* @returns {string[]}
|
|
20
|
+
*/
|
|
21
|
+
function readQueryFile(file) {
|
|
22
|
+
if (!fs.existsSync(file)) throw new Error(`Queries file not found: ${file}`);
|
|
23
|
+
const text = fs.readFileSync(file, "utf8");
|
|
24
|
+
const trimmed = text.trim();
|
|
25
|
+
let queries;
|
|
26
|
+
if (trimmed.startsWith("[")) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(trimmed);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Queries file ${file} is not valid JSON: ${error.message}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (!parsed.every((q) => typeof q === "string")) {
|
|
36
|
+
throw new Error(`Queries file ${file} must be a JSON array of strings`);
|
|
37
|
+
}
|
|
38
|
+
queries = parsed;
|
|
39
|
+
} else {
|
|
40
|
+
queries = text.split(/\r?\n/);
|
|
41
|
+
}
|
|
42
|
+
queries = queries.map((q) => q.trim()).filter(Boolean);
|
|
43
|
+
if (queries.length === 0) throw new Error(`No queries found in ${file}`);
|
|
44
|
+
return queries;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Deterministic sample: the first words of the first few indexed chunks.
|
|
49
|
+
* @param {Array<{content: string}>} chunks
|
|
50
|
+
* @returns {string[]}
|
|
51
|
+
*/
|
|
52
|
+
function sampleQueries(chunks, count = SAMPLE_QUERIES) {
|
|
53
|
+
return chunks
|
|
54
|
+
.map((c) => String(c.content).split(/\s+/).filter(Boolean))
|
|
55
|
+
.filter((words) => words.length > 0)
|
|
56
|
+
.slice(0, count)
|
|
57
|
+
.map((words) => words.slice(0, SAMPLE_WORDS).join(" "));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const elapsedMs = (start) => Number(process.hrtime.bigint() - start) / 1e6;
|
|
61
|
+
|
|
62
|
+
async function timed(fn) {
|
|
63
|
+
const start = process.hrtime.bigint();
|
|
64
|
+
const value = await fn();
|
|
65
|
+
return { value, ms: elapsedMs(start) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {object} params
|
|
70
|
+
* @param {object} params.config
|
|
71
|
+
* @param {string} params.configDir
|
|
72
|
+
* @param {string} [params.queriesFile]
|
|
73
|
+
* @param {number} [params.runs=3] - Passes over the query set
|
|
74
|
+
* @param {number} [params.topK=3]
|
|
75
|
+
* @returns {Promise<object>} Measured report
|
|
76
|
+
*/
|
|
77
|
+
async function runBenchmark({
|
|
78
|
+
config,
|
|
79
|
+
configDir,
|
|
80
|
+
queriesFile,
|
|
81
|
+
runs = 3,
|
|
82
|
+
topK = 3,
|
|
83
|
+
}) {
|
|
84
|
+
const setup = await timed(() => loadComponents({ config, configDir }));
|
|
85
|
+
const components = setup.value;
|
|
86
|
+
const { embedder, retriever, reranker, llm } = components;
|
|
87
|
+
|
|
88
|
+
const queries = queriesFile
|
|
89
|
+
? readQueryFile(queriesFile)
|
|
90
|
+
: sampleQueries(retriever.list ? retriever.list() : []);
|
|
91
|
+
if (queries.length === 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"No queries: pass --queries <file> (the index has no text to sample from).",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// First query pays one-time costs (lazy init, JIT); reported, not counted.
|
|
98
|
+
const warmup = await timed(() =>
|
|
99
|
+
query({ components, question: queries[0], topK }),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const samples = { embed: [], retrieve: [], rerank: [], endToEnd: [] };
|
|
103
|
+
const resultCounts = [];
|
|
104
|
+
for (let run = 0; run < runs; run++) {
|
|
105
|
+
for (const question of queries) {
|
|
106
|
+
const embedded = await timed(() => embedQuery(embedder, question));
|
|
107
|
+
samples.embed.push(embedded.ms);
|
|
108
|
+
const retrieved = await timed(() =>
|
|
109
|
+
retriever.retrieve({
|
|
110
|
+
query: question,
|
|
111
|
+
queryVector: embedded.value,
|
|
112
|
+
topK,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
samples.retrieve.push(retrieved.ms);
|
|
116
|
+
if (reranker) {
|
|
117
|
+
const reranked = await timed(() =>
|
|
118
|
+
reranker.rerank(question, retrieved.value, { topK }),
|
|
119
|
+
);
|
|
120
|
+
samples.rerank.push(reranked.ms);
|
|
121
|
+
}
|
|
122
|
+
const e2e = await timed(() => query({ components, question, topK }));
|
|
123
|
+
samples.endToEnd.push(e2e.ms);
|
|
124
|
+
resultCounts.push(e2e.value.results.length);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const stages = {
|
|
129
|
+
embed: summarize(samples.embed),
|
|
130
|
+
retrieve: summarize(samples.retrieve),
|
|
131
|
+
...(reranker && { rerank: summarize(samples.rerank) }),
|
|
132
|
+
endToEnd: summarize(samples.endToEnd),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
queries,
|
|
137
|
+
queriesSource: queriesFile || "sampled from indexed chunks",
|
|
138
|
+
runs,
|
|
139
|
+
topK,
|
|
140
|
+
setupMs: setup.ms,
|
|
141
|
+
warmupMs: warmup.ms,
|
|
142
|
+
stages,
|
|
143
|
+
includesGeneration: Boolean(llm),
|
|
144
|
+
results: {
|
|
145
|
+
min: Math.min(...resultCounts),
|
|
146
|
+
max: Math.max(...resultCounts),
|
|
147
|
+
},
|
|
148
|
+
environment: {
|
|
149
|
+
node: process.version,
|
|
150
|
+
platform: `${process.platform} ${process.arch}`,
|
|
151
|
+
chunks: retriever.size ? retriever.size() : null,
|
|
152
|
+
embedder: components.names.embedder,
|
|
153
|
+
dimensions: retriever.getEmbedderInfo?.()?.dimensions ?? null,
|
|
154
|
+
retriever: components.names.retriever,
|
|
155
|
+
reranker: components.names.reranker || null,
|
|
156
|
+
llm: components.names.llm || null,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { runBenchmark, readQueryFile, sampleQueries };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Relevance feedback for `rag-pipeline ai feedback` / `ai search`.
|
|
5
|
+
*
|
|
6
|
+
* Feedback is stored as JSON next to the index. Scoring is deterministic and
|
|
7
|
+
* explainable: every stored vote for a result's docId contributes
|
|
8
|
+
*
|
|
9
|
+
* sign(vote) * VOTE_WEIGHT * jaccard(terms(question), terms(votedQuestion))
|
|
10
|
+
*
|
|
11
|
+
* and the summed adjustment is capped to +/- MAX_ADJUSTMENT before being added
|
|
12
|
+
* to the retrieval score. Votes on unrelated questions (no shared terms)
|
|
13
|
+
* contribute nothing.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require("fs");
|
|
17
|
+
const path = require("path");
|
|
18
|
+
const { tokenize } = require("../../evaluate/scoring");
|
|
19
|
+
|
|
20
|
+
const FORMAT_VERSION = 1;
|
|
21
|
+
const VOTE_WEIGHT = 0.1;
|
|
22
|
+
const MAX_ADJUSTMENT = 0.3;
|
|
23
|
+
const RATINGS = ["up", "down"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Where feedback lives: next to a file-store index, else <configDir>/.rag.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} retriever - Built retriever (FileVectorStore has `path`)
|
|
29
|
+
* @param {string} configDir
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function feedbackPath(retriever, configDir) {
|
|
33
|
+
const dir =
|
|
34
|
+
retriever && typeof retriever.path === "string"
|
|
35
|
+
? path.dirname(retriever.path)
|
|
36
|
+
: path.join(configDir, ".rag");
|
|
37
|
+
return path.join(dir, "feedback.json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} file
|
|
42
|
+
* @returns {{version: number, entries: Array<{question: string, docId: string, rating: string, at: string}>}}
|
|
43
|
+
*/
|
|
44
|
+
function loadFeedback(file) {
|
|
45
|
+
if (!fs.existsSync(file)) return { version: FORMAT_VERSION, entries: [] };
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Feedback file ${file} is not valid JSON: ${error.message}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (parsed.version !== FORMAT_VERSION || !Array.isArray(parsed.entries)) {
|
|
55
|
+
throw new Error(`Unsupported feedback file format in ${file}`);
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Append one vote and write the file atomically (temp file, then rename).
|
|
62
|
+
*
|
|
63
|
+
* @param {string} file
|
|
64
|
+
* @param {{question: string, docId: string, rating: string}} vote
|
|
65
|
+
* @returns {{entry: object, total: number}}
|
|
66
|
+
*/
|
|
67
|
+
function addFeedback(file, { question, docId, rating }) {
|
|
68
|
+
if (!RATINGS.includes(rating)) {
|
|
69
|
+
throw new Error(`Rating must be one of: ${RATINGS.join(", ")}`);
|
|
70
|
+
}
|
|
71
|
+
if (!String(question || "").trim()) throw new Error("Question is empty");
|
|
72
|
+
const data = loadFeedback(file);
|
|
73
|
+
const entry = { question, docId, rating, at: new Date().toISOString() };
|
|
74
|
+
data.entries.push(entry);
|
|
75
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
76
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
77
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
78
|
+
fs.renameSync(tmp, file);
|
|
79
|
+
return { entry, total: data.entries.length };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Jaccard similarity of the two texts' token sets (0..1).
|
|
84
|
+
* @param {string} a
|
|
85
|
+
* @param {string} b
|
|
86
|
+
* @returns {number}
|
|
87
|
+
*/
|
|
88
|
+
function termSimilarity(a, b) {
|
|
89
|
+
const sa = new Set(tokenize(a));
|
|
90
|
+
const sb = new Set(tokenize(b));
|
|
91
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
92
|
+
let shared = 0;
|
|
93
|
+
for (const t of sa) if (sb.has(t)) shared++;
|
|
94
|
+
return shared / (sa.size + sb.size - shared);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Re-score retrieval results with stored feedback.
|
|
99
|
+
*
|
|
100
|
+
* @param {Array<{id: string, score: number}>} results - Retrieval results
|
|
101
|
+
* @param {Array<{question: string, docId: string, rating: string}>} entries - Stored votes
|
|
102
|
+
* @param {string} question - Current question
|
|
103
|
+
* @param {object} [options]
|
|
104
|
+
* @param {number} [options.voteWeight=VOTE_WEIGHT]
|
|
105
|
+
* @param {number} [options.maxAdjustment=MAX_ADJUSTMENT]
|
|
106
|
+
* @returns {Array<object>} Results sorted by finalScore (ties keep retrieval
|
|
107
|
+
* order), each with baseScore, adjustment, finalScore and `reasons`.
|
|
108
|
+
*/
|
|
109
|
+
function applyFeedback(results, entries, question, options = {}) {
|
|
110
|
+
const voteWeight = options.voteWeight ?? VOTE_WEIGHT;
|
|
111
|
+
const maxAdjustment = options.maxAdjustment ?? MAX_ADJUSTMENT;
|
|
112
|
+
|
|
113
|
+
const scored = results.map((result, rank) => {
|
|
114
|
+
const reasons = [];
|
|
115
|
+
let raw = 0;
|
|
116
|
+
for (const vote of entries) {
|
|
117
|
+
if (vote.docId !== result.id) continue;
|
|
118
|
+
const similarity = termSimilarity(question, vote.question);
|
|
119
|
+
if (similarity === 0) continue;
|
|
120
|
+
const contribution =
|
|
121
|
+
(vote.rating === "up" ? 1 : -1) * voteWeight * similarity;
|
|
122
|
+
raw += contribution;
|
|
123
|
+
reasons.push({
|
|
124
|
+
rating: vote.rating,
|
|
125
|
+
question: vote.question,
|
|
126
|
+
similarity,
|
|
127
|
+
contribution,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const adjustment = Math.max(-maxAdjustment, Math.min(maxAdjustment, raw));
|
|
131
|
+
const baseScore = typeof result.score === "number" ? result.score : 0;
|
|
132
|
+
return {
|
|
133
|
+
...result,
|
|
134
|
+
baseScore,
|
|
135
|
+
adjustment,
|
|
136
|
+
finalScore: baseScore + adjustment,
|
|
137
|
+
reasons,
|
|
138
|
+
rank,
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
scored.sort((a, b) => b.finalScore - a.finalScore || a.rank - b.rank);
|
|
143
|
+
return scored.map(({ rank, ...rest }) => rest);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
feedbackPath,
|
|
148
|
+
loadFeedback,
|
|
149
|
+
addFeedback,
|
|
150
|
+
termSimilarity,
|
|
151
|
+
applyFeedback,
|
|
152
|
+
RATINGS,
|
|
153
|
+
VOTE_WEIGHT,
|
|
154
|
+
MAX_ADJUSTMENT,
|
|
155
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ingest and query operations shared by the `ingest`, `query` and `run` CLI
|
|
5
|
+
* commands (and the dev server). Components come from the .ragrc.json
|
|
6
|
+
* pipeline via buildComponents(); the retriever must persist between
|
|
7
|
+
* processes for ingest and query to be separate commands (file-store does).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { buildComponents } = require("../../config/component-factory");
|
|
11
|
+
const { readDocuments } = require("../../loader/document-reader");
|
|
12
|
+
const { embedQuery } = require("../../utils/embed-query");
|
|
13
|
+
const { createRagPipeline } = require("../../core/create-pipeline");
|
|
14
|
+
|
|
15
|
+
function requireStage(components, stage) {
|
|
16
|
+
if (!components[stage]) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`No ${stage} configured. Add a { "stage": "${stage}", ... } entry to the pipeline in .ragrc.json.`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return components[stage];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Embed document text with embed() (not embedQuery), normalizing the shape. */
|
|
25
|
+
function embedDocument(embedder, text) {
|
|
26
|
+
return embedQuery({ embed: (t) => embedder.embed(t) }, text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Restore embedder state saved with the index (e.g. LocalEmbedder's
|
|
31
|
+
* vocabulary), so queries embed into the same vector space as ingestion.
|
|
32
|
+
*/
|
|
33
|
+
function restoreEmbedder(embedder, retriever) {
|
|
34
|
+
const info = retriever.getEmbedderInfo?.();
|
|
35
|
+
if (info?.state && typeof embedder.setState === "function") {
|
|
36
|
+
embedder.setState(info.state);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Embed entries with the (already trained/restored) embedder, store them and
|
|
42
|
+
* record the embedder metadata and state with the index.
|
|
43
|
+
*
|
|
44
|
+
* @returns {Promise<Array<{id: string, vector: number[]}>>} The stored vectors
|
|
45
|
+
*/
|
|
46
|
+
async function embedAndStore({
|
|
47
|
+
embedder,
|
|
48
|
+
embedderName,
|
|
49
|
+
store,
|
|
50
|
+
entries,
|
|
51
|
+
previous,
|
|
52
|
+
}) {
|
|
53
|
+
const vectors = [];
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
vectors.push({
|
|
56
|
+
...entry,
|
|
57
|
+
vector: await embedDocument(embedder, entry.content),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const dimensions = vectors[0]?.vector?.length ?? previous?.dimensions ?? 0;
|
|
61
|
+
if (vectors.some((v) => v.vector.length !== dimensions)) {
|
|
62
|
+
throw new Error("Embedder returned vectors of inconsistent dimensions");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await store.store(vectors);
|
|
66
|
+
store.setEmbedderInfo?.({
|
|
67
|
+
name: embedderName,
|
|
68
|
+
dimensions,
|
|
69
|
+
...(typeof embedder.getState === "function" && {
|
|
70
|
+
state: embedder.getState(),
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
store.save?.();
|
|
74
|
+
return vectors;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load documents from a file or directory, embed their chunks and store them.
|
|
79
|
+
* Re-ingesting a source replaces its previous chunks.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} params
|
|
82
|
+
* @param {object} params.config - Normalized config
|
|
83
|
+
* @param {string} params.configDir - Directory of the config file
|
|
84
|
+
* @param {string} params.inputPath - File or directory to ingest
|
|
85
|
+
* @param {boolean} [params.rebuild=false] - Discard the existing index first
|
|
86
|
+
* @returns {Promise<{files: number, chunks: number, totalChunks: number, sources: string[]}>}
|
|
87
|
+
*/
|
|
88
|
+
async function ingest({ config, configDir, inputPath, rebuild = false }) {
|
|
89
|
+
const components = buildComponents(config, { configDir });
|
|
90
|
+
const embedder = requireStage(components, "embedder");
|
|
91
|
+
const store = requireStage(components, "retriever");
|
|
92
|
+
if (typeof store.store !== "function") {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Retriever "${components.names.retriever}" cannot store documents. Use "file-store".`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const embedderName = components.names.embedder;
|
|
99
|
+
if (rebuild) store.clear?.();
|
|
100
|
+
const previous = store.getEmbedderInfo?.();
|
|
101
|
+
if (previous && previous.name !== embedderName && store.size() > 0) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`The index was built with embedder "${previous.name}" but the config now uses ` +
|
|
104
|
+
`"${embedderName}". Re-run with --rebuild to re-index everything.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const documents = await readDocuments(inputPath, { baseDir: configDir });
|
|
109
|
+
for (const { source } of documents) store.removeBySource?.(source);
|
|
110
|
+
|
|
111
|
+
let entries = documents.flatMap(({ source, chunks }) =>
|
|
112
|
+
chunks.map((content, i) => ({
|
|
113
|
+
id: `${source}#${i}`,
|
|
114
|
+
content,
|
|
115
|
+
metadata: { source, chunk: i },
|
|
116
|
+
})),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Corpus-fitted embedders (LocalEmbedder) must be retrained on everything
|
|
120
|
+
// stored, and every stored chunk re-embedded into the new vector space.
|
|
121
|
+
if (typeof embedder.train === "function") {
|
|
122
|
+
entries = [...(store.list?.() || []), ...entries];
|
|
123
|
+
embedder.train(entries.map((e) => e.content));
|
|
124
|
+
} else {
|
|
125
|
+
restoreEmbedder(embedder, store);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const vectors = await embedAndStore({
|
|
129
|
+
embedder,
|
|
130
|
+
embedderName,
|
|
131
|
+
store,
|
|
132
|
+
entries,
|
|
133
|
+
previous,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
files: documents.length,
|
|
138
|
+
chunks: documents.reduce((n, d) => n + d.chunks.length, 0),
|
|
139
|
+
totalChunks: store.size?.() ?? vectors.length,
|
|
140
|
+
sources: documents.map((d) => d.source),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Refit a corpus-trained embedder (only the built-in `local` TF-IDF embedder)
|
|
146
|
+
* and re-embed every stored chunk with the new model.
|
|
147
|
+
*
|
|
148
|
+
* Without `inputPath` the embedder is fitted on the indexed chunks. With it,
|
|
149
|
+
* the embedder is fitted on the documents read from that path only (they are
|
|
150
|
+
* not added to the index); indexed chunks that share no term with the new
|
|
151
|
+
* vocabulary embed to all-zero vectors and are counted in `zeroVectors`.
|
|
152
|
+
*
|
|
153
|
+
* @param {object} params
|
|
154
|
+
* @param {object} params.config
|
|
155
|
+
* @param {string} params.configDir
|
|
156
|
+
* @param {string} [params.inputPath] - Optional training corpus
|
|
157
|
+
* @returns {Promise<{trainedOn: string, trainingDocuments: number, vocabularySize: number,
|
|
158
|
+
* previousVocabularySize: number|null, reembedded: number, zeroVectors: number}>}
|
|
159
|
+
*/
|
|
160
|
+
async function retrain({ config, configDir, inputPath }) {
|
|
161
|
+
// Check the configured name before building components, so a hosted
|
|
162
|
+
// embedder without credentials still gets this explanation.
|
|
163
|
+
const entry = (config.pipeline || []).find((e) => e.stage === "embedder");
|
|
164
|
+
if (!entry) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
'No embedder configured. Add { "stage": "embedder", "name": "local" } to the pipeline in .ragrc.json.',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (entry.name !== "local") {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Training is only supported for the built-in "local" (TF-IDF) embedder; ` +
|
|
172
|
+
`this pipeline uses "${entry.name}". Hosted embedders (openai, cohere, ollama) ` +
|
|
173
|
+
"use fixed pre-trained models and cannot be fitted to your corpus.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const components = buildComponents(config, { configDir });
|
|
178
|
+
const embedderName = components.names.embedder;
|
|
179
|
+
const embedder = requireStage(components, "embedder");
|
|
180
|
+
const store = requireStage(components, "retriever");
|
|
181
|
+
if (typeof store.list !== "function" || typeof store.save !== "function") {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Retriever "${components.names.retriever}" does not persist an index. Use "file-store".`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stored = store.list();
|
|
188
|
+
if (stored.length === 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"The index is empty. Run `rag-pipeline ingest <path>` first.",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const previous = store.getEmbedderInfo?.();
|
|
194
|
+
if (previous && previous.name !== embedderName) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`The index was built with embedder "${previous.name}". ` +
|
|
197
|
+
"Re-run `rag-pipeline ingest <path> --rebuild` with the local embedder first.",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let corpus = stored.map((e) => e.content);
|
|
202
|
+
let trainedOn = "index";
|
|
203
|
+
if (inputPath) {
|
|
204
|
+
const documents = await readDocuments(inputPath, { baseDir: configDir });
|
|
205
|
+
corpus = documents.flatMap((d) => d.chunks);
|
|
206
|
+
if (corpus.length === 0) {
|
|
207
|
+
throw new Error(`No text found to train on in ${inputPath}`);
|
|
208
|
+
}
|
|
209
|
+
trainedOn = inputPath;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
embedder.train(corpus);
|
|
213
|
+
const vectors = await embedAndStore({
|
|
214
|
+
embedder,
|
|
215
|
+
embedderName,
|
|
216
|
+
store,
|
|
217
|
+
entries: stored,
|
|
218
|
+
previous,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
trainedOn,
|
|
223
|
+
trainingDocuments: corpus.length,
|
|
224
|
+
vocabularySize: embedder.getVocabularySize?.() ?? embedder.dimensions,
|
|
225
|
+
previousVocabularySize: previous?.dimensions ?? null,
|
|
226
|
+
reembedded: vectors.length,
|
|
227
|
+
zeroVectors: vectors.filter((v) => v.vector.every((x) => x === 0)).length,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build the configured components once and restore the embedder state saved
|
|
233
|
+
* with the index, so they can be reused across several retrieve()/query()
|
|
234
|
+
* calls (e.g. by `ai benchmark`).
|
|
235
|
+
*
|
|
236
|
+
* @returns {object} Components as returned by buildComponents()
|
|
237
|
+
*/
|
|
238
|
+
function loadComponents({ config, configDir }) {
|
|
239
|
+
const components = buildComponents(config, { configDir });
|
|
240
|
+
const embedder = requireStage(components, "embedder");
|
|
241
|
+
const retriever = requireStage(components, "retriever");
|
|
242
|
+
if (retriever.size && retriever.size() === 0) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
"The index is empty. Run `rag-pipeline ingest <path>` first.",
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
restoreEmbedder(embedder, retriever);
|
|
248
|
+
return components;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Retrieval only (no llm): embed the question, retrieve and, if configured,
|
|
253
|
+
* rerank.
|
|
254
|
+
*
|
|
255
|
+
* @param {object} params
|
|
256
|
+
* @param {object} [params.components] - Prebuilt components (loadComponents)
|
|
257
|
+
* @param {object} [params.config]
|
|
258
|
+
* @param {string} [params.configDir]
|
|
259
|
+
* @param {string} params.question
|
|
260
|
+
* @param {number} [params.topK=3]
|
|
261
|
+
* @returns {Promise<Array<{id: string, score: number, content: string, metadata: object}>>}
|
|
262
|
+
*/
|
|
263
|
+
async function retrieve({ components, config, configDir, question, topK = 3 }) {
|
|
264
|
+
const c = components || loadComponents({ config, configDir });
|
|
265
|
+
const queryVector = await embedQuery(c.embedder, question);
|
|
266
|
+
let results = await c.retriever.retrieve({
|
|
267
|
+
query: question,
|
|
268
|
+
queryVector,
|
|
269
|
+
topK,
|
|
270
|
+
});
|
|
271
|
+
if (c.reranker) {
|
|
272
|
+
results = await c.reranker.rerank(question, results, { topK });
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Answer a question from the index. Without an llm stage this returns the
|
|
279
|
+
* retrieved passages only (answer: null) instead of a placeholder answer.
|
|
280
|
+
*
|
|
281
|
+
* @param {object} params
|
|
282
|
+
* @param {object} params.config
|
|
283
|
+
* @param {string} params.configDir
|
|
284
|
+
* @param {string} params.question
|
|
285
|
+
* @param {number} [params.topK=3]
|
|
286
|
+
* @param {object} [params.components] - Prebuilt components (loadComponents)
|
|
287
|
+
* @returns {Promise<{answer: string|null, results: Array, citations?: object}>}
|
|
288
|
+
*/
|
|
289
|
+
async function query({ config, configDir, question, topK = 3, components }) {
|
|
290
|
+
const c = components || loadComponents({ config, configDir });
|
|
291
|
+
|
|
292
|
+
if (!c.llm) {
|
|
293
|
+
const results = await retrieve({ components: c, question, topK });
|
|
294
|
+
return { answer: null, results };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const pipeline = createRagPipeline({
|
|
298
|
+
embedder: c.embedder,
|
|
299
|
+
retriever: c.retriever,
|
|
300
|
+
llm: c.llm,
|
|
301
|
+
reranker: c.reranker,
|
|
302
|
+
});
|
|
303
|
+
const result = await pipeline.run({
|
|
304
|
+
query: question,
|
|
305
|
+
options: { topK, citations: true },
|
|
306
|
+
});
|
|
307
|
+
if (!result.success) throw new Error(result.error);
|
|
308
|
+
return {
|
|
309
|
+
answer: result.answer,
|
|
310
|
+
results: result.results,
|
|
311
|
+
citations: result.citations,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
ingest,
|
|
317
|
+
retrain,
|
|
318
|
+
query,
|
|
319
|
+
retrieve,
|
|
320
|
+
loadComponents,
|
|
321
|
+
restoreEmbedder,
|
|
322
|
+
};
|
|
@@ -28,6 +28,23 @@ function parsePositiveInteger(value) {
|
|
|
28
28
|
return parsed;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Top-level commands offered by shell completion
|
|
32
|
+
const COMMANDS = [
|
|
33
|
+
"init",
|
|
34
|
+
"ingest",
|
|
35
|
+
"query",
|
|
36
|
+
"run",
|
|
37
|
+
"serve",
|
|
38
|
+
"doctor",
|
|
39
|
+
"validate",
|
|
40
|
+
"hub",
|
|
41
|
+
"ai",
|
|
42
|
+
"plugin",
|
|
43
|
+
"config",
|
|
44
|
+
"info",
|
|
45
|
+
"completion",
|
|
46
|
+
].join(" ");
|
|
47
|
+
|
|
31
48
|
/**
|
|
32
49
|
* Generate shell completion script
|
|
33
50
|
*/
|
|
@@ -36,7 +53,7 @@ function generateCompletionScript(shell) {
|
|
|
36
53
|
case "bash":
|
|
37
54
|
return `# RAG Pipeline bash completion
|
|
38
55
|
_rag_pipeline_completions() {
|
|
39
|
-
COMPREPLY=($(compgen -W "
|
|
56
|
+
COMPREPLY=($(compgen -W "${COMMANDS}" -- "\${COMP_WORDS[COMP_CWORD]}"))
|
|
40
57
|
}
|
|
41
58
|
complete -F _rag_pipeline_completions rag-pipeline`;
|
|
42
59
|
|
|
@@ -45,13 +62,13 @@ complete -F _rag_pipeline_completions rag-pipeline`;
|
|
|
45
62
|
#compdef rag-pipeline
|
|
46
63
|
_rag_pipeline() {
|
|
47
64
|
_arguments \\
|
|
48
|
-
'1:command:(
|
|
65
|
+
'1:command:(${COMMANDS})'
|
|
49
66
|
}
|
|
50
67
|
_rag_pipeline "$@"`;
|
|
51
68
|
|
|
52
69
|
case "fish":
|
|
53
70
|
return `# RAG Pipeline fish completion
|
|
54
|
-
complete -c rag-pipeline -n '__fish_use_subcommand' -a '
|
|
71
|
+
complete -c rag-pipeline -n '__fish_use_subcommand' -a '${COMMANDS}'`;
|
|
55
72
|
|
|
56
73
|
default:
|
|
57
74
|
return `# Completion not available for ${shell}`;
|
|
@@ -71,10 +88,10 @@ function getVersion() {
|
|
|
71
88
|
function getExtendedHelp() {
|
|
72
89
|
return `
|
|
73
90
|
Examples:
|
|
74
|
-
rag-pipeline init
|
|
75
|
-
rag-pipeline
|
|
76
|
-
rag-pipeline
|
|
77
|
-
rag-pipeline config show
|
|
91
|
+
rag-pipeline init Create .ragrc.json (prompts in a terminal)
|
|
92
|
+
rag-pipeline ingest ./docs Index a file or directory
|
|
93
|
+
rag-pipeline query "What is X?" Ask a question about the indexed documents
|
|
94
|
+
rag-pipeline config show Show the current configuration
|
|
78
95
|
|
|
79
96
|
For more information, visit: https://github.com/DevilsDev/rag-pipeline-utils
|
|
80
97
|
`;
|