@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
|
@@ -1,23 +1,161 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
2
|
+
// Share the exported singleton so plugins registered via
|
|
3
|
+
// `pluginRegistry.register()` are resolvable by name here.
|
|
4
|
+
const defaultRegistry = require('./plugin-registry.js');
|
|
5
|
+
const { embedQuery, canEmbed } = require('../utils/embed-query');
|
|
6
|
+
const {
|
|
7
|
+
normalizeCacheOption,
|
|
8
|
+
PipelineCache,
|
|
9
|
+
} = require('../cache/pipeline-cache');
|
|
10
|
+
const { createRunAbort } = require('../utils/run-abort');
|
|
3
11
|
|
|
4
|
-
|
|
12
|
+
/** Metrics that only need the query, the retrieved docs and a reference. */
|
|
13
|
+
const RETRIEVAL_METRICS = ['contextPrecision', 'contextRecall'];
|
|
5
14
|
|
|
6
15
|
function resolve(registry, type, value) {
|
|
7
16
|
const reg = registry || defaultRegistry;
|
|
8
17
|
return typeof value === 'string' ? reg.get(type, value) : value;
|
|
9
18
|
}
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Citations and evaluation for one run. `answer` is null when no llm is
|
|
22
|
+
* configured: citations are then skipped and evaluation is limited to the
|
|
23
|
+
* retrieval metrics (context precision, and context recall when a reference
|
|
24
|
+
* answer is given).
|
|
25
|
+
*
|
|
26
|
+
* @param {object} params
|
|
27
|
+
* @param {string} params.query
|
|
28
|
+
* @param {string|null} params.answer
|
|
29
|
+
* @param {Array<object>} params.results
|
|
30
|
+
* @param {object} params.options - Run options
|
|
31
|
+
* @returns {{ citations?: object, evaluation?: object }}
|
|
32
|
+
*/
|
|
33
|
+
function computeQuality({ query, answer, results, options }) {
|
|
34
|
+
const out = {};
|
|
35
|
+
const docs = Array.isArray(results) ? results : [];
|
|
36
|
+
|
|
37
|
+
if (options.citations && answer !== null) {
|
|
38
|
+
const { CitationTracker } = require('../citation/citation-tracker');
|
|
39
|
+
const tracker = new CitationTracker(options.citationOptions || {});
|
|
40
|
+
out.citations = tracker.track(answer, docs);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options.evaluate) {
|
|
44
|
+
const {
|
|
45
|
+
PipelineEvaluator,
|
|
46
|
+
DEFAULT_CONFIG,
|
|
47
|
+
} = require('../evaluate/pipeline-evaluator');
|
|
48
|
+
const evaluateOptions = options.evaluateOptions || {};
|
|
49
|
+
let config = evaluateOptions;
|
|
50
|
+
if (answer === null) {
|
|
51
|
+
const metrics = evaluateOptions.metrics || DEFAULT_CONFIG.metrics;
|
|
52
|
+
config = {
|
|
53
|
+
...evaluateOptions,
|
|
54
|
+
metrics: metrics.filter((m) => RETRIEVAL_METRICS.includes(m)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const evaluator = new PipelineEvaluator(config);
|
|
58
|
+
out.evaluation = evaluator.evaluate(
|
|
59
|
+
{ query, answer: answer ?? '', results: docs },
|
|
60
|
+
{
|
|
61
|
+
citationResult: out.citations,
|
|
62
|
+
referenceAnswer:
|
|
63
|
+
options.referenceAnswer ?? evaluateOptions.referenceAnswer,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pass a token stream through, stopping when the run's signal aborts. When
|
|
73
|
+
* citations or evaluation were requested, the streamed answer is accumulated
|
|
74
|
+
* and the stream ends with one final chunk
|
|
75
|
+
* `{ token: '', done: true, citations?, evaluation? }` (the source's own done
|
|
76
|
+
* chunk is replaced by it; a token it carried is emitted first).
|
|
77
|
+
*
|
|
78
|
+
* @param {AsyncIterable<object|string>} source
|
|
79
|
+
* @param {object} params
|
|
80
|
+
* @param {AbortSignal} params.signal
|
|
81
|
+
* @param {((text: string) => object)|null} params.finalize
|
|
82
|
+
* @param {() => void} params.onEnd
|
|
83
|
+
*/
|
|
84
|
+
async function* guardStream(source, { signal, finalize, onEnd }) {
|
|
85
|
+
try {
|
|
86
|
+
let text = '';
|
|
87
|
+
for await (const chunk of source) {
|
|
88
|
+
if (signal.aborted) throw signal.reason;
|
|
89
|
+
if (!finalize) {
|
|
90
|
+
yield chunk;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const token =
|
|
94
|
+
typeof chunk === 'string' ? chunk : String(chunk?.token ?? '');
|
|
95
|
+
text += token;
|
|
96
|
+
if (chunk && chunk.done) {
|
|
97
|
+
if (token) yield { ...chunk, token, done: false };
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
yield chunk;
|
|
101
|
+
}
|
|
102
|
+
if (signal.aborted) throw signal.reason;
|
|
103
|
+
if (finalize) yield { token: '', done: true, ...finalize(text) };
|
|
104
|
+
} finally {
|
|
105
|
+
onEnd();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Pass a stream through and report its text when it completed: it reached a
|
|
111
|
+
* done chunk or its end, and the run was not aborted. A stream the consumer
|
|
112
|
+
* stops reading early is not reported, so a partial answer is never cached.
|
|
113
|
+
*/
|
|
114
|
+
async function* reportWhenComplete(source, signal, onComplete) {
|
|
115
|
+
let text = '';
|
|
116
|
+
let completed = false;
|
|
117
|
+
try {
|
|
118
|
+
for await (const chunk of source) {
|
|
119
|
+
text += typeof chunk === 'string' ? chunk : String(chunk?.token ?? '');
|
|
120
|
+
if (chunk && chunk.done) completed = true;
|
|
121
|
+
yield chunk;
|
|
122
|
+
}
|
|
123
|
+
completed = true;
|
|
124
|
+
} finally {
|
|
125
|
+
if (completed && !signal.aborted) await onComplete(text);
|
|
126
|
+
}
|
|
19
127
|
}
|
|
20
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Create a RAG pipeline: (embed) -> retrieve -> (filter) -> (rerank) ->
|
|
131
|
+
* generate -> (citations, evaluation).
|
|
132
|
+
*
|
|
133
|
+
* `run({ query, queryVector, options })` options:
|
|
134
|
+
* - `topK` (default 3)
|
|
135
|
+
* - `timeout` (ms): aborts the run; resolves `{ success: false, error: 'Execution timeout' }`
|
|
136
|
+
* - `signal` (AbortSignal): caller cancellation; resolves
|
|
137
|
+
* `{ success: false, error: 'Execution aborted' }` when it aborts
|
|
138
|
+
* - `stream`: return an async iterable of `{ token, done }` chunks
|
|
139
|
+
* - `citations`, `citationOptions`, `evaluate`, `evaluateOptions`,
|
|
140
|
+
* `referenceAnswer` (ground truth for context recall)
|
|
141
|
+
* - `filterResults(docs)`: hook applied after retrieval
|
|
142
|
+
*
|
|
143
|
+
* `cache` (factory option): `true`, a CacheBackend, or
|
|
144
|
+
* `{ embeddings, retrieval, generation, ttlSeconds }` - see
|
|
145
|
+
* src/cache/pipeline-cache.js. Results then carry `cache: { embeddings,
|
|
146
|
+
* retrieval, generation }` ('hit', 'miss' or 'skip'), and the pipeline has
|
|
147
|
+
* `cacheStats()`.
|
|
148
|
+
*
|
|
149
|
+
* Every component call receives the run's AbortSignal: `retriever.retrieve({
|
|
150
|
+
* ..., signal })`, `embedder.embedQuery(text, { signal })` /
|
|
151
|
+
* `embedder.embed(text, { signal })`, `reranker.rerank(query, docs, { topK,
|
|
152
|
+
* signal })` and `llm.generate(query, docs, { ...options, signal })`.
|
|
153
|
+
* Components that ignore it still work; the run settles on abort anyway and
|
|
154
|
+
* no later stage starts.
|
|
155
|
+
*
|
|
156
|
+
* Without an llm the pipeline is retrieval-only: `answer` is null, citations
|
|
157
|
+
* are skipped and evaluation covers the retrieval metrics only.
|
|
158
|
+
*/
|
|
21
159
|
module.exports.createRagPipeline = function createRagPipeline({
|
|
22
160
|
registry,
|
|
23
161
|
loader,
|
|
@@ -25,6 +163,7 @@ module.exports.createRagPipeline = function createRagPipeline({
|
|
|
25
163
|
retriever,
|
|
26
164
|
llm,
|
|
27
165
|
reranker,
|
|
166
|
+
cache,
|
|
28
167
|
} = {}) {
|
|
29
168
|
const reg = registry || defaultRegistry;
|
|
30
169
|
const loaderI = loader ? resolve(reg, 'loader', loader) : null;
|
|
@@ -36,84 +175,191 @@ module.exports.createRagPipeline = function createRagPipeline({
|
|
|
36
175
|
return [];
|
|
37
176
|
},
|
|
38
177
|
};
|
|
39
|
-
const llmI = llm
|
|
40
|
-
? resolve(reg, 'llm', llm)
|
|
41
|
-
: {
|
|
42
|
-
async generate() {
|
|
43
|
-
return 'ok';
|
|
44
|
-
},
|
|
45
|
-
};
|
|
178
|
+
const llmI = llm ? resolve(reg, 'llm', llm) : null;
|
|
46
179
|
const rerankerI = reranker ? resolve(reg, 'reranker', reranker) : null;
|
|
47
180
|
|
|
48
|
-
|
|
49
|
-
|
|
181
|
+
const cacheLayers = normalizeCacheOption(cache);
|
|
182
|
+
const pc = cacheLayers
|
|
183
|
+
? new PipelineCache(cacheLayers, {
|
|
184
|
+
embedder: embedderI,
|
|
185
|
+
// The default stand-in retriever returns nothing; never cache it.
|
|
186
|
+
retriever: retriever ? retrieverI : null,
|
|
187
|
+
llm: llmI,
|
|
188
|
+
})
|
|
189
|
+
: null;
|
|
190
|
+
|
|
191
|
+
async function runOnce({ query, queryVector, options = {} } = {}) {
|
|
192
|
+
const opts = options || {};
|
|
193
|
+
const { timeout, stream } = opts;
|
|
194
|
+
const run = createRunAbort({ timeout, signal: opts.signal });
|
|
195
|
+
const { signal } = run;
|
|
196
|
+
let streaming = false;
|
|
197
|
+
|
|
198
|
+
// Per-run cache outcome per layer: 'hit', 'miss' or 'skip'.
|
|
199
|
+
const cacheStatus = {};
|
|
200
|
+
const withCache = (result) =>
|
|
201
|
+
pc ? { ...result, cache: { ...cacheStatus } } : result;
|
|
202
|
+
|
|
50
203
|
const doWork = (async () => {
|
|
51
|
-
|
|
52
|
-
if (
|
|
204
|
+
run.throwIfAborted();
|
|
205
|
+
if (loaderI?.load) {
|
|
206
|
+
await loaderI.load();
|
|
207
|
+
run.throwIfAborted();
|
|
208
|
+
}
|
|
209
|
+
let vector = queryVector;
|
|
210
|
+
if (!vector && query && canEmbed(embedderI)) {
|
|
211
|
+
const key = pc && pc.embeddingKey(query);
|
|
212
|
+
vector = pc && (await pc.get('embeddings', key, cacheStatus));
|
|
213
|
+
if (!vector) {
|
|
214
|
+
vector = await embedQuery(embedderI, query, { signal });
|
|
215
|
+
run.throwIfAborted();
|
|
216
|
+
if (pc) await pc.set('embeddings', key, vector);
|
|
217
|
+
}
|
|
218
|
+
run.throwIfAborted();
|
|
219
|
+
}
|
|
53
220
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
221
|
+
// Raw retriever output is cached before filterResults (guardrail ACLs)
|
|
222
|
+
// and the reranker, so both still run on every request.
|
|
223
|
+
const topK = opts.topK ?? 3;
|
|
224
|
+
const retrievalKey =
|
|
225
|
+
pc && pc.retrievalKey({ query, queryVector: vector, topK });
|
|
226
|
+
let docs = pc && (await pc.get('retrieval', retrievalKey, cacheStatus));
|
|
227
|
+
if (!docs) {
|
|
228
|
+
docs = await retrieverI.retrieve({
|
|
229
|
+
query,
|
|
230
|
+
queryVector: vector,
|
|
231
|
+
topK,
|
|
232
|
+
signal,
|
|
233
|
+
});
|
|
234
|
+
run.throwIfAborted();
|
|
235
|
+
if (pc && Array.isArray(docs)) {
|
|
236
|
+
await pc.set('retrieval', retrievalKey, docs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
run.throwIfAborted();
|
|
240
|
+
// Hook for filtering (e.g. ACL guardrails) before anything downstream,
|
|
241
|
+
// including the LLM, sees the retrieved documents.
|
|
242
|
+
if (typeof opts.filterResults === 'function' && Array.isArray(docs)) {
|
|
243
|
+
docs = await opts.filterResults(docs);
|
|
244
|
+
run.throwIfAborted();
|
|
245
|
+
}
|
|
59
246
|
let results = docs;
|
|
60
247
|
if (rerankerI?.rerank && Array.isArray(docs) && docs.length) {
|
|
61
248
|
results = await rerankerI.rerank(query || '', docs, {
|
|
62
|
-
topK:
|
|
249
|
+
topK: opts.topK ?? docs.length,
|
|
250
|
+
signal,
|
|
63
251
|
});
|
|
252
|
+
run.throwIfAborted();
|
|
64
253
|
}
|
|
65
254
|
|
|
255
|
+
const genOptions = { ...opts, signal };
|
|
256
|
+
const wantsQuality = Boolean(opts.citations || opts.evaluate);
|
|
257
|
+
const generationKey =
|
|
258
|
+
pc && llmI
|
|
259
|
+
? pc.generationKey({ query, docs: results, options: opts })
|
|
260
|
+
: null;
|
|
261
|
+
const cachedAnswer =
|
|
262
|
+
pc && llmI
|
|
263
|
+
? await pc.get('generation', generationKey, cacheStatus)
|
|
264
|
+
: undefined;
|
|
265
|
+
run.throwIfAborted();
|
|
266
|
+
|
|
66
267
|
if (stream) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
268
|
+
let source;
|
|
269
|
+
let finalize = wantsQuality
|
|
270
|
+
? (text) =>
|
|
271
|
+
computeQuality({ query, answer: text, results, options: opts })
|
|
272
|
+
: null;
|
|
273
|
+
if (!llmI) {
|
|
274
|
+
// Retrieval only: no tokens, one done chunk carrying the results.
|
|
275
|
+
const quality = computeQuality({
|
|
276
|
+
query,
|
|
277
|
+
answer: null,
|
|
278
|
+
results,
|
|
279
|
+
options: opts,
|
|
280
|
+
});
|
|
281
|
+
source = (async function* noAnswer() {})();
|
|
282
|
+
finalize = () => ({ results, ...quality });
|
|
283
|
+
} else if (typeof cachedAnswer === 'string') {
|
|
284
|
+
source = (async function* cached() {
|
|
285
|
+
yield { token: cachedAnswer, done: false };
|
|
286
|
+
yield { token: '', done: true };
|
|
287
|
+
})();
|
|
288
|
+
} else if (llmI.generateStream) {
|
|
289
|
+
source = await llmI.generateStream(query, results, genOptions);
|
|
71
290
|
} else if (llmI.generate) {
|
|
72
291
|
// Fallback: simulate streaming from regular generate
|
|
73
|
-
const answer =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
: [''];
|
|
78
|
-
for (const c of chunks) yield { token: c, done: false };
|
|
292
|
+
const answer =
|
|
293
|
+
(await llmI.generate(query, results, genOptions)) ?? '';
|
|
294
|
+
source = (async function* gen() {
|
|
295
|
+
yield { token: String(answer || ''), done: false };
|
|
79
296
|
yield { token: '', done: true };
|
|
297
|
+
})();
|
|
298
|
+
}
|
|
299
|
+
if (source) {
|
|
300
|
+
run.throwIfAborted();
|
|
301
|
+
streaming = true;
|
|
302
|
+
if (pc && llmI && generationKey && typeof cachedAnswer !== 'string') {
|
|
303
|
+
source = reportWhenComplete(source, signal, (text) =>
|
|
304
|
+
pc.set('generation', generationKey, text),
|
|
305
|
+
);
|
|
80
306
|
}
|
|
81
|
-
return
|
|
307
|
+
return guardStream(source, {
|
|
308
|
+
signal,
|
|
309
|
+
finalize,
|
|
310
|
+
onEnd: run.detach,
|
|
311
|
+
});
|
|
82
312
|
}
|
|
83
313
|
}
|
|
84
314
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const tracker = new CitationTracker(options.citationOptions || {});
|
|
94
|
-
result.answer = String(answer);
|
|
95
|
-
result.citations = tracker.track(String(answer), results);
|
|
315
|
+
if (!llmI) {
|
|
316
|
+
return withCache({
|
|
317
|
+
success: true,
|
|
318
|
+
query,
|
|
319
|
+
results,
|
|
320
|
+
answer: null,
|
|
321
|
+
...computeQuality({ query, answer: null, results, options: opts }),
|
|
322
|
+
});
|
|
96
323
|
}
|
|
97
324
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
325
|
+
let answer;
|
|
326
|
+
if (typeof cachedAnswer === 'string') {
|
|
327
|
+
answer = cachedAnswer;
|
|
328
|
+
} else {
|
|
329
|
+
const raw = (await llmI.generate?.(query, results, genOptions)) ?? '';
|
|
330
|
+
run.throwIfAborted();
|
|
331
|
+
answer = String(raw);
|
|
332
|
+
if (pc) await pc.set('generation', generationKey, answer);
|
|
333
|
+
run.throwIfAborted();
|
|
106
334
|
}
|
|
107
335
|
|
|
108
|
-
return
|
|
336
|
+
return withCache({
|
|
337
|
+
success: true,
|
|
338
|
+
query,
|
|
339
|
+
results,
|
|
340
|
+
answer,
|
|
341
|
+
...computeQuality({ query, answer, results, options: opts }),
|
|
342
|
+
});
|
|
109
343
|
})();
|
|
110
344
|
|
|
111
345
|
try {
|
|
112
|
-
return await
|
|
346
|
+
return await run.race(doWork);
|
|
113
347
|
} catch (e) {
|
|
114
|
-
return { success: false, error: String(e
|
|
348
|
+
return { success: false, error: String(e?.message || e) };
|
|
349
|
+
} finally {
|
|
350
|
+
// The timeout covers the run up to the returned stream; a caller
|
|
351
|
+
// signal keeps cancelling a stream until it finishes.
|
|
352
|
+
run.clearTimer();
|
|
353
|
+
if (!streaming) run.detach();
|
|
115
354
|
}
|
|
116
355
|
}
|
|
117
356
|
|
|
118
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
run: runOnce,
|
|
359
|
+
cleanup() {},
|
|
360
|
+
/** Cache hits, misses and skips per layer; null when caching is off. */
|
|
361
|
+
cacheStats() {
|
|
362
|
+
return pc ? pc.stats() : null;
|
|
363
|
+
},
|
|
364
|
+
};
|
|
119
365
|
};
|
|
@@ -36,16 +36,77 @@ class Semaphore {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Built-in embedders whose embed() takes one string, not an array. */
|
|
40
|
+
function isSingleTextEmbedder(embedder) {
|
|
41
|
+
/* eslint-disable global-require */
|
|
42
|
+
const singleText = [
|
|
43
|
+
require('../../connectors/local-embedder').LocalEmbedder,
|
|
44
|
+
require('../../connectors/openai-connector').OpenAIConnector,
|
|
45
|
+
require('../../connectors/ollama-connector').OllamaConnector,
|
|
46
|
+
require('../../connectors/cohere-connector').CohereConnector,
|
|
47
|
+
];
|
|
48
|
+
/* eslint-enable global-require */
|
|
49
|
+
return singleText.some((Class) => Class && embedder instanceof Class);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const EMBED_MODES = ['auto', 'batch', 'single'];
|
|
53
|
+
|
|
39
54
|
/**
|
|
40
55
|
* Parallel processor for embedding operations with configurable concurrency
|
|
41
56
|
*/
|
|
42
57
|
class ParallelEmbedder {
|
|
58
|
+
/**
|
|
59
|
+
* @param {object} embedder - Embedder with embed() and/or embedBatch()
|
|
60
|
+
* @param {object} [_options]
|
|
61
|
+
* @param {number} [_options.batchSize=10] - Texts per batch
|
|
62
|
+
* @param {number} [_options.maxConcurrency=3] - Batches in flight at once
|
|
63
|
+
* @param {number} [_options.retryAttempts=2] - Retries per failed batch
|
|
64
|
+
* @param {number} [_options.retryDelay=1000] - Base retry delay in ms
|
|
65
|
+
* @param {'auto'|'batch'|'single'} [_options.mode='auto'] - How a batch is
|
|
66
|
+
* embedded: 'batch' calls embed(texts) and expects one vector per text,
|
|
67
|
+
* 'single' calls embed(text) for each text. 'auto' uses embedBatch(texts)
|
|
68
|
+
* when the embedder has it, 'single' for the built-in connectors (which
|
|
69
|
+
* embed one string at a time) and 'batch' otherwise.
|
|
70
|
+
*/
|
|
43
71
|
constructor(embedder, _options = {}) {
|
|
44
72
|
this.embedder = embedder;
|
|
45
73
|
this.batchSize = _options.batchSize || 10;
|
|
46
74
|
this.maxConcurrency = _options.maxConcurrency || 3;
|
|
47
75
|
this.retryAttempts = _options.retryAttempts || 2;
|
|
48
76
|
this.retryDelay = _options.retryDelay || 1000;
|
|
77
|
+
const mode = _options.mode || 'auto';
|
|
78
|
+
if (!EMBED_MODES.includes(mode)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid mode "${mode}". Expected one of: ${EMBED_MODES.join(', ')}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
this.mode = mode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Embed one batch according to the mode.
|
|
88
|
+
* @param {string[]} batch
|
|
89
|
+
* @returns {Promise<number[][]>}
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
async _embed(batch) {
|
|
93
|
+
const embedder = this.embedder;
|
|
94
|
+
if (this.mode === 'auto' && typeof embedder.embedBatch === 'function') {
|
|
95
|
+
return embedder.embedBatch(batch);
|
|
96
|
+
}
|
|
97
|
+
const single =
|
|
98
|
+
this.mode === 'single' ||
|
|
99
|
+
(this.mode === 'auto' && isSingleTextEmbedder(embedder));
|
|
100
|
+
if (!single) {
|
|
101
|
+
return embedder.embed(batch);
|
|
102
|
+
}
|
|
103
|
+
// One text at a time within a batch, so maxConcurrency still bounds the
|
|
104
|
+
// number of requests in flight.
|
|
105
|
+
const vectors = [];
|
|
106
|
+
for (const text of batch) {
|
|
107
|
+
vectors.push(await embedder.embed(text));
|
|
108
|
+
}
|
|
109
|
+
return vectors;
|
|
49
110
|
}
|
|
50
111
|
|
|
51
112
|
/**
|
|
@@ -133,7 +194,7 @@ class ParallelEmbedder {
|
|
|
133
194
|
|
|
134
195
|
for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {
|
|
135
196
|
try {
|
|
136
|
-
const result = await this.
|
|
197
|
+
const result = await this._embed(batch);
|
|
137
198
|
|
|
138
199
|
// Validate result
|
|
139
200
|
if (!Array.isArray(result) || result.length !== batch.length) {
|