@cascadeflow/n8n-nodes-cascadeflow 0.6.7 → 0.7.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.
Files changed (27) hide show
  1. package/README.md +16 -11
  2. package/dist/nodes/CascadeFlowAgent/CascadeFlowAgent.node.d.ts +34 -0
  3. package/dist/nodes/CascadeFlowAgent/CascadeFlowAgent.node.d.ts.map +1 -0
  4. package/dist/nodes/CascadeFlowAgent/CascadeFlowAgent.node.js +466 -0
  5. package/dist/nodes/CascadeFlowAgent/CascadeFlowAgent.node.js.map +1 -0
  6. package/dist/nodes/CascadeFlowAgent/__tests__/cascadeflow-agent-executor.test.d.ts +2 -0
  7. package/dist/nodes/CascadeFlowAgent/__tests__/cascadeflow-agent-executor.test.d.ts.map +1 -0
  8. package/dist/nodes/CascadeFlowAgent/__tests__/cascadeflow-agent-executor.test.js +98 -0
  9. package/dist/nodes/CascadeFlowAgent/__tests__/cascadeflow-agent-executor.test.js.map +1 -0
  10. package/dist/nodes/CascadeFlowAgent/cascadeflow.svg +15 -0
  11. package/dist/nodes/LmChatCascadeFlow/LmChatCascadeFlow.node.d.ts +100 -0
  12. package/dist/nodes/LmChatCascadeFlow/LmChatCascadeFlow.node.d.ts.map +1 -1
  13. package/dist/nodes/LmChatCascadeFlow/LmChatCascadeFlow.node.js +432 -303
  14. package/dist/nodes/LmChatCascadeFlow/LmChatCascadeFlow.node.js.map +1 -1
  15. package/dist/nodes/LmChatCascadeFlow/__tests__/lm-chat-cascadeflow-node.test.d.ts +2 -0
  16. package/dist/nodes/LmChatCascadeFlow/__tests__/lm-chat-cascadeflow-node.test.d.ts.map +1 -0
  17. package/dist/nodes/LmChatCascadeFlow/__tests__/lm-chat-cascadeflow-node.test.js +59 -0
  18. package/dist/nodes/LmChatCascadeFlow/__tests__/lm-chat-cascadeflow-node.test.js.map +1 -0
  19. package/dist/nodes/LmChatCascadeFlow/cascade-metadata.d.ts +27 -0
  20. package/dist/nodes/LmChatCascadeFlow/cascade-metadata.d.ts.map +1 -0
  21. package/dist/nodes/LmChatCascadeFlow/cascade-metadata.js +21 -0
  22. package/dist/nodes/LmChatCascadeFlow/cascade-metadata.js.map +1 -0
  23. package/dist/nodes/LmChatCascadeFlow/config.d.ts +41 -0
  24. package/dist/nodes/LmChatCascadeFlow/config.d.ts.map +1 -0
  25. package/dist/nodes/LmChatCascadeFlow/config.js +87 -0
  26. package/dist/nodes/LmChatCascadeFlow/config.js.map +1 -0
  27. package/package.json +17 -12
@@ -1,76 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LmChatCascadeFlow = void 0;
3
+ exports.LmChatCascadeFlow = exports.CascadeChatModel = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const chat_models_1 = require("@langchain/core/language_models/chat_models");
6
6
  const outputs_1 = require("@langchain/core/outputs");
7
- // =============================================================================
8
- // DOMAIN CONSTANTS - All 16 supported domains
9
- // =============================================================================
10
- const DOMAINS = {
11
- CODE: 'code',
12
- DATA: 'data',
13
- STRUCTURED: 'structured',
14
- RAG: 'rag',
15
- CONVERSATION: 'conversation',
16
- TOOL: 'tool',
17
- CREATIVE: 'creative',
18
- SUMMARY: 'summary',
19
- TRANSLATION: 'translation',
20
- MATH: 'math',
21
- SCIENCE: 'science',
22
- MEDICAL: 'medical',
23
- LEGAL: 'legal',
24
- FINANCIAL: 'financial',
25
- MULTIMODAL: 'multimodal',
26
- GENERAL: 'general',
27
- };
28
- // Domain display names for n8n UI
29
- const DOMAIN_DISPLAY_NAMES = {
30
- code: 'Code',
31
- data: 'Data Analysis',
32
- structured: 'Structured Output',
33
- rag: 'RAG (Retrieval)',
34
- conversation: 'Conversation',
35
- tool: 'Tool Calling',
36
- creative: 'Creative Writing',
37
- summary: 'Summarization',
38
- translation: 'Translation',
39
- math: 'Mathematics',
40
- science: 'Science',
41
- medical: 'Medical',
42
- legal: 'Legal',
43
- financial: 'Financial',
44
- multimodal: 'Multimodal',
45
- general: 'General',
46
- };
47
- // Domain descriptions for n8n UI
48
- const DOMAIN_DESCRIPTIONS = {
49
- code: 'Programming, debugging, code generation',
50
- data: 'Data analysis, statistics, pandas/SQL',
51
- structured: 'JSON, XML, structured data extraction',
52
- rag: 'Retrieval-augmented generation, document Q&A',
53
- conversation: 'Chat, dialogue, multi-turn conversations',
54
- tool: 'Function calling, tool use, API interactions',
55
- creative: 'Creative writing, stories, poetry',
56
- summary: 'Text summarization, condensing content',
57
- translation: 'Language translation, multilingual',
58
- math: 'Mathematical reasoning, calculations, proofs',
59
- science: 'Scientific knowledge, research, experiments',
60
- medical: 'Healthcare, medical knowledge, clinical',
61
- legal: 'Legal documents, contracts, regulations',
62
- financial: 'Finance, accounting, investment analysis',
63
- multimodal: 'Images, audio, video understanding',
64
- general: 'General purpose, fallback domain',
65
- };
66
- // Quality validation, cost tracking, routing, and circuit breaker - optional import
7
+ const config_1 = require("./config");
8
+ const cascade_metadata_1 = require("./cascade-metadata");
9
+ // Quality validation, cost tracking, and routing - optional import
67
10
  let QualityValidator;
68
11
  let CASCADE_QUALITY_CONFIG;
69
12
  let CostCalculator;
70
13
  let ComplexityDetector;
71
14
  let PreRouter;
72
- let DomainDetector;
73
- let CircuitBreaker;
15
+ let DomainRouter;
74
16
  try {
75
17
  const cascadeCore = require('@cascadeflow/core');
76
18
  QualityValidator = cascadeCore.QualityValidator;
@@ -78,8 +20,7 @@ try {
78
20
  CostCalculator = cascadeCore.CostCalculator;
79
21
  ComplexityDetector = cascadeCore.ComplexityDetector;
80
22
  PreRouter = cascadeCore.PreRouter;
81
- DomainDetector = cascadeCore.DomainDetector;
82
- CircuitBreaker = cascadeCore.CircuitBreaker;
23
+ DomainRouter = cascadeCore.DomainRouter;
83
24
  }
84
25
  catch (e) {
85
26
  // @cascadeflow/core not available - use simple validation and estimates
@@ -90,10 +31,11 @@ catch (e) {
90
31
  * and implements intelligent domain-aware cascading logic with cost tracking
91
32
  */
92
33
  class CascadeChatModel extends chat_models_1.BaseChatModel {
93
- constructor(drafterModel, verifierModelGetter, qualityThreshold = 0.7, useSemanticValidation = true, useAlignmentScoring = true, useComplexityRouting = true, useDomainRouting = false, enabledDomains = [], domainModelGetters = new Map(), domainConfigs = new Map(), useCircuitBreaker = true) {
34
+ constructor(drafterModelGetter, verifierModelGetter, qualityThreshold = 0.7, useSemanticValidation = true, useAlignmentScoring = true, useComplexityRouting = true, useComplexityThresholds = true, useDomainRouting = false, enabledDomains = [], domainModelGetters = new Map(), domainConfigs = new Map(), confidenceThresholds) {
94
35
  super({});
95
36
  // Domain-specific models and configurations
96
37
  this.domainModels = new Map();
38
+ this.domainModelGetters = new Map();
97
39
  this.domainConfigs = new Map();
98
40
  this.enabledDomains = [];
99
41
  // Cost tracking
@@ -103,14 +45,17 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
103
45
  this.drafterCount = 0;
104
46
  this.verifierCount = 0;
105
47
  this.domainCounts = new Map();
106
- this.drafterModel = drafterModel;
48
+ this.drafterModelGetter = drafterModelGetter;
107
49
  this.verifierModelGetter = verifierModelGetter;
108
50
  this.qualityThreshold = qualityThreshold;
109
51
  this.enabledDomains = enabledDomains;
110
52
  this.domainConfigs = domainConfigs;
53
+ this.domainModelGetters = domainModelGetters;
54
+ this.confidenceThresholds = confidenceThresholds;
55
+ this.useComplexityThresholds = useComplexityThresholds;
56
+ this.useComplexityRouting = useComplexityRouting;
111
57
  // Store domain model getters for lazy loading
112
- for (const [domain, getter] of domainModelGetters.entries()) {
113
- // Lazy load domain models
58
+ for (const [domain] of domainModelGetters.entries()) {
114
59
  this.domainModels.set(domain, undefined);
115
60
  }
116
61
  // Initialize quality validator with CASCADE-optimized config + semantic validation
@@ -119,6 +64,7 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
119
64
  this.qualityValidator = new QualityValidator({
120
65
  ...CASCADE_QUALITY_CONFIG,
121
66
  minConfidence: qualityThreshold,
67
+ confidenceThresholds: useComplexityThresholds ? confidenceThresholds : undefined,
122
68
  useSemanticValidation,
123
69
  useAlignmentScoring,
124
70
  semanticThreshold: 0.5,
@@ -154,10 +100,10 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
154
100
  this.costCalculator = null;
155
101
  }
156
102
  // Initialize complexity detector and domain detector
157
- if (useComplexityRouting && ComplexityDetector) {
103
+ if ((useComplexityRouting || useComplexityThresholds) && ComplexityDetector) {
158
104
  try {
159
105
  this.complexityDetector = new ComplexityDetector();
160
- console.log('🧠 CascadeFlow complexity-based routing enabled');
106
+ console.log('🧠 CascadeFlow complexity detector initialized');
161
107
  }
162
108
  catch (e) {
163
109
  console.warn('⚠️ Complexity detector initialization failed');
@@ -167,38 +113,20 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
167
113
  else {
168
114
  this.complexityDetector = null;
169
115
  }
170
- // Initialize domain detector if domain routing is enabled
171
- if (useDomainRouting && DomainDetector && enabledDomains.length > 0) {
116
+ // Initialize domain router if domain routing is enabled
117
+ if (useDomainRouting && DomainRouter && enabledDomains.length > 0) {
172
118
  try {
173
- this.domainDetector = new DomainDetector({ enabledDomains });
119
+ this.domainDetector = new DomainRouter();
174
120
  console.log(`🎯 CascadeFlow domain routing enabled for: ${enabledDomains.join(', ')}`);
175
121
  }
176
122
  catch (e) {
177
- console.warn('⚠️ Domain detector initialization failed');
123
+ console.warn('⚠️ Domain router initialization failed');
178
124
  this.domainDetector = null;
179
125
  }
180
126
  }
181
127
  else {
182
128
  this.domainDetector = null;
183
129
  }
184
- // Initialize circuit breaker for fault tolerance
185
- if (useCircuitBreaker && CircuitBreaker) {
186
- try {
187
- this.circuitBreaker = new CircuitBreaker({
188
- failureThreshold: 3,
189
- recoveryTimeout: 30000, // 30 seconds
190
- halfOpenMaxCalls: 2,
191
- });
192
- console.log('🛡️ CascadeFlow circuit breaker enabled');
193
- }
194
- catch (e) {
195
- console.warn('⚠️ Circuit breaker initialization failed');
196
- this.circuitBreaker = null;
197
- }
198
- }
199
- else {
200
- this.circuitBreaker = null;
201
- }
202
130
  }
203
131
  async getVerifierModel() {
204
132
  if (!this.verifierModel) {
@@ -210,15 +138,72 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
210
138
  return this.verifierModel;
211
139
  }
212
140
  /**
213
- * Get domain-specific model if available, falls back to drafter
141
+ * Agent helper: force a direct verifier call (bypasses cascade logic) while still
142
+ * attaching the same `response_metadata` fields as the standard flows.
143
+ */
144
+ async invokeVerifierDirect(messages, options, runManager) {
145
+ const verifierModel = await this.getVerifierModel();
146
+ const verifierInfo = this.getModelInfo(verifierModel);
147
+ await runManager?.handleText(`⚡ Agent route: using verifier directly (${verifierInfo})\n`);
148
+ const start = Date.now();
149
+ const verifierMessage = await verifierModel.invoke(messages, options);
150
+ const latency = Date.now() - start;
151
+ const verifierCost = await this.calculateMessageCost(verifierMessage, verifierModel);
152
+ const costBreakdown = {
153
+ drafter: 0,
154
+ verifier: verifierCost,
155
+ total: verifierCost,
156
+ };
157
+ if (!verifierMessage.response_metadata) {
158
+ verifierMessage.response_metadata = {};
159
+ }
160
+ verifierMessage.response_metadata.cascadeflow = {
161
+ flow: 'agent_verifier_direct',
162
+ model_used: 'verifier',
163
+ latency_ms: latency,
164
+ cost_usd: verifierCost,
165
+ };
166
+ this.attachCascadeMetadata(verifierMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
167
+ modelUsed: 'verifier',
168
+ domain: null,
169
+ costs: costBreakdown,
170
+ baselineCost: verifierCost,
171
+ }));
172
+ return verifierMessage;
173
+ }
174
+ /**
175
+ * Lazy-load drafter model (so n8n highlights it only when actually used).
176
+ */
177
+ async getDrafterModel() {
178
+ if (!this.drafterModel) {
179
+ this.drafterModel = await this.drafterModelGetter();
180
+ }
181
+ return this.drafterModel;
182
+ }
183
+ /**
184
+ * Get a connected domain-specific model (lazy-loaded).
185
+ * Returns undefined if no domain model is connected.
214
186
  */
215
187
  async getDomainModel(domain) {
216
188
  const existingModel = this.domainModels.get(domain);
217
189
  if (existingModel) {
218
190
  return existingModel;
219
191
  }
220
- // Fallback to drafter if no domain-specific model
221
- return this.drafterModel;
192
+ const getter = this.domainModelGetters.get(domain);
193
+ if (!getter) {
194
+ return undefined;
195
+ }
196
+ try {
197
+ const model = await getter();
198
+ if (model) {
199
+ this.domainModels.set(domain, model);
200
+ return model;
201
+ }
202
+ }
203
+ catch {
204
+ // Treat as "not connected"
205
+ }
206
+ return undefined;
222
207
  }
223
208
  /**
224
209
  * Helper to get model info string (type and name)
@@ -228,6 +213,76 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
228
213
  const modelName = model.modelName || model.model || 'unknown';
229
214
  return `${type} (${modelName})`;
230
215
  }
216
+ /**
217
+ * Detect query complexity using whichever detector API is available.
218
+ */
219
+ async detectComplexity(queryText) {
220
+ if (!this.complexityDetector) {
221
+ return {};
222
+ }
223
+ try {
224
+ if (typeof this.complexityDetector.detectComplexity === 'function') {
225
+ const result = await this.complexityDetector.detectComplexity(queryText);
226
+ return {
227
+ level: result.level ?? result.complexity,
228
+ confidence: result.confidence,
229
+ };
230
+ }
231
+ if (typeof this.complexityDetector.detect === 'function') {
232
+ const result = await this.complexityDetector.detect(queryText);
233
+ return {
234
+ level: result.complexity ?? result.level,
235
+ confidence: result.confidence,
236
+ };
237
+ }
238
+ }
239
+ catch (e) {
240
+ console.warn('Complexity detection failed, using normal flow');
241
+ }
242
+ return {};
243
+ }
244
+ /**
245
+ * Get effective confidence threshold for a given complexity tier.
246
+ */
247
+ getThresholdForComplexity(complexity) {
248
+ if (!complexity || !this.useComplexityThresholds || !this.confidenceThresholds) {
249
+ return this.qualityThreshold;
250
+ }
251
+ switch (complexity) {
252
+ case 'trivial':
253
+ return this.confidenceThresholds.trivial ?? this.qualityThreshold;
254
+ case 'simple':
255
+ return this.confidenceThresholds.simple ?? this.qualityThreshold;
256
+ case 'moderate':
257
+ return this.confidenceThresholds.moderate ?? this.qualityThreshold;
258
+ case 'hard':
259
+ return this.confidenceThresholds.hard ?? this.qualityThreshold;
260
+ case 'expert':
261
+ return this.confidenceThresholds.expert ?? this.qualityThreshold;
262
+ default:
263
+ return this.qualityThreshold;
264
+ }
265
+ }
266
+ /**
267
+ * Estimate baseline cost for a different model using the same token usage.
268
+ */
269
+ async estimateAlternateModelCost(message, model) {
270
+ if (!model) {
271
+ return undefined;
272
+ }
273
+ try {
274
+ return await this.calculateMessageCost(message, model);
275
+ }
276
+ catch (e) {
277
+ return undefined;
278
+ }
279
+ }
280
+ attachCascadeMetadata(message, metadata) {
281
+ if (!message.response_metadata) {
282
+ message.response_metadata = {};
283
+ }
284
+ message.response_metadata.cf = metadata;
285
+ }
231
286
  /**
232
287
  * Detect the domain of a query using semantic detection
233
288
  */
@@ -328,7 +383,7 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
328
383
  /**
329
384
  * Simple quality validation fallback (when @cascadeflow/core not available)
330
385
  */
331
- simpleQualityCheck(responseText) {
386
+ simpleQualityCheck(responseText, threshold) {
332
387
  const wordCount = responseText.split(/\s+/).length;
333
388
  let confidence = 0.75;
334
389
  if (wordCount < 5) {
@@ -345,10 +400,10 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
345
400
  if (hasUncertainty) {
346
401
  confidence -= 0.20;
347
402
  }
348
- const passed = confidence >= this.qualityThreshold;
403
+ const passed = confidence >= threshold;
349
404
  const reason = passed
350
- ? `Simple check passed (confidence: ${confidence.toFixed(2)} >= ${this.qualityThreshold})`
351
- : `Simple check failed (confidence: ${confidence.toFixed(2)} < ${this.qualityThreshold})`;
405
+ ? `Simple check passed (confidence: ${confidence.toFixed(2)} >= ${threshold})`
406
+ : `Simple check failed (confidence: ${confidence.toFixed(2)} < ${threshold})`;
352
407
  return { passed, confidence, score: confidence, reason };
353
408
  }
354
409
  _llmType() {
@@ -363,10 +418,10 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
363
418
  if (this.enabledDomains.length > 0) {
364
419
  detectedDomain = await this.detectDomain(queryText);
365
420
  if (detectedDomain) {
366
- const domainConfig = this.domainConfigs.get(detectedDomain);
367
- if (domainConfig?.model) {
368
- domainModel = domainConfig.model;
369
- await runManager?.handleText(`🎯 Domain: ${DOMAIN_DISPLAY_NAMES[detectedDomain]} → Using domain-specific model\n`);
421
+ const connectedDomainModel = await this.getDomainModel(detectedDomain);
422
+ if (connectedDomainModel) {
423
+ domainModel = connectedDomainModel;
424
+ await runManager?.handleText(`🎯 Domain: ${config_1.DOMAIN_DISPLAY_NAMES[detectedDomain]} → Using domain-specific model\n`);
370
425
  }
371
426
  }
372
427
  }
@@ -374,9 +429,9 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
374
429
  let complexity;
375
430
  let shouldSkipDrafter = false;
376
431
  if (this.complexityDetector) {
377
- try {
378
- const complexityResult = await this.complexityDetector.detectComplexity(queryText);
379
- complexity = complexityResult.level;
432
+ const complexityResult = await this.detectComplexity(queryText);
433
+ complexity = complexityResult.level;
434
+ if (complexity && this.useComplexityRouting) {
380
435
  if (complexity === 'hard' || complexity === 'expert') {
381
436
  shouldSkipDrafter = true;
382
437
  await runManager?.handleText(`🧠 Complexity: ${complexity} → Routing directly to verifier (skip drafter)\n`);
@@ -387,27 +442,22 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
387
442
  console.log(`🧠 Complexity: ${complexity} → Drafter route`);
388
443
  }
389
444
  }
390
- catch (e) {
391
- console.warn('Complexity detection failed, using normal flow');
392
- }
393
445
  }
394
- // Step 3: Execute with circuit breaker protection if available
395
- const executeWithProtection = async (model, modelType) => {
396
- if (this.circuitBreaker) {
397
- return await this.circuitBreaker.execute(modelType, async () => await model.invoke(messages, options));
398
- }
399
- return await model.invoke(messages, options);
400
- };
401
- // Step 3a: If complexity routing says skip drafter, go directly to verifier
446
+ // Step 3: If complexity routing says skip drafter, go directly to verifier
402
447
  if (shouldSkipDrafter) {
403
448
  const verifierModel = await this.getVerifierModel();
404
449
  const verifierInfo = this.getModelInfo(verifierModel);
405
450
  await runManager?.handleText(`⚡ Direct route: Using verifier for ${complexity} query\n`);
406
451
  const verifierStartTime = Date.now();
407
- const verifierMessage = await executeWithProtection(verifierModel, 'verifier');
452
+ const verifierMessage = await verifierModel.invoke(messages, options);
408
453
  const verifierLatency = Date.now() - verifierStartTime;
409
454
  this.verifierCount++;
410
455
  const verifierCost = await this.calculateMessageCost(verifierMessage, verifierModel);
456
+ const costBreakdown = {
457
+ drafter: 0,
458
+ verifier: verifierCost,
459
+ total: verifierCost,
460
+ };
411
461
  const flowLog = `
412
462
  ┌────────────────────────────────────────────┐
413
463
  │ ⚡ FLOW: DIRECT VERIFIER (SMART ROUTE) │
@@ -433,6 +483,11 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
433
483
  model_used: 'verifier',
434
484
  reason: `Query complexity (${complexity}) warranted direct verifier routing`
435
485
  };
486
+ this.attachCascadeMetadata(verifierMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
487
+ modelUsed: 'verifier',
488
+ domain: detectedDomain,
489
+ costs: costBreakdown,
490
+ }));
436
491
  return {
437
492
  generations: [{
438
493
  text: verifierMessage.content.toString(),
@@ -441,13 +496,13 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
441
496
  };
442
497
  }
443
498
  // Step 4: Try domain-specific model first if available
444
- const modelToUse = domainModel || this.drafterModel;
499
+ const modelToUse = domainModel || (await this.getDrafterModel());
445
500
  const modelType = domainModel ? `domain:${detectedDomain}` : 'drafter';
446
501
  const modelInfo = this.getModelInfo(modelToUse);
447
502
  await runManager?.handleText(`🎯 CascadeFlow: Trying ${modelType} model: ${modelInfo}\n`);
448
503
  console.log(`🎯 CascadeFlow: Trying ${modelType} model: ${modelInfo}`);
449
504
  const drafterStartTime = Date.now();
450
- const drafterMessage = await executeWithProtection(modelToUse, modelType);
505
+ const drafterMessage = await modelToUse.invoke(messages, options);
451
506
  const drafterLatency = Date.now() - drafterStartTime;
452
507
  if (domainModel && detectedDomain) {
453
508
  this.domainCounts.set(detectedDomain, (this.domainCounts.get(detectedDomain) || 0) + 1);
@@ -469,7 +524,7 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
469
524
  Query → ${modelType} → Tool Calls (${toolCallsCount}) → Response
470
525
  ⚡ Tool calling: ${modelType} generated tool calls
471
526
  Model used: ${modelInfo}
472
- ${detectedDomain ? `Domain: ${DOMAIN_DISPLAY_NAMES[detectedDomain]}` : ''}
527
+ ${detectedDomain ? `Domain: ${config_1.DOMAIN_DISPLAY_NAMES[detectedDomain]}` : ''}
473
528
  Latency: ${drafterLatency}ms
474
529
  📊 Stats: ${this.drafterCount} drafter, ${this.verifierCount} verifier
475
530
  `;
@@ -486,6 +541,20 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
486
541
  latency_ms: drafterLatency,
487
542
  model_used: modelType
488
543
  };
544
+ const drafterCost = await this.calculateMessageCost(drafterMessage, modelToUse);
545
+ const baselineCost = await this.estimateAlternateModelCost(drafterMessage, this.verifierModel);
546
+ const costBreakdown = {
547
+ drafter: drafterCost,
548
+ verifier: 0,
549
+ total: drafterCost,
550
+ ...(detectedDomain ? { domain: drafterCost } : {}),
551
+ };
552
+ this.attachCascadeMetadata(drafterMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
553
+ modelUsed: modelType,
554
+ domain: detectedDomain,
555
+ costs: costBreakdown,
556
+ baselineCost,
557
+ }));
489
558
  return {
490
559
  generations: [{
491
560
  text: drafterMessage.content.toString(),
@@ -495,15 +564,15 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
495
564
  }
496
565
  // Step 6: Quality check with domain-aware threshold
497
566
  const responseText = drafterMessage.content.toString();
498
- const effectiveThreshold = detectedDomain
499
- ? (this.domainConfigs.get(detectedDomain)?.threshold ?? this.qualityThreshold)
500
- : this.qualityThreshold;
567
+ const domainThreshold = detectedDomain
568
+ ? this.domainConfigs.get(detectedDomain)?.threshold
569
+ : undefined;
570
+ const effectiveThreshold = domainThreshold ?? this.getThresholdForComplexity(complexity);
501
571
  let validationResult;
502
572
  if (this.qualityValidator) {
503
573
  try {
504
- validationResult = await this.qualityValidator.validate(responseText, queryText);
505
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
506
- const qualityLog = ` 📊 Quality validation: confidence=${validationResult.confidence.toFixed(2)}, threshold=${effectiveThreshold}, method=${validationResult.method}\n`;
574
+ validationResult = await this.qualityValidator.validate(responseText, queryText, undefined, complexity, domainThreshold);
575
+ const qualityLog = ` 📊 Quality validation: confidence=${validationResult.confidence.toFixed(2)}, threshold=${effectiveThreshold}, method=${validationResult.method}${complexity ? `, complexity=${complexity}` : ''}\n`;
507
576
  await runManager?.handleText(qualityLog);
508
577
  console.log(qualityLog);
509
578
  if (validationResult.details?.alignmentScore) {
@@ -516,13 +585,11 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
516
585
  const errorLog = ` ⚠️ Quality validator error, using simple check: ${e}\n`;
517
586
  await runManager?.handleText(errorLog);
518
587
  console.warn(errorLog);
519
- validationResult = this.simpleQualityCheck(responseText);
520
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
588
+ validationResult = this.simpleQualityCheck(responseText, effectiveThreshold);
521
589
  }
522
590
  }
523
591
  else {
524
- validationResult = this.simpleQualityCheck(responseText);
525
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
592
+ validationResult = this.simpleQualityCheck(responseText, effectiveThreshold);
526
593
  const simpleLog = ` 📊 Simple quality check: confidence=${validationResult.confidence.toFixed(2)}\n`;
527
594
  await runManager?.handleText(simpleLog);
528
595
  console.log(simpleLog);
@@ -530,6 +597,13 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
530
597
  // Step 7: If quality is sufficient, return response
531
598
  if (validationResult.passed) {
532
599
  const drafterCost = await this.calculateMessageCost(drafterMessage, modelToUse);
600
+ const baselineCost = await this.estimateAlternateModelCost(drafterMessage, this.verifierModel);
601
+ const costBreakdown = {
602
+ drafter: drafterCost,
603
+ verifier: 0,
604
+ total: drafterCost,
605
+ ...(detectedDomain ? { domain: drafterCost } : {}),
606
+ };
533
607
  const flowLog = `
534
608
  ┌─────────────────────────────────────────┐
535
609
  │ ✅ FLOW: ${modelType.toUpperCase()} ACCEPTED (FAST PATH) │
@@ -537,7 +611,7 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
537
611
  Query → ${detectedDomain ? `Domain(${detectedDomain}) → ` : ''}${modelType} → Quality Check ✅ → Response
538
612
  ⚡ Fast & Cheap: Used ${modelType} model only
539
613
  Model used: ${modelInfo}
540
- ${detectedDomain ? `Domain: ${DOMAIN_DISPLAY_NAMES[detectedDomain]}` : ''}
614
+ ${detectedDomain ? `Domain: ${config_1.DOMAIN_DISPLAY_NAMES[detectedDomain]}` : ''}
541
615
  Confidence: ${validationResult.confidence.toFixed(2)} (threshold: ${effectiveThreshold})
542
616
  Quality score: ${validationResult.score.toFixed(2)}
543
617
  Latency: ${drafterLatency}ms
@@ -558,6 +632,13 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
558
632
  cost_usd: drafterCost,
559
633
  model_used: modelType
560
634
  };
635
+ this.attachCascadeMetadata(drafterMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
636
+ modelUsed: modelType,
637
+ domain: detectedDomain,
638
+ confidence: validationResult.confidence,
639
+ costs: costBreakdown,
640
+ baselineCost,
641
+ }));
561
642
  return {
562
643
  generations: [{
563
644
  text: drafterMessage.content.toString(),
@@ -582,18 +663,19 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
582
663
  const verifierStartTime = Date.now();
583
664
  const verifierModel = await this.getVerifierModel();
584
665
  const verifierInfo = this.getModelInfo(verifierModel);
585
- const verifierMessage = await executeWithProtection(verifierModel, 'verifier');
666
+ const verifierMessage = await verifierModel.invoke(messages, options);
586
667
  const verifierLatency = Date.now() - verifierStartTime;
587
668
  this.verifierCount++;
588
669
  const verifierCost = await this.calculateMessageCost(verifierMessage, verifierModel);
589
- const totalCost = verifierCost;
670
+ const drafterCost = await this.calculateMessageCost(drafterMessage, modelToUse);
671
+ const totalCost = drafterCost + verifierCost;
590
672
  const totalLatency = drafterLatency + verifierLatency;
591
673
  const acceptanceRate = (this.drafterCount / (this.drafterCount + this.verifierCount) * 100).toFixed(1);
592
674
  const completionLog = ` ✅ Verifier completed successfully
593
675
  Model used: ${verifierInfo}
594
676
  Verifier latency: ${verifierLatency}ms
595
677
  Total latency: ${totalLatency}ms (${modelType}: ${drafterLatency}ms + verifier: ${verifierLatency}ms)
596
- 💰 Cost: $${totalCost.toFixed(6)} (verifier only, ${modelType} attempt wasted)
678
+ 💰 Cost: $${totalCost.toFixed(6)} (drafter $${drafterCost.toFixed(6)} + verifier $${verifierCost.toFixed(6)})
597
679
  📊 Stats: ${this.drafterCount} drafter (${acceptanceRate}%), ${this.verifierCount} verifier
598
680
  `;
599
681
  await runManager?.handleText(completionLog);
@@ -612,6 +694,19 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
612
694
  model_used: 'verifier',
613
695
  reason: validationResult.reason
614
696
  };
697
+ const costBreakdown = {
698
+ drafter: drafterCost,
699
+ verifier: verifierCost,
700
+ total: totalCost,
701
+ ...(detectedDomain ? { domain: drafterCost } : {}),
702
+ };
703
+ this.attachCascadeMetadata(verifierMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
704
+ modelUsed: 'verifier',
705
+ domain: detectedDomain,
706
+ confidence: validationResult.confidence,
707
+ costs: costBreakdown,
708
+ baselineCost: totalCost,
709
+ }));
615
710
  return {
616
711
  generations: [{
617
712
  text: verifierMessage.content.toString(),
@@ -620,15 +715,13 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
620
715
  };
621
716
  }
622
717
  catch (error) {
623
- // Handle circuit breaker open state
624
718
  const errorMsg = error instanceof Error ? error.message : String(error);
625
- const isCircuitOpen = errorMsg.includes('Circuit breaker') || errorMsg.includes('circuit is open');
626
719
  const errorLog = `
627
720
  ┌─────────────────────────────────────────────┐
628
- │ ❌ FLOW: ${isCircuitOpen ? 'CIRCUIT OPEN' : 'DRAFTER ERROR'} - FALLBACK PATH
721
+ │ ❌ FLOW: DRAFTER ERROR - FALLBACK PATH
629
722
  └─────────────────────────────────────────────┘
630
- Query → Drafter ❌ ${isCircuitOpen ? 'CIRCUIT OPEN' : 'ERROR'} → Verifier → Response
631
- 🔄 Fallback: ${isCircuitOpen ? 'Circuit breaker open' : 'Drafter failed'}, using verifier as backup
723
+ Query → Drafter ❌ ERROR → Verifier → Response
724
+ 🔄 Fallback: Drafter failed, using verifier as backup
632
725
  Error: ${errorMsg}
633
726
  🔄 Loading verifier model...
634
727
  `;
@@ -638,9 +731,10 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
638
731
  const verifierInfo = this.getModelInfo(verifierModel);
639
732
  const verifierMessage = await verifierModel.invoke(messages, options);
640
733
  this.verifierCount++;
734
+ const verifierCost = await this.calculateMessageCost(verifierMessage, verifierModel);
641
735
  const fallbackCompleteLog = ` ✅ Verifier fallback completed successfully
642
736
  Model used: ${verifierInfo}
643
- 💰 Cost: Full verifier cost (fallback due to ${isCircuitOpen ? 'circuit open' : 'error'})
737
+ 💰 Cost: $${verifierCost.toFixed(6)} (fallback due to error)
644
738
  `;
645
739
  await runManager?.handleText(fallbackCompleteLog);
646
740
  console.log(fallbackCompleteLog);
@@ -648,11 +742,23 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
648
742
  verifierMessage.response_metadata = {};
649
743
  }
650
744
  verifierMessage.response_metadata.cascadeflow = {
651
- flow: isCircuitOpen ? 'circuit_breaker_fallback' : 'error_fallback',
745
+ flow: 'error_fallback',
652
746
  error: errorMsg,
653
747
  cost_savings_percent: 0,
654
- model_used: 'verifier'
748
+ model_used: 'verifier',
749
+ cost_usd: verifierCost
750
+ };
751
+ const costBreakdown = {
752
+ drafter: 0,
753
+ verifier: verifierCost,
754
+ total: verifierCost,
655
755
  };
756
+ this.attachCascadeMetadata(verifierMessage, (0, cascade_metadata_1.buildCascadeMetadata)({
757
+ modelUsed: 'verifier',
758
+ domain: null,
759
+ costs: costBreakdown,
760
+ baselineCost: verifierCost,
761
+ }));
656
762
  return {
657
763
  generations: [{
658
764
  text: verifierMessage.content.toString(),
@@ -669,18 +775,28 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
669
775
  const queryText = messages.map(m => m.content.toString()).join(' ');
670
776
  // Detect domain for streaming
671
777
  let detectedDomain = null;
672
- let modelToUse = this.drafterModel;
778
+ let complexity;
779
+ let modelToUse = null;
780
+ let usingDomainModel = false;
673
781
  if (this.enabledDomains.length > 0) {
674
782
  detectedDomain = await this.detectDomain(queryText);
675
783
  if (detectedDomain) {
676
- const domainConfig = this.domainConfigs.get(detectedDomain);
677
- if (domainConfig?.model) {
678
- modelToUse = domainConfig.model;
784
+ const connectedDomainModel = await this.getDomainModel(detectedDomain);
785
+ if (connectedDomainModel) {
786
+ modelToUse = connectedDomainModel;
787
+ usingDomainModel = true;
679
788
  }
680
789
  }
681
790
  }
791
+ if (this.complexityDetector && this.useComplexityThresholds) {
792
+ const complexityResult = await this.detectComplexity(queryText);
793
+ complexity = complexityResult.level;
794
+ }
795
+ if (!modelToUse) {
796
+ modelToUse = await this.getDrafterModel();
797
+ }
682
798
  const modelInfo = this.getModelInfo(modelToUse);
683
- const modelType = detectedDomain ? `domain:${detectedDomain}` : 'drafter';
799
+ const modelType = usingDomainModel && detectedDomain ? `domain:${detectedDomain}` : 'drafter';
684
800
  await runManager?.handleText(`🎯 CascadeFlow (Streaming): Trying ${modelType} model: ${modelInfo}\n`);
685
801
  console.log(`🎯 CascadeFlow (Streaming): Trying ${modelType} model: ${modelInfo}`);
686
802
  const drafterStartTime = Date.now();
@@ -697,7 +813,12 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
697
813
  yield generationChunk;
698
814
  }
699
815
  const drafterLatency = Date.now() - drafterStartTime;
700
- this.drafterCount++;
816
+ if (usingDomainModel && detectedDomain) {
817
+ this.domainCounts.set(detectedDomain, (this.domainCounts.get(detectedDomain) || 0) + 1);
818
+ }
819
+ else {
820
+ this.drafterCount++;
821
+ }
701
822
  if (lastChunk && this.hasToolCalls(lastChunk.message)) {
702
823
  const toolCallsCount = this.getToolCallsCount(lastChunk.message);
703
824
  const toolLog = `\n🔧 Tool calls detected (${toolCallsCount}) - cascade complete\n`;
@@ -706,24 +827,22 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
706
827
  return;
707
828
  }
708
829
  await runManager?.handleText(`\n📊 Running quality check...\n`);
709
- const effectiveThreshold = detectedDomain
710
- ? (this.domainConfigs.get(detectedDomain)?.threshold ?? this.qualityThreshold)
711
- : this.qualityThreshold;
830
+ const domainThreshold = detectedDomain
831
+ ? this.domainConfigs.get(detectedDomain)?.threshold
832
+ : undefined;
833
+ const effectiveThreshold = domainThreshold ?? this.getThresholdForComplexity(complexity);
712
834
  let validationResult;
713
835
  if (this.qualityValidator) {
714
836
  try {
715
- validationResult = await this.qualityValidator.validate(fullDrafterContent, queryText);
716
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
837
+ validationResult = await this.qualityValidator.validate(fullDrafterContent, queryText, undefined, complexity, domainThreshold);
717
838
  await runManager?.handleText(` Confidence: ${validationResult.confidence.toFixed(2)} (threshold: ${effectiveThreshold})\n`);
718
839
  }
719
840
  catch (e) {
720
- validationResult = this.simpleQualityCheck(fullDrafterContent);
721
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
841
+ validationResult = this.simpleQualityCheck(fullDrafterContent, effectiveThreshold);
722
842
  }
723
843
  }
724
844
  else {
725
- validationResult = this.simpleQualityCheck(fullDrafterContent);
726
- validationResult.passed = validationResult.confidence >= effectiveThreshold;
845
+ validationResult = this.simpleQualityCheck(fullDrafterContent, effectiveThreshold);
727
846
  }
728
847
  if (validationResult.passed) {
729
848
  await runManager?.handleText(`✅ Quality check passed - cascade complete (${modelType} accepted)\n`);
@@ -764,6 +883,7 @@ class CascadeChatModel extends chat_models_1.BaseChatModel {
764
883
  }
765
884
  }
766
885
  }
886
+ exports.CascadeChatModel = CascadeChatModel;
767
887
  // =============================================================================
768
888
  // Generate dynamic inputs based on enabled domains
769
889
  // =============================================================================
@@ -785,7 +905,7 @@ function generateDomainInputs(enabledDomains) {
785
905
  // Add domain-specific model inputs for each enabled domain
786
906
  for (const domain of enabledDomains) {
787
907
  baseInputs.push({
788
- displayName: `${DOMAIN_DISPLAY_NAMES[domain]} Model`,
908
+ displayName: `${config_1.DOMAIN_DISPLAY_NAMES[domain]} Model`,
789
909
  type: 'ai_languageModel',
790
910
  maxConnections: 1,
791
911
  required: false,
@@ -797,10 +917,18 @@ function generateDomainInputs(enabledDomains) {
797
917
  // Generate domain toggle properties for n8n UI
798
918
  // =============================================================================
799
919
  function generateDomainProperties() {
800
- const domainOptions = Object.entries(DOMAINS).map(([key, value]) => ({
801
- name: DOMAIN_DISPLAY_NAMES[value],
920
+ const domainOptions = Object.entries(config_1.DOMAINS).map(([key, value]) => ({
921
+ name: config_1.DOMAIN_DISPLAY_NAMES[value],
802
922
  value: value,
803
- description: DOMAIN_DESCRIPTIONS[value],
923
+ description: config_1.DOMAIN_DESCRIPTIONS[value],
924
+ }));
925
+ const domainToggleProperties = config_1.DOMAIN_UI_CONFIGS.map(({ domain, toggleName }) => ({
926
+ displayName: `Enable ${config_1.DOMAIN_DISPLAY_NAMES[domain]} Domain`,
927
+ name: toggleName,
928
+ type: 'boolean',
929
+ default: false,
930
+ displayOptions: { show: { enableDomainRouting: [true] } },
931
+ description: `Whether to enable ${config_1.DOMAIN_DESCRIPTIONS[domain]}. When enabled, adds a "${config_1.DOMAIN_DISPLAY_NAMES[domain]} Model" input port.`,
804
932
  }));
805
933
  return [
806
934
  {
@@ -811,70 +939,7 @@ function generateDomainProperties() {
811
939
  description: 'Whether to enable intelligent routing based on detected query domain (math, code, legal, etc.)',
812
940
  },
813
941
  // Individual domain toggles - each one adds its own model input port
814
- {
815
- displayName: 'Enable Code Domain',
816
- name: 'enableCodeDomain',
817
- type: 'boolean',
818
- default: false,
819
- displayOptions: { show: { enableDomainRouting: [true] } },
820
- description: 'Whether to enable code domain routing. When enabled, adds a "Code Model" input port.',
821
- },
822
- {
823
- displayName: 'Enable Math Domain',
824
- name: 'enableMathDomain',
825
- type: 'boolean',
826
- default: false,
827
- displayOptions: { show: { enableDomainRouting: [true] } },
828
- description: 'Whether to enable math domain routing. When enabled, adds a "Math Model" input port.',
829
- },
830
- {
831
- displayName: 'Enable Data Domain',
832
- name: 'enableDataDomain',
833
- type: 'boolean',
834
- default: false,
835
- displayOptions: { show: { enableDomainRouting: [true] } },
836
- description: 'Whether to enable data analysis domain routing. When enabled, adds a "Data Model" input port.',
837
- },
838
- {
839
- displayName: 'Enable Creative Domain',
840
- name: 'enableCreativeDomain',
841
- type: 'boolean',
842
- default: false,
843
- displayOptions: { show: { enableDomainRouting: [true] } },
844
- description: 'Whether to enable creative writing domain routing. When enabled, adds a "Creative Model" input port.',
845
- },
846
- {
847
- displayName: 'Enable Legal Domain',
848
- name: 'enableLegalDomain',
849
- type: 'boolean',
850
- default: false,
851
- displayOptions: { show: { enableDomainRouting: [true] } },
852
- description: 'Whether to enable legal domain routing. When enabled, adds a "Legal Model" input port.',
853
- },
854
- {
855
- displayName: 'Enable Medical Domain',
856
- name: 'enableMedicalDomain',
857
- type: 'boolean',
858
- default: false,
859
- displayOptions: { show: { enableDomainRouting: [true] } },
860
- description: 'Whether to enable medical domain routing. When enabled, adds a "Medical Model" input port.',
861
- },
862
- {
863
- displayName: 'Enable Financial Domain',
864
- name: 'enableFinancialDomain',
865
- type: 'boolean',
866
- default: false,
867
- displayOptions: { show: { enableDomainRouting: [true] } },
868
- description: 'Whether to enable financial domain routing. When enabled, adds a "Financial Model" input port.',
869
- },
870
- {
871
- displayName: 'Enable Science Domain',
872
- name: 'enableScienceDomain',
873
- type: 'boolean',
874
- default: false,
875
- displayOptions: { show: { enableDomainRouting: [true] } },
876
- description: 'Whether to enable science domain routing. When enabled, adds a "Science Model" input port.',
877
- },
942
+ ...domainToggleProperties,
878
943
  {
879
944
  displayName: 'Domain-Specific Settings',
880
945
  name: 'domainSettings',
@@ -905,7 +970,7 @@ function generateDomainProperties() {
905
970
  displayName: 'Quality Threshold',
906
971
  name: 'threshold',
907
972
  type: 'number',
908
- default: 0.64,
973
+ default: 0.4,
909
974
  typeOptions: {
910
975
  minValue: 0,
911
976
  maxValue: 1,
@@ -980,26 +1045,50 @@ class LmChatCascadeFlow {
980
1045
  if (params?.enableCodeDomain) {
981
1046
  inputs.push({ displayName: 'Code', type: 'ai_languageModel', maxConnections: 1, required: false });
982
1047
  }
983
- if (params?.enableMathDomain) {
984
- inputs.push({ displayName: 'Math', type: 'ai_languageModel', maxConnections: 1, required: false });
985
- }
986
1048
  if (params?.enableDataDomain) {
987
- inputs.push({ displayName: 'Data', type: 'ai_languageModel', maxConnections: 1, required: false });
1049
+ inputs.push({ displayName: 'Data Analysis', type: 'ai_languageModel', maxConnections: 1, required: false });
1050
+ }
1051
+ if (params?.enableStructuredDomain) {
1052
+ inputs.push({ displayName: 'Structured Output', type: 'ai_languageModel', maxConnections: 1, required: false });
1053
+ }
1054
+ if (params?.enableRagDomain) {
1055
+ inputs.push({ displayName: 'RAG (Retrieval)', type: 'ai_languageModel', maxConnections: 1, required: false });
1056
+ }
1057
+ if (params?.enableConversationDomain) {
1058
+ inputs.push({ displayName: 'Conversation', type: 'ai_languageModel', maxConnections: 1, required: false });
1059
+ }
1060
+ if (params?.enableToolDomain) {
1061
+ inputs.push({ displayName: 'Tool Calling', type: 'ai_languageModel', maxConnections: 1, required: false });
988
1062
  }
989
1063
  if (params?.enableCreativeDomain) {
990
1064
  inputs.push({ displayName: 'Creative', type: 'ai_languageModel', maxConnections: 1, required: false });
991
1065
  }
992
- if (params?.enableLegalDomain) {
993
- inputs.push({ displayName: 'Legal', type: 'ai_languageModel', maxConnections: 1, required: false });
1066
+ if (params?.enableSummaryDomain) {
1067
+ inputs.push({ displayName: 'Summary', type: 'ai_languageModel', maxConnections: 1, required: false });
1068
+ }
1069
+ if (params?.enableTranslationDomain) {
1070
+ inputs.push({ displayName: 'Translation', type: 'ai_languageModel', maxConnections: 1, required: false });
1071
+ }
1072
+ if (params?.enableMathDomain) {
1073
+ inputs.push({ displayName: 'Math', type: 'ai_languageModel', maxConnections: 1, required: false });
1074
+ }
1075
+ if (params?.enableScienceDomain) {
1076
+ inputs.push({ displayName: 'Science', type: 'ai_languageModel', maxConnections: 1, required: false });
994
1077
  }
995
1078
  if (params?.enableMedicalDomain) {
996
1079
  inputs.push({ displayName: 'Medical', type: 'ai_languageModel', maxConnections: 1, required: false });
997
1080
  }
1081
+ if (params?.enableLegalDomain) {
1082
+ inputs.push({ displayName: 'Legal', type: 'ai_languageModel', maxConnections: 1, required: false });
1083
+ }
998
1084
  if (params?.enableFinancialDomain) {
999
1085
  inputs.push({ displayName: 'Financial', type: 'ai_languageModel', maxConnections: 1, required: false });
1000
1086
  }
1001
- if (params?.enableScienceDomain) {
1002
- inputs.push({ displayName: 'Science', type: 'ai_languageModel', maxConnections: 1, required: false });
1087
+ if (params?.enableMultimodalDomain) {
1088
+ inputs.push({ displayName: 'Multimodal', type: 'ai_languageModel', maxConnections: 1, required: false });
1089
+ }
1090
+ if (params?.enableGeneralDomain) {
1091
+ inputs.push({ displayName: 'General', type: 'ai_languageModel', maxConnections: 1, required: false });
1003
1092
  }
1004
1093
  }
1005
1094
 
@@ -1013,13 +1102,85 @@ class LmChatCascadeFlow {
1013
1102
  displayName: 'Quality Threshold',
1014
1103
  name: 'qualityThreshold',
1015
1104
  type: 'number',
1016
- default: 0.64,
1105
+ default: 0.4,
1106
+ typeOptions: {
1107
+ minValue: 0,
1108
+ maxValue: 1,
1109
+ numberPrecision: 2,
1110
+ },
1111
+ description: 'Minimum quality score (0-1) to accept drafter response when complexity thresholds are disabled',
1112
+ },
1113
+ {
1114
+ displayName: 'Use Complexity Thresholds',
1115
+ name: 'useComplexityThresholds',
1116
+ type: 'boolean',
1117
+ default: true,
1118
+ description: 'Whether to use per-complexity confidence thresholds (trivial → expert) to match CascadeFlow Python defaults',
1119
+ },
1120
+ {
1121
+ displayName: 'Trivial Threshold',
1122
+ name: 'trivialThreshold',
1123
+ type: 'number',
1124
+ default: 0.25,
1125
+ displayOptions: { show: { useComplexityThresholds: [true] } },
1017
1126
  typeOptions: {
1018
1127
  minValue: 0,
1019
1128
  maxValue: 1,
1020
1129
  numberPrecision: 2,
1021
1130
  },
1022
- description: 'Minimum quality score (0-1) to accept drafter response. Lower = more cost savings, higher = better quality.',
1131
+ description: 'Minimum confidence for trivial queries',
1132
+ },
1133
+ {
1134
+ displayName: 'Simple Threshold',
1135
+ name: 'simpleThreshold',
1136
+ type: 'number',
1137
+ default: 0.4,
1138
+ displayOptions: { show: { useComplexityThresholds: [true] } },
1139
+ typeOptions: {
1140
+ minValue: 0,
1141
+ maxValue: 1,
1142
+ numberPrecision: 2,
1143
+ },
1144
+ description: 'Minimum confidence for simple queries',
1145
+ },
1146
+ {
1147
+ displayName: 'Moderate Threshold',
1148
+ name: 'moderateThreshold',
1149
+ type: 'number',
1150
+ default: 0.55,
1151
+ displayOptions: { show: { useComplexityThresholds: [true] } },
1152
+ typeOptions: {
1153
+ minValue: 0,
1154
+ maxValue: 1,
1155
+ numberPrecision: 2,
1156
+ },
1157
+ description: 'Minimum confidence for moderate queries',
1158
+ },
1159
+ {
1160
+ displayName: 'Hard Threshold',
1161
+ name: 'hardThreshold',
1162
+ type: 'number',
1163
+ default: 0.7,
1164
+ displayOptions: { show: { useComplexityThresholds: [true] } },
1165
+ typeOptions: {
1166
+ minValue: 0,
1167
+ maxValue: 1,
1168
+ numberPrecision: 2,
1169
+ },
1170
+ description: 'Minimum confidence for hard queries',
1171
+ },
1172
+ {
1173
+ displayName: 'Expert Threshold',
1174
+ name: 'expertThreshold',
1175
+ type: 'number',
1176
+ default: 0.8,
1177
+ displayOptions: { show: { useComplexityThresholds: [true] } },
1178
+ typeOptions: {
1179
+ minValue: 0,
1180
+ maxValue: 1,
1181
+ numberPrecision: 2,
1182
+ },
1183
+ description: 'Minimum confidence for expert queries',
1023
1184
  },
1024
1185
  {
1025
1186
  displayName: 'Enable Alignment Scoring',
@@ -1035,13 +1196,6 @@ class LmChatCascadeFlow {
1035
1196
  default: true,
1036
1197
  description: 'Whether to route queries directly to the verifier based on detected complexity',
1037
1198
  },
1038
- {
1039
- displayName: 'Enable Circuit Breaker',
1040
- name: 'useCircuitBreaker',
1041
- type: 'boolean',
1042
- default: true,
1043
- description: 'Whether to use circuit breaker for fault tolerance (auto-fallback on repeated failures)',
1044
- },
1045
1199
  // Domain routing settings
1046
1200
  ...generateDomainProperties(),
1047
1201
  ],
@@ -1049,41 +1203,30 @@ class LmChatCascadeFlow {
1049
1203
  }
1050
1204
  async supplyData() {
1051
1205
  // Get core parameters
1052
- const qualityThreshold = this.getNodeParameter('qualityThreshold', 0, 0.64);
1206
+ const qualityThreshold = this.getNodeParameter('qualityThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.simple);
1053
1207
  const useSemanticValidation = false; // Disabled - loads heavy ML model causing OOM in n8n
1054
1208
  const useAlignmentScoring = this.getNodeParameter('useAlignmentScoring', 0, true);
1055
1209
  const useComplexityRouting = this.getNodeParameter('useComplexityRouting', 0, true);
1056
- const useCircuitBreaker = this.getNodeParameter('useCircuitBreaker', 0, true);
1210
+ const useComplexityThresholds = this.getNodeParameter('useComplexityThresholds', 0, true);
1211
+ const confidenceThresholds = useComplexityThresholds
1212
+ ? {
1213
+ trivial: this.getNodeParameter('trivialThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.trivial),
1214
+ simple: this.getNodeParameter('simpleThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.simple),
1215
+ moderate: this.getNodeParameter('moderateThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.moderate),
1216
+ hard: this.getNodeParameter('hardThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.hard),
1217
+ expert: this.getNodeParameter('expertThreshold', 0, config_1.DEFAULT_COMPLEXITY_THRESHOLDS.expert),
1218
+ }
1219
+ : undefined;
1057
1220
  // Get domain routing parameters
1058
1221
  const enableDomainRouting = this.getNodeParameter('enableDomainRouting', 0, false);
1059
1222
  // Build enabledDomains array from individual toggle parameters
1060
1223
  const enabledDomains = [];
1061
1224
  if (enableDomainRouting) {
1062
- // Check each domain toggle and build the array in the same order as the inputs template
1063
- if (this.getNodeParameter('enableCodeDomain', 0, false)) {
1064
- enabledDomains.push('code');
1065
- }
1066
- if (this.getNodeParameter('enableMathDomain', 0, false)) {
1067
- enabledDomains.push('math');
1068
- }
1069
- if (this.getNodeParameter('enableDataDomain', 0, false)) {
1070
- enabledDomains.push('data');
1071
- }
1072
- if (this.getNodeParameter('enableCreativeDomain', 0, false)) {
1073
- enabledDomains.push('creative');
1074
- }
1075
- if (this.getNodeParameter('enableLegalDomain', 0, false)) {
1076
- enabledDomains.push('legal');
1077
- }
1078
- if (this.getNodeParameter('enableMedicalDomain', 0, false)) {
1079
- enabledDomains.push('medical');
1080
- }
1081
- if (this.getNodeParameter('enableFinancialDomain', 0, false)) {
1082
- enabledDomains.push('financial');
1083
- }
1084
- if (this.getNodeParameter('enableScienceDomain', 0, false)) {
1085
- enabledDomains.push('science');
1225
+ const toggleParams = {};
1226
+ for (const { toggleName } of config_1.DOMAIN_UI_CONFIGS) {
1227
+ toggleParams[toggleName] = this.getNodeParameter(toggleName, 0, false);
1086
1228
  }
1229
+ enabledDomains.push(...(0, config_1.getEnabledDomains)(toggleParams));
1087
1230
  }
1088
1231
  // Get domain-specific settings
1089
1232
  const domainSettingsRaw = this.getNodeParameter('domainSettings', 0, { domainConfig: [] });
@@ -1097,12 +1240,15 @@ class LmChatCascadeFlow {
1097
1240
  });
1098
1241
  }
1099
1242
  }
1100
- // Get the drafter model (index 1 - bottom port, labeled "Drafter")
1101
- const drafterData = await this.getInputConnectionData('ai_languageModel', 1);
1102
- const drafterModel = (Array.isArray(drafterData) ? drafterData[0] : drafterData);
1103
- if (!drafterModel) {
1104
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Drafter model is required. Please connect your DRAFTER model to the BOTTOM port (labeled "Drafter").');
1105
- }
1243
+ // Lazy loader for drafter (index 1 - bottom port, labeled "Drafter")
1244
+ const drafterModelGetter = async () => {
1245
+ const drafterData = await this.getInputConnectionData('ai_languageModel', 1);
1246
+ const drafterModel = (Array.isArray(drafterData) ? drafterData[0] : drafterData);
1247
+ if (!drafterModel) {
1248
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Drafter model is required. Please connect your DRAFTER model to the BOTTOM port (labeled "Drafter").');
1249
+ }
1250
+ return drafterModel;
1251
+ };
1106
1252
  // Create lazy loader for verifier (index 0 - top port, labeled "Verifier")
1107
1253
  const verifierModelGetter = async () => {
1108
1254
  const verifierData = await this.getInputConnectionData('ai_languageModel', 0);
@@ -1127,50 +1273,33 @@ class LmChatCascadeFlow {
1127
1273
  try {
1128
1274
  const domainData = await this.getInputConnectionData('ai_languageModel', domainIndex);
1129
1275
  const domainModel = (Array.isArray(domainData) ? domainData[0] : domainData);
1130
- if (domainModel) {
1131
- // Store in domain config
1132
- const config = domainConfigs.get(domain) || {
1133
- enabled: true,
1134
- threshold: qualityThreshold,
1135
- temperature: 0.7,
1136
- };
1137
- config.model = domainModel;
1138
- domainConfigs.set(domain, config);
1139
- return domainModel;
1140
- }
1276
+ return domainModel || undefined;
1141
1277
  }
1142
- catch (e) {
1143
- console.log(`No ${domain} model connected, using drafter as fallback`);
1278
+ catch {
1279
+ return undefined;
1144
1280
  }
1145
1281
  return undefined;
1146
1282
  });
1147
1283
  }
1148
1284
  }
1149
- // Eagerly load domain models
1150
- for (const [domain, getter] of domainModelGetters.entries()) {
1151
- await getter();
1152
- }
1153
- // Debug info
1154
- const getDrafterInfo = () => {
1155
- const type = typeof drafterModel._llmType === 'function' ? drafterModel._llmType() : 'unknown';
1156
- const modelName = drafterModel.modelName || drafterModel.model || 'unknown';
1157
- return `${type} (${modelName})`;
1158
- };
1159
1285
  console.log('🚀 CascadeFlow v2 initialized');
1160
1286
  console.log(` PORT MAPPING:`);
1161
1287
  console.log(` ├─ TOP port (labeled "Verifier") → VERIFIER model: lazy-loaded`);
1162
- console.log(` └─ BOTTOM port (labeled "Drafter") → DRAFTER model: ${getDrafterInfo()}`);
1288
+ console.log(` └─ BOTTOM port (labeled "Drafter") → DRAFTER model: lazy-loaded`);
1163
1289
  console.log(` Quality threshold: ${qualityThreshold}`);
1164
1290
  console.log(` Semantic validation: ${useSemanticValidation ? 'enabled' : 'disabled'}`);
1165
1291
  console.log(` Alignment scoring: ${useAlignmentScoring ? 'enabled' : 'disabled'}`);
1166
1292
  console.log(` Complexity routing: ${useComplexityRouting ? 'enabled' : 'disabled'}`);
1167
- console.log(` Circuit breaker: ${useCircuitBreaker ? 'enabled' : 'disabled'}`);
1293
+ console.log(` Complexity thresholds: ${useComplexityThresholds ? 'enabled' : 'disabled'}`);
1168
1294
  console.log(` Domain routing: ${enableDomainRouting ? 'enabled' : 'disabled'}`);
1169
1295
  if (enabledDomains.length > 0) {
1170
1296
  console.log(` Enabled domains: ${enabledDomains.join(', ')}`);
1171
1297
  }
1298
+ if (useComplexityThresholds && confidenceThresholds) {
1299
+ console.log(` Thresholds: ${JSON.stringify(confidenceThresholds)}`);
1300
+ }
1172
1301
  // Create and return the cascade model
1173
- const cascadeModel = new CascadeChatModel(drafterModel, verifierModelGetter, qualityThreshold, useSemanticValidation, useAlignmentScoring, useComplexityRouting, enableDomainRouting, enabledDomains, domainModelGetters, domainConfigs, useCircuitBreaker);
1302
+ const cascadeModel = new CascadeChatModel(drafterModelGetter, verifierModelGetter, qualityThreshold, useSemanticValidation, useAlignmentScoring, useComplexityRouting, useComplexityThresholds, enableDomainRouting, enabledDomains, domainModelGetters, domainConfigs, confidenceThresholds);
1174
1303
  return {
1175
1304
  response: cascadeModel,
1176
1305
  };