lynkr 7.2.5 → 8.0.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 +2 -2
- package/config/model-tiers.json +89 -0
- package/docs/docs.html +1 -0
- package/docs/index.md +7 -0
- package/docs/toon-integration-spec.md +130 -0
- package/documentation/README.md +3 -2
- package/documentation/claude-code-cli.md +23 -16
- package/documentation/cursor-integration.md +17 -14
- package/documentation/docker.md +11 -4
- package/documentation/embeddings.md +7 -5
- package/documentation/faq.md +66 -12
- package/documentation/features.md +22 -15
- package/documentation/installation.md +66 -14
- package/documentation/production.md +43 -8
- package/documentation/providers.md +145 -42
- package/documentation/routing.md +476 -0
- package/documentation/token-optimization.md +7 -5
- package/documentation/troubleshooting.md +81 -5
- package/install.sh +6 -1
- package/package.json +4 -2
- package/scripts/setup.js +0 -1
- package/src/agents/executor.js +14 -6
- package/src/api/middleware/session.js +15 -2
- package/src/api/openai-router.js +130 -37
- package/src/api/providers-handler.js +15 -1
- package/src/api/router.js +107 -2
- package/src/budget/index.js +4 -3
- package/src/clients/databricks.js +431 -234
- package/src/clients/gpt-utils.js +181 -0
- package/src/clients/ollama-utils.js +66 -140
- package/src/clients/routing.js +0 -1
- package/src/clients/standard-tools.js +76 -3
- package/src/config/index.js +113 -35
- package/src/context/toon.js +173 -0
- package/src/logger/index.js +23 -0
- package/src/orchestrator/index.js +686 -211
- package/src/routing/agentic-detector.js +320 -0
- package/src/routing/complexity-analyzer.js +202 -2
- package/src/routing/cost-optimizer.js +305 -0
- package/src/routing/index.js +168 -159
- package/src/routing/model-tiers.js +365 -0
- package/src/server.js +2 -2
- package/src/sessions/cleanup.js +3 -3
- package/src/sessions/record.js +10 -1
- package/src/sessions/store.js +7 -2
- package/src/tools/agent-task.js +48 -1
- package/src/tools/index.js +15 -2
- package/te +11622 -0
- package/test/README.md +1 -1
- package/test/azure-openai-config.test.js +17 -8
- package/test/azure-openai-integration.test.js +7 -1
- package/test/azure-openai-routing.test.js +41 -43
- package/test/bedrock-integration.test.js +18 -32
- package/test/hybrid-routing-integration.test.js +35 -20
- package/test/hybrid-routing-performance.test.js +74 -64
- package/test/llamacpp-integration.test.js +28 -9
- package/test/lmstudio-integration.test.js +20 -8
- package/test/openai-integration.test.js +17 -20
- package/test/performance-tests.js +1 -1
- package/test/routing.test.js +65 -59
- package/test/toon-compression.test.js +131 -0
- package/CLAWROUTER_ROUTING_PLAN.md +0 -910
- package/ROUTER_COMPARISON.md +0 -173
- package/TIER_ROUTING_PLAN.md +0 -771
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic Workflow Detector
|
|
3
|
+
* Detects multi-step tool chains and autonomous agent patterns
|
|
4
|
+
* Used to boost complexity tier for agentic workloads
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const logger = require('../logger');
|
|
8
|
+
|
|
9
|
+
// Agent type classification with tier requirements
|
|
10
|
+
const AGENT_TYPES = {
|
|
11
|
+
SINGLE_SHOT: {
|
|
12
|
+
minTier: 'SIMPLE',
|
|
13
|
+
scoreBoost: 0,
|
|
14
|
+
description: 'Simple request-response, no tool chains',
|
|
15
|
+
},
|
|
16
|
+
TOOL_CHAIN: {
|
|
17
|
+
minTier: 'MEDIUM',
|
|
18
|
+
scoreBoost: 15,
|
|
19
|
+
requiresToolUse: true,
|
|
20
|
+
description: 'Sequential tool usage (read -> edit -> test)',
|
|
21
|
+
},
|
|
22
|
+
ITERATIVE: {
|
|
23
|
+
minTier: 'COMPLEX',
|
|
24
|
+
scoreBoost: 25,
|
|
25
|
+
requiresToolUse: true,
|
|
26
|
+
description: 'Retry loops, debugging cycles, iterative refinement',
|
|
27
|
+
},
|
|
28
|
+
AUTONOMOUS: {
|
|
29
|
+
minTier: 'REASONING',
|
|
30
|
+
scoreBoost: 35,
|
|
31
|
+
requiresToolUse: true,
|
|
32
|
+
description: 'Open-ended tasks, full autonomy, complex decision making',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Detection patterns
|
|
37
|
+
const PATTERNS = {
|
|
38
|
+
// Tool chain indicators
|
|
39
|
+
toolChain: /\b(then\s+use|after\s+that|next\s+step|finally|first.*then|step\s*\d+)\b/i,
|
|
40
|
+
|
|
41
|
+
// Iterative work indicators
|
|
42
|
+
iterative: /\b(keep\s+trying|until|repeat|loop|retry|iterate|fix.*again|try.*different|debug)\b/i,
|
|
43
|
+
|
|
44
|
+
// Autonomous work indicators
|
|
45
|
+
autonomous: /\b(figure\s+out|solve|complete\s+the\s+task|do\s+whatever|make\s+it\s+work|find\s+a\s+way|whatever\s+it\s+takes)\b/i,
|
|
46
|
+
|
|
47
|
+
// Multi-file work
|
|
48
|
+
multiFile: /\b(multiple\s+files?|across\s+(the\s+)?codebase|all\s+files?|refactor\s+entire|whole\s+project|everywhere)\b/i,
|
|
49
|
+
|
|
50
|
+
// Planning indicators
|
|
51
|
+
planning: /\b(plan|design|architect|strategy|roadmap|approach|how\s+would\s+you)\b/i,
|
|
52
|
+
|
|
53
|
+
// Implementation indicators
|
|
54
|
+
implementation: /\b(implement|build|create|develop|write|code|add\s+feature)\b/i,
|
|
55
|
+
|
|
56
|
+
// Analysis indicators
|
|
57
|
+
analysis: /\b(analyze|investigate|understand|explain|why\s+is|what\s+causes|root\s+cause)\b/i,
|
|
58
|
+
|
|
59
|
+
// Testing indicators
|
|
60
|
+
testing: /\b(test|verify|validate|check|ensure|confirm|make\s+sure)\b/i,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// High-complexity tools that indicate agentic work
|
|
64
|
+
const AGENTIC_TOOLS = new Set([
|
|
65
|
+
// Execution tools
|
|
66
|
+
'Bash', 'bash', 'shell', 'execute', 'run_command',
|
|
67
|
+
// Write tools
|
|
68
|
+
'Write', 'write_file', 'fs_write', 'create_file',
|
|
69
|
+
// Edit tools
|
|
70
|
+
'Edit', 'edit_file', 'fs_edit', 'edit_patch', 'str_replace_editor',
|
|
71
|
+
// Agent tools
|
|
72
|
+
'Task', 'agent_task', 'spawn_agent', 'delegate',
|
|
73
|
+
// Git tools
|
|
74
|
+
'Git', 'git_commit', 'git_push', 'git_create_branch',
|
|
75
|
+
// Test tools
|
|
76
|
+
'Test', 'run_tests', 'pytest', 'jest',
|
|
77
|
+
// Notebook tools
|
|
78
|
+
'NotebookEdit', 'notebook_edit',
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// Read-only tools (lower complexity)
|
|
82
|
+
const READ_ONLY_TOOLS = new Set([
|
|
83
|
+
'Read', 'read_file', 'fs_read',
|
|
84
|
+
'Glob', 'glob', 'find_files',
|
|
85
|
+
'Grep', 'grep', 'search', 'ripgrep',
|
|
86
|
+
'WebFetch', 'web_fetch', 'fetch_url',
|
|
87
|
+
'WebSearch', 'web_search',
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
class AgenticDetector {
|
|
91
|
+
/**
|
|
92
|
+
* Detect agentic workflow patterns
|
|
93
|
+
* @param {Object} payload - Request payload with messages and tools
|
|
94
|
+
* @returns {Object} Detection result
|
|
95
|
+
*/
|
|
96
|
+
detect(payload) {
|
|
97
|
+
const messages = payload?.messages || [];
|
|
98
|
+
const tools = payload?.tools || [];
|
|
99
|
+
const content = this._extractContent(messages);
|
|
100
|
+
|
|
101
|
+
let score = 0;
|
|
102
|
+
const signals = [];
|
|
103
|
+
|
|
104
|
+
// Signal 1: Tool count (many tools = likely multi-step)
|
|
105
|
+
const toolCount = tools.length;
|
|
106
|
+
if (toolCount > 10) {
|
|
107
|
+
score += 25;
|
|
108
|
+
signals.push({ signal: 'very_high_tool_count', value: toolCount, weight: 25 });
|
|
109
|
+
} else if (toolCount > 5) {
|
|
110
|
+
score += 15;
|
|
111
|
+
signals.push({ signal: 'high_tool_count', value: toolCount, weight: 15 });
|
|
112
|
+
} else if (toolCount > 3) {
|
|
113
|
+
score += 8;
|
|
114
|
+
signals.push({ signal: 'moderate_tool_count', value: toolCount, weight: 8 });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Signal 2: Agentic tools present (Bash, Write, Edit, Task)
|
|
118
|
+
const agenticToolCount = tools.filter(t => {
|
|
119
|
+
const name = t.name || t.function?.name || '';
|
|
120
|
+
return AGENTIC_TOOLS.has(name);
|
|
121
|
+
}).length;
|
|
122
|
+
|
|
123
|
+
if (agenticToolCount > 3) {
|
|
124
|
+
score += 25;
|
|
125
|
+
signals.push({ signal: 'many_agentic_tools', value: agenticToolCount, weight: 25 });
|
|
126
|
+
} else if (agenticToolCount > 1) {
|
|
127
|
+
score += 15;
|
|
128
|
+
signals.push({ signal: 'has_agentic_tools', value: agenticToolCount, weight: 15 });
|
|
129
|
+
} else if (agenticToolCount === 1) {
|
|
130
|
+
score += 8;
|
|
131
|
+
signals.push({ signal: 'single_agentic_tool', value: agenticToolCount, weight: 8 });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Signal 3: Prior tool results (already in agentic loop)
|
|
135
|
+
const toolResultCount = this._countToolResults(messages);
|
|
136
|
+
if (toolResultCount > 5) {
|
|
137
|
+
score += 30;
|
|
138
|
+
signals.push({ signal: 'deep_tool_loop', value: toolResultCount, weight: 30 });
|
|
139
|
+
} else if (toolResultCount > 2) {
|
|
140
|
+
score += 20;
|
|
141
|
+
signals.push({ signal: 'active_tool_loop', value: toolResultCount, weight: 20 });
|
|
142
|
+
} else if (toolResultCount > 0) {
|
|
143
|
+
score += 10;
|
|
144
|
+
signals.push({ signal: 'has_tool_results', value: toolResultCount, weight: 10 });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Signal 4: Pattern matching on content
|
|
148
|
+
if (PATTERNS.autonomous.test(content)) {
|
|
149
|
+
score += 25;
|
|
150
|
+
signals.push({ signal: 'autonomous_pattern', weight: 25 });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (PATTERNS.iterative.test(content)) {
|
|
154
|
+
score += 20;
|
|
155
|
+
signals.push({ signal: 'iterative_pattern', weight: 20 });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (PATTERNS.toolChain.test(content)) {
|
|
159
|
+
score += 15;
|
|
160
|
+
signals.push({ signal: 'tool_chain_pattern', weight: 15 });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (PATTERNS.multiFile.test(content)) {
|
|
164
|
+
score += 15;
|
|
165
|
+
signals.push({ signal: 'multi_file_work', weight: 15 });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (PATTERNS.planning.test(content)) {
|
|
169
|
+
score += 10;
|
|
170
|
+
signals.push({ signal: 'planning_required', weight: 10 });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (PATTERNS.implementation.test(content) && PATTERNS.testing.test(content)) {
|
|
174
|
+
score += 15;
|
|
175
|
+
signals.push({ signal: 'implementation_with_testing', weight: 15 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Signal 5: Conversation depth
|
|
179
|
+
const messageCount = messages.length;
|
|
180
|
+
if (messageCount > 15) {
|
|
181
|
+
score += 20;
|
|
182
|
+
signals.push({ signal: 'very_deep_conversation', value: messageCount, weight: 20 });
|
|
183
|
+
} else if (messageCount > 8) {
|
|
184
|
+
score += 12;
|
|
185
|
+
signals.push({ signal: 'deep_conversation', value: messageCount, weight: 12 });
|
|
186
|
+
} else if (messageCount > 4) {
|
|
187
|
+
score += 6;
|
|
188
|
+
signals.push({ signal: 'ongoing_conversation', value: messageCount, weight: 6 });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Signal 6: Content length (longer prompts often = more complex tasks)
|
|
192
|
+
if (content.length > 2000) {
|
|
193
|
+
score += 10;
|
|
194
|
+
signals.push({ signal: 'long_prompt', value: content.length, weight: 10 });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Determine agent type
|
|
198
|
+
const agentType = this._classifyAgentType(score, signals);
|
|
199
|
+
const isAgentic = score >= 25;
|
|
200
|
+
|
|
201
|
+
const result = {
|
|
202
|
+
isAgentic,
|
|
203
|
+
agentType,
|
|
204
|
+
confidence: Math.min(score / 100, 1),
|
|
205
|
+
score,
|
|
206
|
+
signals,
|
|
207
|
+
minTier: AGENT_TYPES[agentType].minTier,
|
|
208
|
+
scoreBoost: AGENT_TYPES[agentType].scoreBoost,
|
|
209
|
+
description: AGENT_TYPES[agentType].description,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (isAgentic) {
|
|
213
|
+
logger.debug({
|
|
214
|
+
agentType,
|
|
215
|
+
score,
|
|
216
|
+
signalCount: signals.length,
|
|
217
|
+
toolCount,
|
|
218
|
+
toolResultCount,
|
|
219
|
+
}, '[AgenticDetector] Agentic workflow detected');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Classify agent type based on score and signals
|
|
227
|
+
*/
|
|
228
|
+
_classifyAgentType(score, signals) {
|
|
229
|
+
// Check for specific signal combinations
|
|
230
|
+
const hasAutonomousPattern = signals.some(s => s.signal === 'autonomous_pattern');
|
|
231
|
+
const hasDeepToolLoop = signals.some(s => s.signal === 'deep_tool_loop');
|
|
232
|
+
const hasManyAgenticTools = signals.some(s => s.signal === 'many_agentic_tools');
|
|
233
|
+
|
|
234
|
+
// Autonomous: high score + autonomous pattern or very deep tool usage
|
|
235
|
+
if (score >= 60 || (hasAutonomousPattern && score >= 40)) {
|
|
236
|
+
return 'AUTONOMOUS';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Iterative: moderate-high score with tool loops
|
|
240
|
+
if (score >= 40 || (hasDeepToolLoop && score >= 30)) {
|
|
241
|
+
return 'ITERATIVE';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Tool chain: some tool usage indicated
|
|
245
|
+
if (score >= 20 || hasManyAgenticTools) {
|
|
246
|
+
return 'TOOL_CHAIN';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return 'SINGLE_SHOT';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Extract user content from messages
|
|
254
|
+
*/
|
|
255
|
+
_extractContent(messages) {
|
|
256
|
+
const userMsgs = messages.filter(m => m?.role === 'user');
|
|
257
|
+
if (userMsgs.length === 0) return '';
|
|
258
|
+
|
|
259
|
+
// Get last user message
|
|
260
|
+
const last = userMsgs[userMsgs.length - 1];
|
|
261
|
+
|
|
262
|
+
if (typeof last.content === 'string') {
|
|
263
|
+
return last.content;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (Array.isArray(last.content)) {
|
|
267
|
+
return last.content
|
|
268
|
+
.filter(block => block?.type === 'text')
|
|
269
|
+
.map(block => block.text || '')
|
|
270
|
+
.join(' ');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Count tool results in conversation
|
|
278
|
+
*/
|
|
279
|
+
_countToolResults(messages) {
|
|
280
|
+
let count = 0;
|
|
281
|
+
|
|
282
|
+
for (const msg of messages) {
|
|
283
|
+
if (msg?.role === 'user' && Array.isArray(msg.content)) {
|
|
284
|
+
count += msg.content.filter(c => c?.type === 'tool_result').length;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return count;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get detection stats for debugging
|
|
293
|
+
*/
|
|
294
|
+
getPatternStats(content) {
|
|
295
|
+
const stats = {};
|
|
296
|
+
for (const [name, pattern] of Object.entries(PATTERNS)) {
|
|
297
|
+
stats[name] = pattern.test(content);
|
|
298
|
+
}
|
|
299
|
+
return stats;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Singleton instance
|
|
304
|
+
let instance = null;
|
|
305
|
+
|
|
306
|
+
function getAgenticDetector() {
|
|
307
|
+
if (!instance) {
|
|
308
|
+
instance = new AgenticDetector();
|
|
309
|
+
}
|
|
310
|
+
return instance;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
AgenticDetector,
|
|
315
|
+
getAgenticDetector,
|
|
316
|
+
AGENT_TYPES,
|
|
317
|
+
PATTERNS,
|
|
318
|
+
AGENTIC_TOOLS,
|
|
319
|
+
READ_ONLY_TOOLS,
|
|
320
|
+
};
|
|
@@ -88,6 +88,58 @@ const FORCE_LOCAL_PATTERNS = [
|
|
|
88
88
|
/^(help|menu|commands?|options?)[\s\.\!\?]*$/i,
|
|
89
89
|
];
|
|
90
90
|
|
|
91
|
+
// Weighted Scoring (15 Dimensions)
|
|
92
|
+
const DIMENSION_WEIGHTS = {
|
|
93
|
+
// Content Analysis (35%)
|
|
94
|
+
tokenCount: 0.08,
|
|
95
|
+
promptComplexity: 0.10,
|
|
96
|
+
technicalDepth: 0.10,
|
|
97
|
+
domainSpecificity: 0.07,
|
|
98
|
+
// Tool Analysis (25%)
|
|
99
|
+
toolCount: 0.08,
|
|
100
|
+
toolComplexity: 0.10,
|
|
101
|
+
toolChainPotential: 0.07,
|
|
102
|
+
// Reasoning Requirements (25%)
|
|
103
|
+
multiStepReasoning: 0.10,
|
|
104
|
+
codeGeneration: 0.08,
|
|
105
|
+
analysisDepth: 0.07,
|
|
106
|
+
// Context Factors (15%)
|
|
107
|
+
conversationDepth: 0.05,
|
|
108
|
+
priorToolUsage: 0.05,
|
|
109
|
+
ambiguity: 0.05,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Tool complexity weights (higher = more complex)
|
|
113
|
+
const TOOL_COMPLEXITY_WEIGHTS = {
|
|
114
|
+
Bash: 0.9,
|
|
115
|
+
bash: 0.9,
|
|
116
|
+
shell: 0.9,
|
|
117
|
+
Write: 0.8,
|
|
118
|
+
write_file: 0.8,
|
|
119
|
+
Edit: 0.7,
|
|
120
|
+
edit_file: 0.7,
|
|
121
|
+
NotebookEdit: 0.7,
|
|
122
|
+
Task: 0.9,
|
|
123
|
+
agent_task: 0.9,
|
|
124
|
+
WebSearch: 0.5,
|
|
125
|
+
WebFetch: 0.4,
|
|
126
|
+
Read: 0.3,
|
|
127
|
+
read_file: 0.3,
|
|
128
|
+
Glob: 0.2,
|
|
129
|
+
Grep: 0.2,
|
|
130
|
+
default: 0.5,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Domain-specific keywords for complexity
|
|
134
|
+
const DOMAIN_KEYWORDS = {
|
|
135
|
+
security: /\b(auth|encrypt|vulnerability|injection|xss|csrf|jwt|oauth|password|credential|secret)\b/i,
|
|
136
|
+
ml: /\b(model|train|inference|tensor|embedding|neural|llm|gpt|transformer|pytorch|tensorflow)\b/i,
|
|
137
|
+
distributed: /\b(microservice|kafka|redis|queue|scale|cluster|replicate|kubernetes|docker|container)\b/i,
|
|
138
|
+
database: /\b(sql|nosql|migration|index|query|transaction|orm|postgres|mongodb|mysql)\b/i,
|
|
139
|
+
frontend: /\b(react|vue|angular|svelte|css|html|component|state|redux|hooks)\b/i,
|
|
140
|
+
devops: /\b(ci\/cd|pipeline|deploy|terraform|ansible|github\s*actions|jenkins)\b/i,
|
|
141
|
+
};
|
|
142
|
+
|
|
91
143
|
// ============================================================================
|
|
92
144
|
// PHASE 3: Metrics Tracking
|
|
93
145
|
// ============================================================================
|
|
@@ -360,6 +412,116 @@ function scoreReasoning(content) {
|
|
|
360
412
|
return { score: Math.min(score, 15), reasons };
|
|
361
413
|
}
|
|
362
414
|
|
|
415
|
+
// ============================================================================
|
|
416
|
+
// WEIGHTED SCORING FUNCTION (15 Dimensions)
|
|
417
|
+
// ============================================================================
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Calculate weighted complexity score (0-100)
|
|
421
|
+
* Uses 15 dimensions with configurable weights
|
|
422
|
+
* @param {Object} payload - Request payload
|
|
423
|
+
* @param {string} content - Extracted content
|
|
424
|
+
* @returns {Object} Weighted score result
|
|
425
|
+
*/
|
|
426
|
+
function calculateWeightedScore(payload, content) {
|
|
427
|
+
const dimensions = {};
|
|
428
|
+
|
|
429
|
+
// 1. Token count (0-100)
|
|
430
|
+
const tokens = estimateTokens(payload);
|
|
431
|
+
dimensions.tokenCount = tokens < 500 ? 10 : tokens < 2000 ? 30 : tokens < 5000 ? 50 : tokens < 10000 ? 70 : 90;
|
|
432
|
+
|
|
433
|
+
// 2. Prompt complexity (sentence structure, avg length)
|
|
434
|
+
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
435
|
+
const avgLength = content.length / Math.max(sentences.length, 1);
|
|
436
|
+
dimensions.promptComplexity = Math.min(avgLength / 2, 100);
|
|
437
|
+
|
|
438
|
+
// 3. Technical depth (keyword density)
|
|
439
|
+
const techMatches = (content.match(PATTERNS.technical) || []).length;
|
|
440
|
+
dimensions.technicalDepth = Math.min(techMatches * 15, 100);
|
|
441
|
+
|
|
442
|
+
// 4. Domain specificity (how many domains are touched)
|
|
443
|
+
let domainScore = 0;
|
|
444
|
+
const domainsMatched = [];
|
|
445
|
+
for (const [domain, regex] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
446
|
+
if (regex.test(content)) {
|
|
447
|
+
domainScore += 20;
|
|
448
|
+
domainsMatched.push(domain);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
dimensions.domainSpecificity = Math.min(domainScore, 100);
|
|
452
|
+
|
|
453
|
+
// 5. Tool count
|
|
454
|
+
const toolCount = payload?.tools?.length ?? 0;
|
|
455
|
+
dimensions.toolCount = toolCount === 0 ? 0 :
|
|
456
|
+
toolCount <= 3 ? 20 :
|
|
457
|
+
toolCount <= 6 ? 40 :
|
|
458
|
+
toolCount <= 10 ? 60 :
|
|
459
|
+
toolCount <= 15 ? 80 : 100;
|
|
460
|
+
|
|
461
|
+
// 6. Tool complexity (weighted by tool types)
|
|
462
|
+
if (payload?.tools?.length > 0) {
|
|
463
|
+
const totalWeight = payload.tools.reduce((sum, t) => {
|
|
464
|
+
const name = t.name || t.function?.name || '';
|
|
465
|
+
return sum + (TOOL_COMPLEXITY_WEIGHTS[name] || TOOL_COMPLEXITY_WEIGHTS.default);
|
|
466
|
+
}, 0);
|
|
467
|
+
const avgWeight = totalWeight / payload.tools.length;
|
|
468
|
+
dimensions.toolComplexity = avgWeight * 100;
|
|
469
|
+
} else {
|
|
470
|
+
dimensions.toolComplexity = 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 7. Tool chain potential (sequential operations)
|
|
474
|
+
dimensions.toolChainPotential = /\b(then|after|next|finally|first.*then|step\s*\d+)\b/i.test(content) ? 70 : 20;
|
|
475
|
+
|
|
476
|
+
// 8. Multi-step reasoning
|
|
477
|
+
dimensions.multiStepReasoning = ADVANCED_PATTERNS.reasoning.stepByStep.test(content) ? 80 :
|
|
478
|
+
ADVANCED_PATTERNS.reasoning.planning.test(content) ? 60 : 20;
|
|
479
|
+
|
|
480
|
+
// 9. Code generation requirement
|
|
481
|
+
dimensions.codeGeneration = /\b(write|create|implement|build|generate)\s+(a\s+)?(new\s+)?(function|class|module|api|endpoint|service|component)/i.test(content) ? 80 : 20;
|
|
482
|
+
|
|
483
|
+
// 10. Analysis depth
|
|
484
|
+
dimensions.analysisDepth = ADVANCED_PATTERNS.reasoning.tradeoffs.test(content) ? 80 :
|
|
485
|
+
ADVANCED_PATTERNS.reasoning.analysis.test(content) ? 60 : 20;
|
|
486
|
+
|
|
487
|
+
// 11. Conversation depth
|
|
488
|
+
const messageCount = payload?.messages?.length ?? 0;
|
|
489
|
+
dimensions.conversationDepth = messageCount < 3 ? 10 :
|
|
490
|
+
messageCount < 6 ? 30 :
|
|
491
|
+
messageCount < 10 ? 50 : 70;
|
|
492
|
+
|
|
493
|
+
// 12. Prior tool usage (tool results in conversation)
|
|
494
|
+
const toolResults = (payload?.messages || []).filter(m =>
|
|
495
|
+
m.role === 'user' && Array.isArray(m.content) && m.content.some(c => c.type === 'tool_result')
|
|
496
|
+
).length;
|
|
497
|
+
dimensions.priorToolUsage = toolResults === 0 ? 10 :
|
|
498
|
+
toolResults < 3 ? 40 :
|
|
499
|
+
toolResults < 6 ? 60 : 80;
|
|
500
|
+
|
|
501
|
+
// 13. Ambiguity (inverse of specificity)
|
|
502
|
+
const hasSpecifics = /\b(file|function|line\s*\d+|error|bug|at\s+[\w.]+:\d+|\/[\w/]+\.\w+)\b/i.test(content);
|
|
503
|
+
dimensions.ambiguity = hasSpecifics ? 20 : content.length < 50 ? 70 : 40;
|
|
504
|
+
|
|
505
|
+
// Calculate weighted total
|
|
506
|
+
let weightedTotal = 0;
|
|
507
|
+
for (const [dimension, weight] of Object.entries(DIMENSION_WEIGHTS)) {
|
|
508
|
+
weightedTotal += (dimensions[dimension] || 0) * weight;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
score: Math.round(weightedTotal),
|
|
513
|
+
dimensions,
|
|
514
|
+
weights: DIMENSION_WEIGHTS,
|
|
515
|
+
meta: {
|
|
516
|
+
tokens,
|
|
517
|
+
toolCount,
|
|
518
|
+
messageCount,
|
|
519
|
+
toolResults,
|
|
520
|
+
domainsMatched,
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
363
525
|
/**
|
|
364
526
|
* Get threshold based on SMART_TOOL_SELECTION_MODE
|
|
365
527
|
*/
|
|
@@ -381,13 +543,45 @@ function getThreshold() {
|
|
|
381
543
|
* Analyze request complexity and return full analysis
|
|
382
544
|
*
|
|
383
545
|
* @param {Object} payload - Request payload
|
|
546
|
+
* @param {Object} options - Analysis options
|
|
384
547
|
* @returns {Object} Complexity analysis result
|
|
385
548
|
*/
|
|
386
|
-
function analyzeComplexity(payload) {
|
|
549
|
+
function analyzeComplexity(payload, options = {}) {
|
|
387
550
|
const content = extractContent(payload);
|
|
388
551
|
const messageCount = payload?.messages?.length ?? 0;
|
|
552
|
+
const useWeighted = options.weighted ?? config.routing?.weightedScoring ?? false;
|
|
553
|
+
|
|
554
|
+
// Use weighted scoring if enabled
|
|
555
|
+
if (useWeighted) {
|
|
556
|
+
const weighted = calculateWeightedScore(payload, content);
|
|
557
|
+
const threshold = getThreshold();
|
|
558
|
+
const mode = config.smartToolSelection?.mode ?? 'heuristic';
|
|
559
|
+
|
|
560
|
+
// Check force patterns
|
|
561
|
+
const taskTypeResult = scoreTaskType(content);
|
|
562
|
+
let recommendation;
|
|
563
|
+
if (taskTypeResult.reason === 'force_local') {
|
|
564
|
+
recommendation = 'local';
|
|
565
|
+
} else if (taskTypeResult.reason === 'force_cloud') {
|
|
566
|
+
recommendation = 'cloud';
|
|
567
|
+
} else {
|
|
568
|
+
recommendation = weighted.score >= threshold ? 'cloud' : 'local';
|
|
569
|
+
}
|
|
389
570
|
|
|
390
|
-
|
|
571
|
+
return {
|
|
572
|
+
score: weighted.score,
|
|
573
|
+
threshold,
|
|
574
|
+
mode: 'weighted',
|
|
575
|
+
recommendation,
|
|
576
|
+
breakdown: weighted.dimensions,
|
|
577
|
+
weights: weighted.weights,
|
|
578
|
+
meta: weighted.meta,
|
|
579
|
+
forceReason: taskTypeResult.reason?.startsWith('force_') ? taskTypeResult.reason : null,
|
|
580
|
+
content: content.slice(0, 100) + (content.length > 100 ? '...' : ''),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Standard scoring (original logic)
|
|
391
585
|
const tokenScore = scoreTokens(payload);
|
|
392
586
|
const toolScore = scoreTools(payload);
|
|
393
587
|
const taskTypeResult = scoreTaskType(content);
|
|
@@ -577,6 +771,9 @@ module.exports = {
|
|
|
577
771
|
scoreCodeComplexity,
|
|
578
772
|
scoreReasoning,
|
|
579
773
|
|
|
774
|
+
// Weighted scoring
|
|
775
|
+
calculateWeightedScore,
|
|
776
|
+
|
|
580
777
|
// Configuration
|
|
581
778
|
getThreshold,
|
|
582
779
|
|
|
@@ -592,4 +789,7 @@ module.exports = {
|
|
|
592
789
|
ADVANCED_PATTERNS,
|
|
593
790
|
FORCE_CLOUD_PATTERNS,
|
|
594
791
|
FORCE_LOCAL_PATTERNS,
|
|
792
|
+
DIMENSION_WEIGHTS,
|
|
793
|
+
TOOL_COMPLEXITY_WEIGHTS,
|
|
794
|
+
DOMAIN_KEYWORDS,
|
|
595
795
|
};
|