@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.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/README.md +293 -369
- package/contracts/loader-contract.json +11 -6
- package/contracts/reranker-contract.json +18 -26
- package/dist/index.cjs +21190 -47
- package/dist/index.d.ts +298 -68
- package/dist/index.mjs +9 -76
- package/package.json +20 -8
- package/src/agentic/agentic-pipeline.js +235 -0
- package/src/agentic/iterative-retriever.js +213 -0
- package/src/agentic/query-planner.js +208 -0
- package/src/agentic/self-critique.js +102 -0
- package/src/ai/adaptive-retrieval.js +0 -3
- package/src/ai/federated-learning.js +0 -3
- package/src/ai/federation.js +1 -6
- package/src/ai/index.js +9 -20
- package/src/ai/model-training.js +0 -3
- package/src/ai/multimodal-processing.js +0 -3
- package/src/ai/multimodal.js +1 -6
- package/src/ai/orchestrator.js +1 -6
- package/src/ai/retrieval-engine.js +1 -6
- package/src/chunking/chunking-engine.js +142 -0
- package/src/chunking/strategies/fixed-size.js +54 -0
- package/src/chunking/strategies/recursive.js +127 -0
- package/src/chunking/strategies/semantic.js +128 -0
- package/src/chunking/strategies/sentence.js +32 -0
- package/src/chunking/strategies/structure-aware.js +138 -0
- package/src/citation/citation-tracker.js +161 -0
- package/src/citation/hallucination-detector.js +74 -0
- package/src/citation/source-mapper.js +95 -0
- package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
- package/src/cli/commands/ai-ml/federated-commands.js +207 -0
- package/src/cli/commands/ai-ml/index.js +17 -0
- package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
- package/src/cli/commands/ai-ml/shared.js +51 -0
- package/src/cli/commands/ai-ml/training-commands.js +228 -0
- package/src/cli/commands/ai-ml.js +176 -757
- package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
- package/src/cli/commands/plugin-hub/review-commands.js +78 -0
- package/src/cli/commands/plugin-hub.js +106 -782
- package/src/cli/diagnostics/config-checker.js +81 -0
- package/src/cli/diagnostics/dependency-checker.js +69 -0
- package/src/cli/diagnostics/environment-checker.js +45 -0
- package/src/cli/diagnostics/performance-checker.js +51 -0
- package/src/cli/diagnostics/plugin-checker.js +106 -0
- package/src/cli/diagnostics/security-checker.js +61 -0
- package/src/cli/doctor-command.js +70 -369
- package/src/cli/enhanced-cli-commands.js +162 -706
- package/src/cli/handlers/config-handler.js +126 -0
- package/src/cli/handlers/diagnostics-handler.js +113 -0
- package/src/cli/handlers/ingest-handler.js +42 -0
- package/src/cli/handlers/init-handler.js +51 -0
- package/src/cli/handlers/query-handler.js +45 -0
- package/src/cli/handlers/run-handler.js +181 -0
- package/src/cli/interactive-wizard.js +99 -723
- package/src/cli/shared/cli-utils.js +89 -0
- package/src/cli/wizard/configuration.js +148 -0
- package/src/cli/wizard/observability.js +79 -0
- package/src/cli/wizard/performance.js +76 -0
- package/src/cli/wizard/plugin-selection.js +297 -0
- package/src/cli/wizard/preview-save.js +172 -0
- package/src/cli/wizard/project-setup.js +66 -0
- package/src/config/memory-optimization.js +3 -3
- package/src/connectors/anthropic-connector.js +179 -0
- package/src/connectors/base-connector.js +94 -0
- package/src/connectors/cohere-connector.js +143 -0
- package/src/connectors/local-embedder.js +171 -0
- package/src/connectors/memory-retriever.js +150 -0
- package/src/connectors/ollama-connector.js +205 -0
- package/src/connectors/openai-connector.js +173 -0
- package/src/core/create-pipeline.js +21 -1
- package/src/core/observability/tracing.js +2 -1
- package/src/core/pipeline-factory.js +0 -1
- package/src/core/plugin-registry.js +14 -14
- package/src/cost/cost-calculator.js +146 -0
- package/src/cost/cost-tracker.js +260 -0
- package/src/cost/token-budget.js +245 -0
- package/src/dag/core/execution-scheduler.js +2 -1
- package/src/dag/core/topology-validation.js +0 -3
- package/src/dag/dag-engine.js +3 -5
- package/src/dag/dag-node.js +0 -1
- package/src/dag/enhanced-dag-engine.js +171 -99
- package/src/dashboard/dashboard-generator.js +642 -0
- package/src/dashboard/metrics-aggregator.js +409 -0
- package/src/debug/execution-tracer.js +210 -0
- package/src/debug/trace-inspector.js +281 -0
- package/src/dev/dev-server.js +5 -5
- package/src/dev/hot-reload.js +3 -2
- package/src/dx/integration-templates.js +0 -3
- package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
- package/src/ecosystem/plugin-hub.js +2 -2
- package/src/enterprise/audit-logging.js +2 -2
- package/src/enterprise/sso-integration.js +52 -32
- package/src/evaluate/context-metrics.js +127 -0
- package/src/evaluate/evaluator.js +4 -2
- package/src/evaluate/faithfulness.js +117 -0
- package/src/evaluate/groundedness.js +60 -0
- package/src/evaluate/pipeline-evaluator.js +156 -0
- package/src/evaluate/relevance.js +129 -0
- package/src/evaluate/scoring.js +27 -0
- package/src/graph/entity-extractor.js +183 -0
- package/src/graph/graph-index.js +113 -0
- package/src/graph/graph-retriever.js +244 -0
- package/src/graph/knowledge-graph.js +256 -0
- package/src/guardrails/guardrails-pipeline.js +136 -0
- package/src/guardrails/post-generation-guard.js +170 -0
- package/src/guardrails/pre-retrieval-guard.js +161 -0
- package/src/guardrails/retrieval-guard.js +126 -0
- package/src/index.js +255 -9
- package/src/loader/html-loader.js +4 -15
- package/src/loader/markdown-loader.js +4 -15
- package/src/mcp/mcp-server.js +199 -0
- package/src/mcp/mcp-tool-builder.js +121 -0
- package/src/observability/audit-logger.js +0 -2
- package/src/observability/event-logger.js +2 -1
- package/src/observability/security-monitor.js +0 -2
- package/src/observability/telemetry.js +0 -2
- package/src/reranker/cascade-reranker.js +79 -0
- package/src/reranker/embedding-reranker.js +124 -0
- package/src/reranker/llm-reranker.js +6 -3
- package/src/reranker/scoring-reranker.js +123 -0
- package/src/retrieval/bm25-search.js +157 -0
- package/src/retrieval/hybrid-retriever.js +111 -0
- package/src/retrieval/rank-fusion.js +62 -0
- package/src/security/jwt-config.js +4 -3
- package/src/security/jwt-validator.js +2 -1
- package/src/security/plugin-sandbox.js +0 -3
- package/src/security/plugin-signature-verifier.js +4 -6
- package/src/security/validator.js +0 -2
- package/src/streaming/embedding-stream.js +197 -0
- package/src/streaming/sse-adapter.js +168 -0
- package/src/streaming/stream-router.js +210 -0
- package/src/streaming/streaming-embedder.js +231 -0
- package/src/streaming/websocket-adapter.js +171 -0
- package/src/templates/scaffold.js +147 -0
- package/src/templates/template-registry.js +272 -0
- package/src/utils/contract-validator.js +2 -1
- package/src/utils/input-sanitizer.js +80 -99
- package/src/utils/performance-monitor.js +0 -3
- package/src/utils/plugin-scaffolder.js +9 -16
- package/src/utils/rate-limiter.js +0 -1
- package/src/utils/retry-policy.js +0 -3
- package/src/utils/safe-expression-evaluator.js +0 -3
- package/src/utils/secure-logger-config.js +2 -4
- package/src/utils/secure-logging.js +0 -3
- package/src/utils/structured-logger.js +0 -3
- package/src/ai/index-fixed.js +0 -509
- package/src/ai/module-template.js +0 -37
- package/src/ci/workflow-analyzer.js +0 -71
- package/src/cli/plugin-marketplace-commands.js +0 -869
- package/src/dev/README.md +0 -126
- package/src/dev/examples/hot-reload-usage.js +0 -355
- package/src/mocks/openai-embedder.js +0 -119
- package/src/mocks/openai-llm.js +0 -77
- package/src/mocks/pdf-loader.js +0 -21
- package/src/mocks/pinecone-retriever.js +0 -106
- package/src/scripts/script-utilities.js +0 -36
- package/src/utils/ci/diagnostic-reporter.js +0 -57
- package/src/utils/examples/error-formatter-usage.js +0 -351
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default budget configuration.
|
|
7
|
+
* @type {{maxTokens: number, maxCost: number, softLimitPercent: number, trackingWindow: number}}
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
maxTokens: Infinity,
|
|
11
|
+
maxCost: Infinity,
|
|
12
|
+
softLimitPercent: 0.8, // warn at 80% of limit
|
|
13
|
+
trackingWindow: 0, // 0 = no windowing, else ms for sliding window
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configurable token and cost budgets with hard and soft limits.
|
|
18
|
+
*
|
|
19
|
+
* Emits:
|
|
20
|
+
* - `usage` — after every {@link record} call with the recorded entry.
|
|
21
|
+
* - `warning` — when usage crosses the soft limit threshold.
|
|
22
|
+
* - `exceeded` — when usage crosses a hard limit.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const budget = new TokenBudget({ maxTokens: 100000, maxCost: 5.0 });
|
|
26
|
+
* budget.on('warning', (msg) => console.warn(msg));
|
|
27
|
+
* const status = budget.check(2000, 0.10);
|
|
28
|
+
* if (status.allowed) budget.record(2000, 0.10);
|
|
29
|
+
*/
|
|
30
|
+
class TokenBudget extends EventEmitter {
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} [options]
|
|
33
|
+
* @param {number} [options.maxTokens=Infinity] - Hard token limit.
|
|
34
|
+
* @param {number} [options.maxCost=Infinity] - Hard cost limit (USD).
|
|
35
|
+
* @param {number} [options.softLimitPercent=0.8] - Fraction (0-1) at which soft-limit warnings fire.
|
|
36
|
+
* @param {number} [options.trackingWindow=0] - Sliding window in ms (0 disables).
|
|
37
|
+
*/
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
super();
|
|
40
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
41
|
+
this.usage = { tokens: 0, cost: 0, operations: 0, history: [] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Prune history entries outside the sliding window.
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
_pruneWindow() {
|
|
49
|
+
if (this.config.trackingWindow <= 0) return;
|
|
50
|
+
|
|
51
|
+
const cutoff = Date.now() - this.config.trackingWindow;
|
|
52
|
+
this.usage.history = this.usage.history.filter(
|
|
53
|
+
(h) => h.timestamp >= cutoff,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Recompute totals from remaining history
|
|
57
|
+
this.usage.tokens = this.usage.history.reduce(
|
|
58
|
+
(sum, h) => sum + h.tokens,
|
|
59
|
+
0,
|
|
60
|
+
);
|
|
61
|
+
this.usage.cost = this.usage.history.reduce((sum, h) => sum + h.cost, 0);
|
|
62
|
+
this.usage.operations = this.usage.history.length;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check whether a prospective usage fits within the budget.
|
|
67
|
+
*
|
|
68
|
+
* @param {number} [tokens=0] - Tokens about to be consumed.
|
|
69
|
+
* @param {number} [cost=0] - Cost about to be incurred.
|
|
70
|
+
* @returns {{allowed: boolean, reason?: string, remaining: {tokens: number, cost: number},
|
|
71
|
+
* warnings: string[], usage: {tokens: number, cost: number, operations: number}}}
|
|
72
|
+
*/
|
|
73
|
+
check(tokens = 0, cost = 0) {
|
|
74
|
+
this._pruneWindow();
|
|
75
|
+
|
|
76
|
+
const projectedTokens = this.usage.tokens + tokens;
|
|
77
|
+
const projectedCost = this.usage.cost + cost;
|
|
78
|
+
const warnings = [];
|
|
79
|
+
|
|
80
|
+
// Hard-limit checks
|
|
81
|
+
if (projectedTokens > this.config.maxTokens) {
|
|
82
|
+
return {
|
|
83
|
+
allowed: false,
|
|
84
|
+
reason: `Token limit exceeded: ${projectedTokens} > ${this.config.maxTokens}`,
|
|
85
|
+
remaining: {
|
|
86
|
+
tokens: Math.max(0, this.config.maxTokens - this.usage.tokens),
|
|
87
|
+
cost: Math.max(0, this.config.maxCost - this.usage.cost),
|
|
88
|
+
},
|
|
89
|
+
warnings,
|
|
90
|
+
usage: {
|
|
91
|
+
tokens: this.usage.tokens,
|
|
92
|
+
cost: this.usage.cost,
|
|
93
|
+
operations: this.usage.operations,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (projectedCost > this.config.maxCost) {
|
|
99
|
+
return {
|
|
100
|
+
allowed: false,
|
|
101
|
+
reason: `Cost limit exceeded: ${projectedCost.toFixed(4)} > ${this.config.maxCost}`,
|
|
102
|
+
remaining: {
|
|
103
|
+
tokens: Math.max(0, this.config.maxTokens - this.usage.tokens),
|
|
104
|
+
cost: Math.max(0, this.config.maxCost - this.usage.cost),
|
|
105
|
+
},
|
|
106
|
+
warnings,
|
|
107
|
+
usage: {
|
|
108
|
+
tokens: this.usage.tokens,
|
|
109
|
+
cost: this.usage.cost,
|
|
110
|
+
operations: this.usage.operations,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Soft-limit checks
|
|
116
|
+
const softTokens = this.config.maxTokens * this.config.softLimitPercent;
|
|
117
|
+
const softCost = this.config.maxCost * this.config.softLimitPercent;
|
|
118
|
+
|
|
119
|
+
if (isFinite(this.config.maxTokens) && projectedTokens > softTokens) {
|
|
120
|
+
warnings.push(
|
|
121
|
+
`Approaching token limit: ${projectedTokens} / ${this.config.maxTokens} (${((projectedTokens / this.config.maxTokens) * 100).toFixed(1)}%)`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isFinite(this.config.maxCost) && projectedCost > softCost) {
|
|
126
|
+
warnings.push(
|
|
127
|
+
`Approaching cost limit: $${projectedCost.toFixed(4)} / $${this.config.maxCost} (${((projectedCost / this.config.maxCost) * 100).toFixed(1)}%)`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
allowed: true,
|
|
133
|
+
remaining: {
|
|
134
|
+
tokens: isFinite(this.config.maxTokens)
|
|
135
|
+
? this.config.maxTokens - projectedTokens
|
|
136
|
+
: Infinity,
|
|
137
|
+
cost: isFinite(this.config.maxCost)
|
|
138
|
+
? this.config.maxCost - projectedCost
|
|
139
|
+
: Infinity,
|
|
140
|
+
},
|
|
141
|
+
warnings,
|
|
142
|
+
usage: {
|
|
143
|
+
tokens: this.usage.tokens,
|
|
144
|
+
cost: this.usage.cost,
|
|
145
|
+
operations: this.usage.operations,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Record actual token and cost usage.
|
|
152
|
+
*
|
|
153
|
+
* @param {number} tokens - Tokens consumed.
|
|
154
|
+
* @param {number} [cost=0] - Cost incurred (USD).
|
|
155
|
+
*/
|
|
156
|
+
record(tokens, cost = 0) {
|
|
157
|
+
const entry = { tokens, cost, timestamp: Date.now() };
|
|
158
|
+
this.usage.history.push(entry);
|
|
159
|
+
this.usage.tokens += tokens;
|
|
160
|
+
this.usage.cost += cost;
|
|
161
|
+
this.usage.operations += 1;
|
|
162
|
+
|
|
163
|
+
this.emit('usage', entry);
|
|
164
|
+
|
|
165
|
+
// Soft-limit warnings
|
|
166
|
+
if (isFinite(this.config.maxTokens)) {
|
|
167
|
+
const softTokens = this.config.maxTokens * this.config.softLimitPercent;
|
|
168
|
+
if (
|
|
169
|
+
this.usage.tokens > softTokens &&
|
|
170
|
+
this.usage.tokens <= this.config.maxTokens
|
|
171
|
+
) {
|
|
172
|
+
this.emit(
|
|
173
|
+
'warning',
|
|
174
|
+
`Token usage at ${((this.usage.tokens / this.config.maxTokens) * 100).toFixed(1)}% of limit`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isFinite(this.config.maxCost)) {
|
|
180
|
+
const softCost = this.config.maxCost * this.config.softLimitPercent;
|
|
181
|
+
if (
|
|
182
|
+
this.usage.cost > softCost &&
|
|
183
|
+
this.usage.cost <= this.config.maxCost
|
|
184
|
+
) {
|
|
185
|
+
this.emit(
|
|
186
|
+
'warning',
|
|
187
|
+
`Cost usage at ${((this.usage.cost / this.config.maxCost) * 100).toFixed(1)}% of limit`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Hard-limit exceeded
|
|
193
|
+
if (this.usage.tokens > this.config.maxTokens) {
|
|
194
|
+
this.emit(
|
|
195
|
+
'exceeded',
|
|
196
|
+
`Token limit exceeded: ${this.usage.tokens} > ${this.config.maxTokens}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this.usage.cost > this.config.maxCost) {
|
|
201
|
+
this.emit(
|
|
202
|
+
'exceeded',
|
|
203
|
+
`Cost limit exceeded: ${this.usage.cost.toFixed(4)} > ${this.config.maxCost}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Reset all usage counters to zero.
|
|
210
|
+
*/
|
|
211
|
+
reset() {
|
|
212
|
+
this.usage = { tokens: 0, cost: 0, operations: 0, history: [] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Return current usage summary.
|
|
217
|
+
*
|
|
218
|
+
* @returns {{tokens: number, cost: number, operations: number,
|
|
219
|
+
* limits: {maxTokens: number, maxCost: number},
|
|
220
|
+
* remaining: {tokens: number, cost: number}}}
|
|
221
|
+
*/
|
|
222
|
+
getUsage() {
|
|
223
|
+
this._pruneWindow();
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
tokens: this.usage.tokens,
|
|
227
|
+
cost: this.usage.cost,
|
|
228
|
+
operations: this.usage.operations,
|
|
229
|
+
limits: {
|
|
230
|
+
maxTokens: this.config.maxTokens,
|
|
231
|
+
maxCost: this.config.maxCost,
|
|
232
|
+
},
|
|
233
|
+
remaining: {
|
|
234
|
+
tokens: isFinite(this.config.maxTokens)
|
|
235
|
+
? Math.max(0, this.config.maxTokens - this.usage.tokens)
|
|
236
|
+
: Infinity,
|
|
237
|
+
cost: isFinite(this.config.maxCost)
|
|
238
|
+
? Math.max(0, this.config.maxCost - this.usage.cost)
|
|
239
|
+
: Infinity,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = { TokenBudget, DEFAULT_CONFIG };
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
16
|
const ErrorContext = require('./error-context.js');
|
|
17
|
+
const { logger } = require('../../utils/logger');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* ExecutionScheduler class for managing DAG node execution
|
|
@@ -251,7 +252,7 @@ class ExecutionScheduler {
|
|
|
251
252
|
|
|
252
253
|
// Handle non-critical failures with warnings and no result nullification
|
|
253
254
|
if (isNonCritical || (continueOnError && !requiredIds.has(node.id))) {
|
|
254
|
-
|
|
255
|
+
logger.warn(`Non-critical node failure: ${nodeErr.message}`);
|
|
255
256
|
errors.set(node.id, nodeErr);
|
|
256
257
|
// Don't set result for failed optional nodes - return special symbol so they don't appear in results
|
|
257
258
|
return Symbol('failed-optional-node');
|
package/src/dag/dag-engine.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
} = require('./core/topology-validation.js');
|
|
16
16
|
const ExecutionScheduler = require('./core/execution-scheduler.js');
|
|
17
17
|
const ErrorContext = require('./core/error-context.js');
|
|
18
|
+
const { logger } = require('../utils/logger');
|
|
18
19
|
|
|
19
20
|
class DAG {
|
|
20
21
|
constructor() {
|
|
@@ -458,14 +459,12 @@ class DAG {
|
|
|
458
459
|
continue; // no error recorded yet
|
|
459
460
|
}
|
|
460
461
|
// FINAL failure path - warn EXACT string and keep going
|
|
461
|
-
|
|
462
|
-
console.warn(`Node ${node.id} failed during resume: ${e.message}`);
|
|
462
|
+
logger.warn(`Node ${node.id} failed during resume: ${e.message}`);
|
|
463
463
|
keepRetrying = false; // Don't throw, just continue
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
466
|
} catch (error) {
|
|
467
|
-
|
|
468
|
-
console.warn(`Node ${node.id} failed during resume: ${error.message}`);
|
|
467
|
+
logger.warn(`Node ${node.id} failed during resume: ${error.message}`);
|
|
469
468
|
continue;
|
|
470
469
|
}
|
|
471
470
|
}
|
|
@@ -553,4 +552,3 @@ class DAG {
|
|
|
553
552
|
|
|
554
553
|
// Export classes
|
|
555
554
|
module.exports = { DAG, DAGNode };
|
|
556
|
-
module.exports.default = module.exports;
|
package/src/dag/dag-node.js
CHANGED
|
@@ -589,108 +589,36 @@ class EnhancedDAGEngine {
|
|
|
589
589
|
// Validate DAG before execution
|
|
590
590
|
this.validate();
|
|
591
591
|
|
|
592
|
-
// Initialize execution state
|
|
592
|
+
// Initialize execution state and context
|
|
593
593
|
const results = new Map();
|
|
594
594
|
const errors = new Map();
|
|
595
595
|
const order = this.getTopologicalOrder();
|
|
596
|
-
|
|
597
|
-
// Create execution context
|
|
598
|
-
const context = {
|
|
596
|
+
const context = this._buildExecutionContext(
|
|
599
597
|
results,
|
|
600
598
|
errors,
|
|
601
599
|
seed,
|
|
602
|
-
|
|
603
|
-
gracefulDegradation: config.gracefulDegradation,
|
|
604
|
-
retryFailedNodes: config.retryFailedNodes,
|
|
605
|
-
maxRetries: config.maxRetries,
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
// Execute nodes with concurrency control
|
|
609
|
-
const semaphore = new Semaphore(config.concurrency);
|
|
610
|
-
const nodePromises = new Map();
|
|
611
|
-
|
|
612
|
-
for (const node of order) {
|
|
613
|
-
const promise = semaphore.acquire().then(async (release) => {
|
|
614
|
-
try {
|
|
615
|
-
// Wait for dependencies to complete
|
|
616
|
-
const depPromises = node.inputs
|
|
617
|
-
.map((dep) => nodePromises.get(dep.id))
|
|
618
|
-
.filter(Boolean);
|
|
619
|
-
|
|
620
|
-
await Promise.all(depPromises);
|
|
621
|
-
|
|
622
|
-
// Execute the node
|
|
623
|
-
const result = await this.executeNode(node, context);
|
|
624
|
-
|
|
625
|
-
if (
|
|
626
|
-
typeof result !== 'symbol' ||
|
|
627
|
-
result.description !== 'failed-optional-node'
|
|
628
|
-
) {
|
|
629
|
-
results.set(node.id, result);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return result;
|
|
633
|
-
} finally {
|
|
634
|
-
release();
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
nodePromises.set(node.id, promise);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Wait for all nodes to complete
|
|
642
|
-
const allResults = await Promise.allSettled(
|
|
643
|
-
Array.from(nodePromises.values()),
|
|
600
|
+
config,
|
|
644
601
|
);
|
|
645
602
|
|
|
603
|
+
// Schedule and run all nodes with concurrency control
|
|
604
|
+
await this._scheduleNodes(order, context, config);
|
|
605
|
+
|
|
646
606
|
const executionTime = Date.now() - startTime;
|
|
647
607
|
|
|
648
608
|
// Update engine metrics
|
|
649
|
-
|
|
650
|
-
this.engineMetrics.totalExecutions++;
|
|
651
|
-
this.engineMetrics.totalExecutionTime += executionTime;
|
|
652
|
-
this.engineMetrics.avgExecutionTime =
|
|
653
|
-
this.engineMetrics.totalExecutionTime /
|
|
654
|
-
this.engineMetrics.totalExecutions;
|
|
655
|
-
|
|
656
|
-
if (errors.size === 0) {
|
|
657
|
-
this.engineMetrics.successfulExecutions++;
|
|
658
|
-
} else {
|
|
659
|
-
this.engineMetrics.failedExecutions++;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
609
|
+
this._recordExecutionMetrics(executionTime, errors.size === 0);
|
|
662
610
|
|
|
663
|
-
//
|
|
664
|
-
const summary =
|
|
611
|
+
// Build and store execution summary
|
|
612
|
+
const summary = this._buildExecutionSummary(
|
|
665
613
|
executionId,
|
|
666
|
-
success: errors.size === 0,
|
|
667
614
|
executionTime,
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
message: error.message,
|
|
673
|
-
code: error.code,
|
|
674
|
-
})),
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
// Store execution history
|
|
678
|
-
if (this.enableTracing) {
|
|
679
|
-
this.executionHistory.push({
|
|
680
|
-
...summary,
|
|
681
|
-
timestamp: new Date().toISOString(),
|
|
682
|
-
results: Object.fromEntries(results),
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Keep only last 100 executions
|
|
686
|
-
if (this.executionHistory.length > 100) {
|
|
687
|
-
this.executionHistory.shift();
|
|
688
|
-
}
|
|
689
|
-
}
|
|
615
|
+
results,
|
|
616
|
+
errors,
|
|
617
|
+
);
|
|
618
|
+
this._storeExecutionHistory(summary, results);
|
|
690
619
|
|
|
691
620
|
this.logger.info('DAG execution completed', summary);
|
|
692
621
|
|
|
693
|
-
// Return results
|
|
694
622
|
return {
|
|
695
623
|
results: Object.fromEntries(results),
|
|
696
624
|
errors: Object.fromEntries(errors),
|
|
@@ -699,21 +627,168 @@ class EnhancedDAGEngine {
|
|
|
699
627
|
};
|
|
700
628
|
} catch (error) {
|
|
701
629
|
const executionTime = Date.now() - startTime;
|
|
630
|
+
this._recordExecutionFailure(executionId, error, executionTime);
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
702
635
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Build the execution context passed to each node during DAG execution.
|
|
638
|
+
* @param {Map} results - Map to store completed node results
|
|
639
|
+
* @param {Map} errors - Map to store node execution errors
|
|
640
|
+
* @param {any} seed - Initial seed value for source nodes
|
|
641
|
+
* @param {Object} config - Merged execution configuration
|
|
642
|
+
* @returns {ExecutionContext} Execution context object
|
|
643
|
+
* @private
|
|
644
|
+
*/
|
|
645
|
+
_buildExecutionContext(results, errors, seed, config) {
|
|
646
|
+
return {
|
|
647
|
+
results,
|
|
648
|
+
errors,
|
|
649
|
+
seed,
|
|
650
|
+
continueOnError: config.continueOnError,
|
|
651
|
+
gracefulDegradation: config.gracefulDegradation,
|
|
652
|
+
retryFailedNodes: config.retryFailedNodes,
|
|
653
|
+
maxRetries: config.maxRetries,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Schedule and execute all nodes in topological order with concurrency control.
|
|
659
|
+
* Each node waits for its dependencies before executing.
|
|
660
|
+
* @param {EnhancedDAGNode[]} order - Nodes in topological execution order
|
|
661
|
+
* @param {ExecutionContext} context - Execution context with results and errors maps
|
|
662
|
+
* @param {Object} config - Execution configuration including concurrency limit
|
|
663
|
+
* @returns {Promise<void>} Resolves when all nodes have settled
|
|
664
|
+
* @private
|
|
665
|
+
*/
|
|
666
|
+
async _scheduleNodes(order, context, config) {
|
|
667
|
+
const { results } = context;
|
|
668
|
+
const semaphore = new Semaphore(config.concurrency);
|
|
669
|
+
const nodePromises = new Map();
|
|
670
|
+
|
|
671
|
+
for (const node of order) {
|
|
672
|
+
const promise = semaphore.acquire().then(async (release) => {
|
|
673
|
+
try {
|
|
674
|
+
// Wait for dependencies to complete
|
|
675
|
+
const depPromises = node.inputs
|
|
676
|
+
.map((dep) => nodePromises.get(dep.id))
|
|
677
|
+
.filter(Boolean);
|
|
678
|
+
|
|
679
|
+
await Promise.all(depPromises);
|
|
680
|
+
|
|
681
|
+
// Execute the node
|
|
682
|
+
const result = await this.executeNode(node, context);
|
|
683
|
+
|
|
684
|
+
if (
|
|
685
|
+
typeof result !== 'symbol' ||
|
|
686
|
+
result.description !== 'failed-optional-node'
|
|
687
|
+
) {
|
|
688
|
+
results.set(node.id, result);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return result;
|
|
692
|
+
} finally {
|
|
693
|
+
release();
|
|
706
694
|
}
|
|
695
|
+
});
|
|
707
696
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
error: error.message,
|
|
711
|
-
executionTime,
|
|
712
|
-
stack: error.stack,
|
|
713
|
-
});
|
|
697
|
+
nodePromises.set(node.id, promise);
|
|
698
|
+
}
|
|
714
699
|
|
|
715
|
-
|
|
716
|
-
|
|
700
|
+
await Promise.allSettled(Array.from(nodePromises.values()));
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Record engine-level metrics after a completed execution.
|
|
705
|
+
* @param {number} executionTime - Total execution time in milliseconds
|
|
706
|
+
* @param {boolean} success - Whether the execution completed without errors
|
|
707
|
+
* @private
|
|
708
|
+
*/
|
|
709
|
+
_recordExecutionMetrics(executionTime, success) {
|
|
710
|
+
if (!this.enableMetrics) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
this.engineMetrics.totalExecutions++;
|
|
715
|
+
this.engineMetrics.totalExecutionTime += executionTime;
|
|
716
|
+
this.engineMetrics.avgExecutionTime =
|
|
717
|
+
this.engineMetrics.totalExecutionTime /
|
|
718
|
+
this.engineMetrics.totalExecutions;
|
|
719
|
+
|
|
720
|
+
if (success) {
|
|
721
|
+
this.engineMetrics.successfulExecutions++;
|
|
722
|
+
} else {
|
|
723
|
+
this.engineMetrics.failedExecutions++;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Build a summary object describing the outcome of a DAG execution.
|
|
729
|
+
* @param {string} executionId - Unique execution identifier
|
|
730
|
+
* @param {number} executionTime - Total execution time in milliseconds
|
|
731
|
+
* @param {Map} results - Map of successful node results
|
|
732
|
+
* @param {Map} errors - Map of node execution errors
|
|
733
|
+
* @returns {Object} Execution summary
|
|
734
|
+
* @private
|
|
735
|
+
*/
|
|
736
|
+
_buildExecutionSummary(executionId, executionTime, results, errors) {
|
|
737
|
+
return {
|
|
738
|
+
executionId,
|
|
739
|
+
success: errors.size === 0,
|
|
740
|
+
executionTime,
|
|
741
|
+
nodesExecuted: results.size,
|
|
742
|
+
totalNodes: this.nodes.size,
|
|
743
|
+
errors: Array.from(errors.entries()).map(([id, error]) => ({
|
|
744
|
+
nodeId: id,
|
|
745
|
+
message: error.message,
|
|
746
|
+
code: error.code,
|
|
747
|
+
})),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Store an execution summary in the tracing history buffer.
|
|
753
|
+
* Keeps only the most recent 100 entries.
|
|
754
|
+
* @param {Object} summary - Execution summary to store
|
|
755
|
+
* @param {Map} results - Map of node results to include in history
|
|
756
|
+
* @private
|
|
757
|
+
*/
|
|
758
|
+
_storeExecutionHistory(summary, results) {
|
|
759
|
+
if (!this.enableTracing) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
this.executionHistory.push({
|
|
764
|
+
...summary,
|
|
765
|
+
timestamp: new Date().toISOString(),
|
|
766
|
+
results: Object.fromEntries(results),
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
if (this.executionHistory.length > 100) {
|
|
770
|
+
this.executionHistory.shift();
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Record metrics and log details when a DAG execution fails with an exception.
|
|
776
|
+
* @param {string} executionId - Unique execution identifier
|
|
777
|
+
* @param {Error} error - The error that caused the failure
|
|
778
|
+
* @param {number} executionTime - Time elapsed before the failure
|
|
779
|
+
* @private
|
|
780
|
+
*/
|
|
781
|
+
_recordExecutionFailure(executionId, error, executionTime) {
|
|
782
|
+
if (this.enableMetrics) {
|
|
783
|
+
this.engineMetrics.totalExecutions++;
|
|
784
|
+
this.engineMetrics.failedExecutions++;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
this.logger.error('DAG execution failed', {
|
|
788
|
+
executionId,
|
|
789
|
+
error: error.message,
|
|
790
|
+
executionTime,
|
|
791
|
+
stack: error.stack,
|
|
717
792
|
});
|
|
718
793
|
}
|
|
719
794
|
|
|
@@ -817,6 +892,3 @@ module.exports = {
|
|
|
817
892
|
Semaphore,
|
|
818
893
|
createEnhancedDAG,
|
|
819
894
|
};
|
|
820
|
-
|
|
821
|
-
// CommonJS/ESM interop
|
|
822
|
-
module.exports.default = module.exports;
|