@dmsdc-ai/aigentry-deliberation 0.0.19 → 0.0.20

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 (2) hide show
  1. package/model-router.js +224 -0
  2. package/package.json +2 -1
@@ -0,0 +1,224 @@
1
+ // model-router.js
2
+ // Dynamic model selection based on deliberation prompt context
3
+
4
+ const CATEGORY_KEYWORDS = {
5
+ reasoning: [
6
+ 'analyze', 'analysis', 'debug', 'debugging', 'complex', 'logic', 'reasoning',
7
+ 'problem', 'solve', 'evaluate', 'assess', 'critique', 'argument', 'inference',
8
+ 'contradiction', 'flaw', 'why', 'explain', 'cause', 'effect', 'implication',
9
+ 'consequence', 'tradeoff', 'compare', 'pros', 'cons', 'decision', 'strategy',
10
+ 'architecture', 'design pattern', 'refactor', 'optimize', 'performance',
11
+ ],
12
+ coding: [
13
+ 'code', 'implement', 'function', 'class', 'module', 'api', 'library',
14
+ 'algorithm', 'data structure', 'typescript', 'javascript', 'python', 'rust',
15
+ 'build', 'deploy', 'test', 'unit test', 'integration', 'bug', 'fix', 'error',
16
+ 'syntax', 'runtime', 'compile', 'script', 'program', 'software', 'feature',
17
+ 'endpoint', 'schema', 'database', 'query', 'sql', 'migration', 'lint',
18
+ ],
19
+ creative: [
20
+ 'write', 'writing', 'design', 'brainstorm', 'idea', 'creative', 'story',
21
+ 'narrative', 'style', 'tone', 'voice', 'draft', 'outline', 'concept',
22
+ 'vision', 'imagine', 'suggest', 'propose', 'generate', 'name', 'brand',
23
+ 'ui', 'ux', 'interface', 'experience', 'aesthetic', 'layout', 'visual',
24
+ ],
25
+ research: [
26
+ 'research', 'find', 'search', 'fact', 'data', 'statistic', 'source',
27
+ 'reference', 'study', 'survey', 'report', 'paper', 'documentation', 'docs',
28
+ 'compare', 'benchmark', 'review', 'overview', 'summary', 'what is', 'who is',
29
+ 'history', 'background', 'context', 'information', 'knowledge',
30
+ ],
31
+ simple: [
32
+ 'yes', 'no', 'confirm', 'ok', 'okay', 'agree', 'disagree', 'correct',
33
+ 'hello', 'hi', 'thanks', 'thank you', 'sure', 'got it', 'understood',
34
+ 'clarify', 'quick', 'brief', 'simple', 'basic',
35
+ ],
36
+ };
37
+
38
+ const COMPLEXITY_KEYWORDS = {
39
+ high: [
40
+ 'complex', 'complicated', 'difficult', 'challenging', 'sophisticated',
41
+ 'advanced', 'deep', 'thorough', 'comprehensive', 'exhaustive', 'detailed',
42
+ 'multi-step', 'multi-faceted', 'nuanced', 'ambiguous', 'architecture',
43
+ 'system', 'scalable', 'distributed', 'concurrent', 'critical', 'production',
44
+ ],
45
+ low: [
46
+ 'simple', 'basic', 'quick', 'brief', 'short', 'easy', 'trivial',
47
+ 'straightforward', 'obvious', 'clear', 'confirm', 'yes', 'no', 'agree',
48
+ 'ok', 'sure', 'hello', 'hi',
49
+ ],
50
+ };
51
+
52
+ const ROLE_KEYWORDS = {
53
+ critic: 'reasoning',
54
+ implementer: 'coding',
55
+ mediator: 'creative',
56
+ researcher: 'research',
57
+ };
58
+
59
+ /**
60
+ * Count keyword matches in text for a given keyword list.
61
+ */
62
+ function countMatches(text, keywords) {
63
+ const lower = text.toLowerCase();
64
+ return keywords.reduce((count, kw) => {
65
+ return count + (lower.includes(kw.toLowerCase()) ? 1 : 0);
66
+ }, 0);
67
+ }
68
+
69
+ /**
70
+ * Analyze deliberation context to determine category and complexity.
71
+ * @param {string} topic - The deliberation topic
72
+ * @param {string} recentLog - Recent log text from deliberation
73
+ * @param {string} role - The speaker's role
74
+ * @returns {{ category: string, complexity: string }}
75
+ */
76
+ export function analyzePromptContext(topic, recentLog, role) {
77
+ const text = `${topic} ${recentLog}`;
78
+
79
+ // Role-based category hint
80
+ let roleCategory = null;
81
+ if (role) {
82
+ const lowerRole = role.toLowerCase();
83
+ for (const [keyword, category] of Object.entries(ROLE_KEYWORDS)) {
84
+ if (lowerRole.includes(keyword)) {
85
+ roleCategory = category;
86
+ break;
87
+ }
88
+ }
89
+ }
90
+
91
+ // Keyword-based category scoring
92
+ const scores = {};
93
+ for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
94
+ scores[category] = countMatches(text, keywords);
95
+ }
96
+
97
+ // Find top category by keyword score
98
+ let topCategory = 'simple';
99
+ let topScore = 0;
100
+ for (const [category, score] of Object.entries(scores)) {
101
+ if (score > topScore) {
102
+ topScore = score;
103
+ topCategory = category;
104
+ }
105
+ }
106
+
107
+ // Role hint takes precedence if no strong keyword signal
108
+ const category = topScore >= 2 ? topCategory : (roleCategory || topCategory);
109
+
110
+ // Complexity detection
111
+ const highCount = countMatches(text, COMPLEXITY_KEYWORDS.high);
112
+ const lowCount = countMatches(text, COMPLEXITY_KEYWORDS.low);
113
+
114
+ let complexity;
115
+ if (highCount > lowCount && highCount >= 1) {
116
+ complexity = 'high';
117
+ } else if (lowCount > 0 && highCount === 0) {
118
+ complexity = 'low';
119
+ } else {
120
+ complexity = 'medium';
121
+ }
122
+
123
+ return { category, complexity };
124
+ }
125
+
126
+ /**
127
+ * Select the optimal model for a given provider based on category and complexity.
128
+ * @param {string} provider - The AI provider identifier
129
+ * @param {string} category - Prompt category
130
+ * @param {string} complexity - Prompt complexity
131
+ * @returns {{ model: string, reason: string }}
132
+ */
133
+ export function selectModelForProvider(provider, category, complexity) {
134
+ const isHighReasoning = category === 'reasoning' || complexity === 'high';
135
+ const isSimple = complexity === 'low' || category === 'simple';
136
+ const isCoding = category === 'coding';
137
+
138
+ switch (provider) {
139
+ case 'chatgpt': {
140
+ if (isHighReasoning) return { model: 'o3', reason: 'High-complexity reasoning task' };
141
+ if (isCoding) return { model: 'o4-mini', reason: 'Coding/implementation task' };
142
+ if (isSimple) return { model: 'gpt-4o-mini', reason: 'Simple task, cost-efficient model' };
143
+ return { model: 'gpt-4o', reason: 'Creative or medium-complexity task' };
144
+ }
145
+
146
+ case 'claude': {
147
+ if (isHighReasoning) return { model: 'opus', reason: 'High-complexity reasoning task' };
148
+ if (isSimple) return { model: 'haiku', reason: 'Simple task, fast and cost-efficient' };
149
+ return { model: 'sonnet', reason: 'Coding or medium-complexity task' };
150
+ }
151
+
152
+ case 'gemini': {
153
+ if (isHighReasoning || complexity === 'high') return { model: '2.5 Pro', reason: 'High-complexity or reasoning task' };
154
+ if (isSimple) return { model: '2.0 Flash', reason: 'Simple task, fast response' };
155
+ return { model: '2.5 Flash', reason: 'Medium-complexity task' };
156
+ }
157
+
158
+ case 'deepseek': {
159
+ if (category === 'reasoning') return { model: 'DeepSeek-R1', reason: 'Reasoning-focused task' };
160
+ return { model: 'DeepSeek-V3', reason: 'General task' };
161
+ }
162
+
163
+ case 'grok': {
164
+ if (complexity === 'high' || isHighReasoning) return { model: 'grok-3', reason: 'High-complexity task' };
165
+ return { model: 'grok-3-mini', reason: 'Simple task' };
166
+ }
167
+
168
+ case 'mistral': {
169
+ if (complexity === 'high' || isHighReasoning) return { model: 'Mistral Large', reason: 'High-complexity task' };
170
+ return { model: 'Mistral Small', reason: 'Simple task' };
171
+ }
172
+
173
+ case 'poe': {
174
+ if (complexity === 'high' || isHighReasoning) return { model: 'Claude-3.5-Sonnet', reason: 'High-complexity task' };
175
+ if (isSimple) return { model: 'Claude-3-Haiku', reason: 'Simple task' };
176
+ return { model: 'GPT-4o', reason: 'Medium-complexity task' };
177
+ }
178
+
179
+ case 'qwen': {
180
+ if (complexity === 'high' || isHighReasoning) return { model: 'Qwen-Max', reason: 'High-complexity task' };
181
+ return { model: 'Qwen-Plus', reason: 'Simple task' };
182
+ }
183
+
184
+ case 'huggingchat': {
185
+ if (complexity === 'high' || isHighReasoning) return { model: 'Qwen/QwQ-32B', reason: 'High-complexity task' };
186
+ return { model: 'meta-llama/Llama-3.3-70B', reason: 'Simple task' };
187
+ }
188
+
189
+ case 'copilot': {
190
+ return { model: 'GPT-4o', reason: 'Copilot uses GPT-4o (no model selection)' };
191
+ }
192
+
193
+ case 'perplexity': {
194
+ return { model: 'default', reason: 'Perplexity uses default model (no model selection)' };
195
+ }
196
+
197
+ default: {
198
+ return { model: 'default', reason: `Unknown provider: ${provider}` };
199
+ }
200
+ }
201
+ }
202
+
203
+ /**
204
+ * High-level function: analyze state and return optimal model selection for a turn.
205
+ * @param {object} state - Deliberation state with topic, log, speaker_roles
206
+ * @param {string} speaker - The current speaker identifier
207
+ * @param {string} provider - The AI provider to use
208
+ * @returns {{ model: string, category: string, complexity: string, reason: string }}
209
+ */
210
+ export function getModelSelectionForTurn(state, speaker, provider) {
211
+ const topic = state.topic || '';
212
+ const role = (state.speaker_roles && state.speaker_roles[speaker]) || '';
213
+
214
+ // Use recent log entries (last 5) for context analysis
215
+ const recentEntries = Array.isArray(state.log) ? state.log.slice(-5) : [];
216
+ const recentLog = recentEntries
217
+ .map(entry => (typeof entry === 'string' ? entry : JSON.stringify(entry)))
218
+ .join(' ');
219
+
220
+ const { category, complexity } = analyzePromptContext(topic, recentLog, role);
221
+ const { model, reason } = selectModelForProvider(provider, category, complexity);
222
+
223
+ return { model, category, complexity, reason };
224
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-deliberation",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "MCP Deliberation Server — Multi-session AI deliberation with smart speaker ordering and persona roles",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "files": [
31
31
  "index.js",
32
+ "model-router.js",
32
33
  "install.js",
33
34
  "doctor.js",
34
35
  "observer.js",