@devilsdev/rag-pipeline-utils 2.3.0 → 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 -361
- 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
|
@@ -85,7 +85,27 @@ module.exports.createRagPipeline = function createRagPipeline({
|
|
|
85
85
|
const answer = (await llmI.generate?.(query, results, options)) ?? '';
|
|
86
86
|
// tiny delay so durations > 0 in timing tests
|
|
87
87
|
await new Promise((r) => setTimeout(r, 5));
|
|
88
|
-
|
|
88
|
+
|
|
89
|
+
const result = { success: true, query, results };
|
|
90
|
+
|
|
91
|
+
if (options.citations) {
|
|
92
|
+
const { CitationTracker } = require('../citation/citation-tracker');
|
|
93
|
+
const tracker = new CitationTracker(options.citationOptions || {});
|
|
94
|
+
result.answer = String(answer);
|
|
95
|
+
result.citations = tracker.track(String(answer), results);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.evaluate) {
|
|
99
|
+
const { PipelineEvaluator } = require('../evaluate/pipeline-evaluator');
|
|
100
|
+
const evaluator = new PipelineEvaluator(options.evaluateOptions || {});
|
|
101
|
+
result.answer = result.answer || String(answer);
|
|
102
|
+
result.evaluation = evaluator.evaluate(
|
|
103
|
+
{ query, answer: String(answer), results },
|
|
104
|
+
{ citationResult: result.citations },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
89
109
|
})();
|
|
90
110
|
|
|
91
111
|
try {
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
EventSeverity,
|
|
11
11
|
PipelineEventLogger,
|
|
12
12
|
} = require('../../observability/event-logger.js');
|
|
13
|
+
const { logger } = require('../../utils/logger');
|
|
13
14
|
|
|
14
15
|
const _logger = global.eventLogger || eventLogger || new PipelineEventLogger();
|
|
15
16
|
global.eventLogger = _logger;
|
|
@@ -233,7 +234,7 @@ class Span {
|
|
|
233
234
|
*/
|
|
234
235
|
end(endTime = new Date()) {
|
|
235
236
|
if (this.endTime !== null) {
|
|
236
|
-
|
|
237
|
+
logger.warn(`Span ${this.name} already ended`);
|
|
237
238
|
return;
|
|
238
239
|
}
|
|
239
240
|
|
|
@@ -6,6 +6,7 @@ const Ajv = require('ajv');
|
|
|
6
6
|
const {
|
|
7
7
|
createPluginVerifier,
|
|
8
8
|
} = require('../security/plugin-signature-verifier.js');
|
|
9
|
+
const { logger } = require('../utils/logger');
|
|
9
10
|
|
|
10
11
|
class PluginRegistry {
|
|
11
12
|
constructor(options = {}) {
|
|
@@ -46,9 +47,9 @@ class PluginRegistry {
|
|
|
46
47
|
process.env.NODE_ENV !== 'production' &&
|
|
47
48
|
!this._disableContractWarnings
|
|
48
49
|
) {
|
|
49
|
-
|
|
50
|
+
logger.warn(
|
|
50
51
|
`[PLUGIN_REGISTRY] Warning: Failed to compile contract schema: ${error.message}`,
|
|
51
|
-
);
|
|
52
|
+
);
|
|
52
53
|
}
|
|
53
54
|
this._contractSchema = null;
|
|
54
55
|
}
|
|
@@ -93,9 +94,9 @@ class PluginRegistry {
|
|
|
93
94
|
process.env.NODE_ENV !== 'production' &&
|
|
94
95
|
!this._disableContractWarnings
|
|
95
96
|
) {
|
|
96
|
-
|
|
97
|
+
logger.warn(
|
|
97
98
|
`[PLUGIN_REGISTRY] Warning: Failed to load contract schema: ${error.message}`,
|
|
98
|
-
);
|
|
99
|
+
);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
return null;
|
|
@@ -218,7 +219,7 @@ class PluginRegistry {
|
|
|
218
219
|
|
|
219
220
|
this._contractWarningsShown.add(warningKey);
|
|
220
221
|
|
|
221
|
-
|
|
222
|
+
logger.warn(
|
|
222
223
|
`
|
|
223
224
|
╔════════════════════════════════════════════════════════════════════════════╗
|
|
224
225
|
║ [PLUGIN_REGISTRY] Missing Contract for '${type}' plugins ║
|
|
@@ -251,7 +252,7 @@ class PluginRegistry {
|
|
|
251
252
|
║ new PluginRegistry({ disableContractWarnings: true }) ║
|
|
252
253
|
╚════════════════════════════════════════════════════════════════════════════╝
|
|
253
254
|
`.trim(),
|
|
254
|
-
);
|
|
255
|
+
);
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
_loadContracts() {
|
|
@@ -289,7 +290,7 @@ class PluginRegistry {
|
|
|
289
290
|
|
|
290
291
|
if (process.env.NODE_ENV === 'production') {
|
|
291
292
|
// In production, log error but continue (fail open)
|
|
292
|
-
|
|
293
|
+
logger.error(`[PLUGIN_REGISTRY] ERROR: ${error.message}`);
|
|
293
294
|
} else {
|
|
294
295
|
// In development, throw to fail fast
|
|
295
296
|
throw error;
|
|
@@ -312,9 +313,9 @@ class PluginRegistry {
|
|
|
312
313
|
process.env.NODE_ENV !== 'production' &&
|
|
313
314
|
!this._disableContractWarnings
|
|
314
315
|
) {
|
|
315
|
-
|
|
316
|
+
logger.warn(
|
|
316
317
|
`[PLUGIN_REGISTRY] Warning: Failed to load contract for '${type}': ${error.message}`,
|
|
317
|
-
);
|
|
318
|
+
);
|
|
318
319
|
}
|
|
319
320
|
}
|
|
320
321
|
}
|
|
@@ -329,9 +330,9 @@ class PluginRegistry {
|
|
|
329
330
|
process.env.NODE_ENV !== 'production' &&
|
|
330
331
|
!this._disableContractWarnings
|
|
331
332
|
) {
|
|
332
|
-
|
|
333
|
+
logger.warn(
|
|
333
334
|
`[PLUGIN_REGISTRY] Warning: Contracts directory not accessible: ${error.message}`,
|
|
334
|
-
);
|
|
335
|
+
);
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
}
|
|
@@ -487,9 +488,9 @@ class PluginRegistry {
|
|
|
487
488
|
|
|
488
489
|
// In production, this should go to a secure audit log
|
|
489
490
|
if (process.env.NODE_ENV === 'production') {
|
|
490
|
-
|
|
491
|
+
logger.warn('[AUDIT]', JSON.stringify(auditEntry));
|
|
491
492
|
} else {
|
|
492
|
-
|
|
493
|
+
logger.info('[AUDIT]', auditEntry);
|
|
493
494
|
}
|
|
494
495
|
}
|
|
495
496
|
|
|
@@ -537,4 +538,3 @@ const registry = new PluginRegistry({
|
|
|
537
538
|
// CJS+ESM interop pattern
|
|
538
539
|
module.exports = registry;
|
|
539
540
|
module.exports.PluginRegistry = PluginRegistry;
|
|
540
|
-
module.exports.default = module.exports;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Built-in pricing table for common LLM and embedding models.
|
|
7
|
+
* Prices are in USD per `unit` tokens.
|
|
8
|
+
* @type {Record<string, {input: number, output: number, unit: number}>}
|
|
9
|
+
*/
|
|
10
|
+
const PRICING = {
|
|
11
|
+
'gpt-4': { input: 0.03, output: 0.06, unit: 1000 },
|
|
12
|
+
'gpt-4-turbo': { input: 0.01, output: 0.03, unit: 1000 },
|
|
13
|
+
'gpt-4o': { input: 0.005, output: 0.015, unit: 1000 },
|
|
14
|
+
'gpt-4o-mini': { input: 0.00015, output: 0.0006, unit: 1000 },
|
|
15
|
+
'gpt-3.5-turbo': { input: 0.0005, output: 0.0015, unit: 1000 },
|
|
16
|
+
'claude-3-opus': { input: 0.015, output: 0.075, unit: 1000 },
|
|
17
|
+
'claude-3-sonnet': { input: 0.003, output: 0.015, unit: 1000 },
|
|
18
|
+
'claude-3-haiku': { input: 0.00025, output: 0.00125, unit: 1000 },
|
|
19
|
+
'claude-3.5-sonnet': { input: 0.003, output: 0.015, unit: 1000 },
|
|
20
|
+
'cohere-command': { input: 0.001, output: 0.002, unit: 1000 },
|
|
21
|
+
'text-embedding-3-small': { input: 0.00002, output: 0, unit: 1000 },
|
|
22
|
+
'text-embedding-3-large': { input: 0.00013, output: 0, unit: 1000 },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maps token counts to cost estimates by provider and model.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const calc = new CostCalculator();
|
|
30
|
+
* const cost = calc.estimate('gpt-4o', 1500, 500);
|
|
31
|
+
* console.log(cost.totalCost); // 0.015
|
|
32
|
+
*/
|
|
33
|
+
class CostCalculator extends EventEmitter {
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} [options]
|
|
36
|
+
* @param {Record<string, {input: number, output: number, unit: number}>} [options.customPricing]
|
|
37
|
+
* Additional or override model pricing entries.
|
|
38
|
+
*/
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
super();
|
|
41
|
+
this.pricing = { ...PRICING, ...(options.customPricing || {}) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a model key from the pricing table.
|
|
46
|
+
* Tries exact match (case-insensitive), then partial match.
|
|
47
|
+
* @param {string} model
|
|
48
|
+
* @returns {string|null} The matched pricing key or null.
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
_resolveModel(model) {
|
|
52
|
+
const lower = model.toLowerCase();
|
|
53
|
+
const keys = Object.keys(this.pricing);
|
|
54
|
+
|
|
55
|
+
// Exact case-insensitive match
|
|
56
|
+
const exact = keys.find((k) => k.toLowerCase() === lower);
|
|
57
|
+
if (exact) return exact;
|
|
58
|
+
|
|
59
|
+
// Partial match — model string contains a known key or vice-versa
|
|
60
|
+
const partial = keys.find(
|
|
61
|
+
(k) => lower.includes(k.toLowerCase()) || k.toLowerCase().includes(lower),
|
|
62
|
+
);
|
|
63
|
+
return partial || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Estimate the cost for a given token count.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} model - Model identifier (case-insensitive, partial match supported).
|
|
70
|
+
* @param {number} inputTokens - Number of input / prompt tokens.
|
|
71
|
+
* @param {number} [outputTokens=0] - Number of output / completion tokens.
|
|
72
|
+
* @returns {{inputCost: number, outputCost: number, totalCost: number, model: string,
|
|
73
|
+
* inputTokens: number, outputTokens: number, currency: string, warning?: string}}
|
|
74
|
+
*/
|
|
75
|
+
estimate(model, inputTokens, outputTokens = 0) {
|
|
76
|
+
const resolvedKey = this._resolveModel(model);
|
|
77
|
+
|
|
78
|
+
if (!resolvedKey) {
|
|
79
|
+
return {
|
|
80
|
+
inputCost: 0,
|
|
81
|
+
outputCost: 0,
|
|
82
|
+
totalCost: 0,
|
|
83
|
+
model,
|
|
84
|
+
inputTokens,
|
|
85
|
+
outputTokens,
|
|
86
|
+
currency: 'USD',
|
|
87
|
+
warning: 'Unknown model',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const price = this.pricing[resolvedKey];
|
|
92
|
+
const inputCost = (inputTokens / price.unit) * price.input;
|
|
93
|
+
const outputCost = (outputTokens / price.unit) * price.output;
|
|
94
|
+
const totalCost = inputCost + outputCost;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
inputCost,
|
|
98
|
+
outputCost,
|
|
99
|
+
totalCost,
|
|
100
|
+
model: resolvedKey,
|
|
101
|
+
inputTokens,
|
|
102
|
+
outputTokens,
|
|
103
|
+
currency: 'USD',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Register custom model pricing.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} model - Model identifier.
|
|
111
|
+
* @param {{input: number, output: number, unit: number}} pricing - Pricing definition.
|
|
112
|
+
*/
|
|
113
|
+
addModel(model, pricing) {
|
|
114
|
+
if (
|
|
115
|
+
!pricing ||
|
|
116
|
+
typeof pricing.input !== 'number' ||
|
|
117
|
+
typeof pricing.output !== 'number'
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'Pricing must include numeric "input" and "output" fields',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
this.pricing[model] = { unit: 1000, ...pricing };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* List all known model names.
|
|
128
|
+
* @returns {string[]}
|
|
129
|
+
*/
|
|
130
|
+
listModels() {
|
|
131
|
+
return Object.keys(this.pricing);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Retrieve pricing for a specific model.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} model - Model identifier (supports fuzzy resolution).
|
|
138
|
+
* @returns {{input: number, output: number, unit: number}|null}
|
|
139
|
+
*/
|
|
140
|
+
getModelPricing(model) {
|
|
141
|
+
const key = this._resolveModel(model);
|
|
142
|
+
return key ? { ...this.pricing[key] } : null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { CostCalculator, PRICING };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
const { CostCalculator } = require('./cost-calculator');
|
|
5
|
+
const { TokenBudget } = require('./token-budget');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default tracker configuration.
|
|
9
|
+
* @type {{model: string, embeddingModel: string, enableBudget: boolean, budgetOptions: object}}
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
model: 'gpt-3.5-turbo',
|
|
13
|
+
embeddingModel: 'text-embedding-3-small',
|
|
14
|
+
enableBudget: false,
|
|
15
|
+
budgetOptions: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Estimated tokens per character when actual token counts are unavailable.
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
const CHARS_PER_TOKEN = 4;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wraps pipeline execution to automatically track token usage and costs.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const tracker = new CostTracker({ model: 'gpt-4o', enableBudget: true, budgetOptions: { maxCost: 10 } });
|
|
29
|
+
* const wrapped = tracker.wrap(pipeline);
|
|
30
|
+
* const result = await wrapped.run(input);
|
|
31
|
+
* console.log(result.cost); // { inputTokens, outputTokens, estimatedCost, model }
|
|
32
|
+
* console.log(tracker.getSummary());
|
|
33
|
+
*/
|
|
34
|
+
class CostTracker extends EventEmitter {
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} [options]
|
|
37
|
+
* @param {string} [options.model='gpt-3.5-turbo'] - Default LLM model for cost estimation.
|
|
38
|
+
* @param {string} [options.embeddingModel='text-embedding-3-small'] - Default embedding model.
|
|
39
|
+
* @param {boolean} [options.enableBudget=false] - Whether to enforce a token/cost budget.
|
|
40
|
+
* @param {object} [options.budgetOptions] - Options forwarded to {@link TokenBudget}.
|
|
41
|
+
* @param {object} [options.calculatorOptions] - Options forwarded to {@link CostCalculator}.
|
|
42
|
+
*/
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
super();
|
|
45
|
+
this.config = { ...DEFAULT_CONFIG, ...options };
|
|
46
|
+
this.calculator = new CostCalculator(options.calculatorOptions);
|
|
47
|
+
this.budget = this.config.enableBudget
|
|
48
|
+
? new TokenBudget(this.config.budgetOptions)
|
|
49
|
+
: null;
|
|
50
|
+
this.sessions = [];
|
|
51
|
+
this.currentSession = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract token usage from a pipeline result.
|
|
56
|
+
* Checks common locations: result.usage, result.evaluation, or estimates from content length.
|
|
57
|
+
* @param {object} result
|
|
58
|
+
* @returns {{inputTokens: number, outputTokens: number}}
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
_extractUsage(result) {
|
|
62
|
+
// Direct usage field (OpenAI-style)
|
|
63
|
+
if (result.usage) {
|
|
64
|
+
return {
|
|
65
|
+
inputTokens:
|
|
66
|
+
result.usage.prompt_tokens || result.usage.inputTokens || 0,
|
|
67
|
+
outputTokens:
|
|
68
|
+
result.usage.completion_tokens || result.usage.outputTokens || 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Evaluation metadata
|
|
73
|
+
if (result.evaluation && result.evaluation.tokens) {
|
|
74
|
+
return {
|
|
75
|
+
inputTokens: result.evaluation.tokens.input || 0,
|
|
76
|
+
outputTokens: result.evaluation.tokens.output || 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Estimate from content length
|
|
81
|
+
const inputText = result.input || result.query || result.prompt || '';
|
|
82
|
+
const outputText = result.output || result.answer || result.response || '';
|
|
83
|
+
return {
|
|
84
|
+
inputTokens: Math.ceil(String(inputText).length / CHARS_PER_TOKEN),
|
|
85
|
+
outputTokens: Math.ceil(String(outputText).length / CHARS_PER_TOKEN),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wrap a pipeline so that every `.run()` call is cost-tracked.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} pipeline - Object with a `.run()` method.
|
|
93
|
+
* @returns {object} A proxy pipeline whose `.run()` enriches results with cost data.
|
|
94
|
+
*/
|
|
95
|
+
wrap(pipeline) {
|
|
96
|
+
const tracker = this;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
...pipeline,
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Execute the wrapped pipeline and attach cost metadata to the result.
|
|
103
|
+
* @param {...*} args - Arguments forwarded to the original pipeline.run().
|
|
104
|
+
* @returns {Promise<object>} The pipeline result enriched with a `.cost` property.
|
|
105
|
+
*/
|
|
106
|
+
run: async (...args) => {
|
|
107
|
+
if (!tracker.currentSession) {
|
|
108
|
+
tracker.startSession({ autoStarted: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await pipeline.run(...args);
|
|
112
|
+
|
|
113
|
+
const { inputTokens, outputTokens } = tracker._extractUsage(result);
|
|
114
|
+
const model = result.model || tracker.config.model;
|
|
115
|
+
const costEstimate = tracker.calculator.estimate(
|
|
116
|
+
model,
|
|
117
|
+
inputTokens,
|
|
118
|
+
outputTokens,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Budget enforcement
|
|
122
|
+
if (tracker.budget) {
|
|
123
|
+
const budgetCheck = tracker.budget.check(
|
|
124
|
+
inputTokens + outputTokens,
|
|
125
|
+
costEstimate.totalCost,
|
|
126
|
+
);
|
|
127
|
+
if (!budgetCheck.allowed) {
|
|
128
|
+
const err = new Error(`Budget exceeded: ${budgetCheck.reason}`);
|
|
129
|
+
err.code = 'BUDGET_EXCEEDED';
|
|
130
|
+
err.budgetCheck = budgetCheck;
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
tracker.budget.record(
|
|
134
|
+
inputTokens + outputTokens,
|
|
135
|
+
costEstimate.totalCost,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Attach cost data to result
|
|
140
|
+
result.cost = {
|
|
141
|
+
inputTokens,
|
|
142
|
+
outputTokens,
|
|
143
|
+
estimatedCost: costEstimate.totalCost,
|
|
144
|
+
model: costEstimate.model,
|
|
145
|
+
currency: 'USD',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Record in session
|
|
149
|
+
const entry = {
|
|
150
|
+
timestamp: Date.now(),
|
|
151
|
+
inputTokens,
|
|
152
|
+
outputTokens,
|
|
153
|
+
cost: costEstimate.totalCost,
|
|
154
|
+
model: costEstimate.model,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (tracker.currentSession) {
|
|
158
|
+
tracker.currentSession.entries.push(entry);
|
|
159
|
+
tracker.currentSession.totalTokens += inputTokens + outputTokens;
|
|
160
|
+
tracker.currentSession.totalCost += costEstimate.totalCost;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
tracker.emit('tracked', entry);
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Start a new tracking session.
|
|
172
|
+
*
|
|
173
|
+
* @param {object} [metadata={}] - Arbitrary metadata to attach to the session.
|
|
174
|
+
* @returns {{id: string, startTime: number, metadata: object, entries: Array,
|
|
175
|
+
* totalTokens: number, totalCost: number}}
|
|
176
|
+
*/
|
|
177
|
+
startSession(metadata = {}) {
|
|
178
|
+
const session = {
|
|
179
|
+
id: `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
180
|
+
startTime: Date.now(),
|
|
181
|
+
endTime: null,
|
|
182
|
+
metadata,
|
|
183
|
+
entries: [],
|
|
184
|
+
totalTokens: 0,
|
|
185
|
+
totalCost: 0,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
this.currentSession = session;
|
|
189
|
+
this.sessions.push(session);
|
|
190
|
+
return session;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* End the current tracking session and compute totals.
|
|
195
|
+
*
|
|
196
|
+
* @returns {object|null} The closed session, or null if no session was active.
|
|
197
|
+
*/
|
|
198
|
+
endSession() {
|
|
199
|
+
if (!this.currentSession) return null;
|
|
200
|
+
|
|
201
|
+
this.currentSession.endTime = Date.now();
|
|
202
|
+
const closed = this.currentSession;
|
|
203
|
+
this.currentSession = null;
|
|
204
|
+
return closed;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Return an aggregate summary across all sessions.
|
|
209
|
+
*
|
|
210
|
+
* @returns {{totalTokens: number, totalCost: number, sessionCount: number,
|
|
211
|
+
* avgCostPerQuery: number, breakdown: Record<string, {tokens: number, cost: number, count: number}>}}
|
|
212
|
+
*/
|
|
213
|
+
getSummary() {
|
|
214
|
+
let totalTokens = 0;
|
|
215
|
+
let totalCost = 0;
|
|
216
|
+
let totalOps = 0;
|
|
217
|
+
const breakdown = {};
|
|
218
|
+
|
|
219
|
+
for (const session of this.sessions) {
|
|
220
|
+
for (const entry of session.entries) {
|
|
221
|
+
totalTokens += entry.inputTokens + entry.outputTokens;
|
|
222
|
+
totalCost += entry.cost;
|
|
223
|
+
totalOps += 1;
|
|
224
|
+
|
|
225
|
+
if (!breakdown[entry.model]) {
|
|
226
|
+
breakdown[entry.model] = { tokens: 0, cost: 0, count: 0 };
|
|
227
|
+
}
|
|
228
|
+
breakdown[entry.model].tokens += entry.inputTokens + entry.outputTokens;
|
|
229
|
+
breakdown[entry.model].cost += entry.cost;
|
|
230
|
+
breakdown[entry.model].count += 1;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
totalTokens,
|
|
236
|
+
totalCost,
|
|
237
|
+
sessionCount: this.sessions.length,
|
|
238
|
+
avgCostPerQuery: totalOps > 0 ? totalCost / totalOps : 0,
|
|
239
|
+
breakdown,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Return the full history of tracked operations across all sessions.
|
|
245
|
+
*
|
|
246
|
+
* @returns {Array<{timestamp: number, inputTokens: number, outputTokens: number,
|
|
247
|
+
* cost: number, model: string, sessionId: string}>}
|
|
248
|
+
*/
|
|
249
|
+
getHistory() {
|
|
250
|
+
const history = [];
|
|
251
|
+
for (const session of this.sessions) {
|
|
252
|
+
for (const entry of session.entries) {
|
|
253
|
+
history.push({ ...entry, sessionId: session.id });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return history;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = { CostTracker, DEFAULT_CONFIG };
|