223 lines
7.4 KiB
JavaScript
223 lines
7.4 KiB
JavaScript
|
|
/**
|
||
|
|
* AAMOS Swarm Orchestrator V2
|
||
|
|
* Parallell exekvering med dynamisk lastbalansering
|
||
|
|
*/
|
||
|
|
|
||
|
|
import {
|
||
|
|
MODELS,
|
||
|
|
kimiCreate,
|
||
|
|
kimiStream,
|
||
|
|
qwenCreate,
|
||
|
|
qwenStream,
|
||
|
|
claudeChat,
|
||
|
|
groqChat
|
||
|
|
} from './model-router.mjs';
|
||
|
|
|
||
|
|
// ── Provider-hälsa ────────────────────────────────────────────────────────
|
||
|
|
const _providerHealth = new Map();
|
||
|
|
|
||
|
|
function checkHealth(provider) {
|
||
|
|
const lastCheck = _providerHealth.get(provider);
|
||
|
|
if (!lastCheck) return true;
|
||
|
|
return Date.now() - lastCheck.lastFailure > 30000; // 30s cooldown
|
||
|
|
}
|
||
|
|
|
||
|
|
function markFailure(provider) {
|
||
|
|
_providerHealth.set(provider, { lastFailure: Date.now(), failures: (_providerHealth.get(provider)?.failures || 0) + 1 });
|
||
|
|
}
|
||
|
|
|
||
|
|
function markSuccess(provider) {
|
||
|
|
_providerHealth.set(provider, { lastFailure: 0, failures: 0, lastSuccess: Date.now() });
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Viktad routing ────────────────────────────────────────────────────────
|
||
|
|
function selectProvider(task, message) {
|
||
|
|
const providers = [
|
||
|
|
{ name: 'kimi', models: [MODELS.KIMI_K2_6, MODELS.KIMI_K2_7_CODE, MODELS.KIMI_K2_7_HIGHSPEED], weight: 0.5 },
|
||
|
|
{ name: 'bedrock', models: [MODELS.QWEN_235B, MODELS.QWEN_32B, MODELS.QWEN_CODER], weight: 0.3 },
|
||
|
|
{ name: 'groq', models: ['llama-3.3-70b-versatile', 'openai/gpt-oss-120b'], weight: 0.2 },
|
||
|
|
];
|
||
|
|
|
||
|
|
const available = providers.filter(p => checkHealth(p.name));
|
||
|
|
const totalWeight = available.reduce((a, p) => a + p.weight, 0);
|
||
|
|
let random = Math.random() * totalWeight;
|
||
|
|
|
||
|
|
for (const provider of available) {
|
||
|
|
random -= provider.weight;
|
||
|
|
if (random <= 0) return provider;
|
||
|
|
}
|
||
|
|
|
||
|
|
return available[0] || providers[0]; // fallback
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Swarm exekvering ──────────────────────────────────────────────────────
|
||
|
|
export async function swarmExecute(tasks, options = {}) {
|
||
|
|
const {
|
||
|
|
maxConcurrency = 4,
|
||
|
|
sharedContext = {},
|
||
|
|
timeout = 60000,
|
||
|
|
onProgress = () => {}
|
||
|
|
} = options;
|
||
|
|
|
||
|
|
const startTime = Date.now();
|
||
|
|
const results = [];
|
||
|
|
const executing = new Set();
|
||
|
|
|
||
|
|
async function executeTask(task, index) {
|
||
|
|
const taskStart = Date.now();
|
||
|
|
const provider = selectProvider(task.type, task.message);
|
||
|
|
|
||
|
|
onProgress({ type: 'start', task: index, provider: provider.name, model: task.model });
|
||
|
|
|
||
|
|
try {
|
||
|
|
let result;
|
||
|
|
|
||
|
|
// Välj rätt execute-funktion baserat på provider
|
||
|
|
switch(provider.name) {
|
||
|
|
case 'kimi':
|
||
|
|
result = await kimiCreate(task.model, sharedContext.system, [
|
||
|
|
...sharedContext.history || [],
|
||
|
|
{ role: 'user', content: task.message }
|
||
|
|
], task.maxTokens || 4096);
|
||
|
|
break;
|
||
|
|
case 'bedrock':
|
||
|
|
result = await qwenCreate(task.model, sharedContext.system, [
|
||
|
|
...sharedContext.history || [],
|
||
|
|
{ role: 'user', content: task.message }
|
||
|
|
], task.maxTokens || 4096);
|
||
|
|
break;
|
||
|
|
case 'groq':
|
||
|
|
result = await groqChat(task.model, sharedContext.system, [
|
||
|
|
...sharedContext.history || [],
|
||
|
|
{ role: 'user', content: task.message }
|
||
|
|
]);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
throw new Error(`Okänd provider: ${provider.name}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
markSuccess(provider.name);
|
||
|
|
|
||
|
|
const duration = Date.now() - taskStart;
|
||
|
|
onProgress({ type: 'complete', task: index, duration, provider: provider.name });
|
||
|
|
|
||
|
|
return {
|
||
|
|
status: 'fulfilled',
|
||
|
|
value: {
|
||
|
|
task: task.type,
|
||
|
|
model: task.model,
|
||
|
|
provider: provider.name,
|
||
|
|
duration,
|
||
|
|
result: typeof result === 'string' ? result : result.text,
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
markFailure(provider.name);
|
||
|
|
|
||
|
|
const duration = Date.now() - taskStart;
|
||
|
|
onProgress({ type: 'error', task: index, duration, error: error.message });
|
||
|
|
|
||
|
|
return {
|
||
|
|
status: 'rejected',
|
||
|
|
reason: {
|
||
|
|
task: task.type,
|
||
|
|
model: task.model,
|
||
|
|
provider: provider.name,
|
||
|
|
duration,
|
||
|
|
error: error.message,
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Kör med begränsad concurrency
|
||
|
|
for (let i = 0; i < tasks.length; i++) {
|
||
|
|
while (executing.size >= maxConcurrency) {
|
||
|
|
await Promise.race(executing);
|
||
|
|
}
|
||
|
|
|
||
|
|
const promise = executeTask(tasks[i], i).then(result => {
|
||
|
|
executing.delete(promise);
|
||
|
|
results[i] = result;
|
||
|
|
return result;
|
||
|
|
});
|
||
|
|
|
||
|
|
executing.add(promise);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Vänta på alla
|
||
|
|
await Promise.all(executing);
|
||
|
|
|
||
|
|
const totalTime = Date.now() - startTime;
|
||
|
|
|
||
|
|
// Sammanställ
|
||
|
|
const successful = results.filter(r => r.status === 'fulfilled').map(r => r.value);
|
||
|
|
const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
|
||
|
|
|
||
|
|
return {
|
||
|
|
successful,
|
||
|
|
failed,
|
||
|
|
totalTime,
|
||
|
|
summary: {
|
||
|
|
total: tasks.length,
|
||
|
|
success: successful.length,
|
||
|
|
failed: failed.length,
|
||
|
|
avgDuration: successful.reduce((a, r) => a + r.duration, 0) / successful.length || 0,
|
||
|
|
providers: [...new Set(successful.map(r => r.provider))],
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Agent-koordinering ────────────────────────────────────────────────────
|
||
|
|
export async function coordinateAgents(agents, sharedGoal, options = {}) {
|
||
|
|
console.log(`🎯 Koordinerar ${agents.length} agenter för: ${sharedGoal}`);
|
||
|
|
|
||
|
|
// Fas 1: Planering (Bernt-nivå)
|
||
|
|
const plan = await swarmExecute([{
|
||
|
|
type: 'planning',
|
||
|
|
model: MODELS.KIMI_K2_6,
|
||
|
|
message: `Planera uppgifter för: ${sharedGoal}. Tillgängliga agenter: ${agents.map(a => a.name).join(', ')}`,
|
||
|
|
maxTokens: 2048,
|
||
|
|
}], { sharedContext: { system: 'Du är en expert på att bryta ner komplexa uppgifter i deluppgifter.' } });
|
||
|
|
|
||
|
|
// Fas 2: Exekvering (parallell)
|
||
|
|
const tasks = agents.map(agent => ({
|
||
|
|
type: agent.specialization[0],
|
||
|
|
model: agent.model,
|
||
|
|
message: `${agent.role}: ${sharedGoal}`,
|
||
|
|
maxTokens: 4096,
|
||
|
|
}));
|
||
|
|
|
||
|
|
const results = await swarmExecute(tasks, {
|
||
|
|
maxConcurrency: options.maxConcurrency || agents.length,
|
||
|
|
sharedContext: {
|
||
|
|
system: `Delad kontext för: ${sharedGoal}`,
|
||
|
|
history: [{ role: 'assistant', content: plan.successful[0]?.result || '' }]
|
||
|
|
},
|
||
|
|
onProgress: (event) => {
|
||
|
|
if (event.type === 'start') console.log(` 🚀 ${event.provider} startar task ${event.task}`);
|
||
|
|
if (event.type === 'complete') console.log(` ✅ Task ${event.task} klar (${event.duration}ms)`);
|
||
|
|
if (event.type === 'error') console.log(` ❌ Task ${event.task} misslyckades: ${event.error}`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Fas 3: Sammanställning
|
||
|
|
const synthesis = await swarmExecute([{
|
||
|
|
type: 'synthesis',
|
||
|
|
model: MODELS.KIMI_K2_6,
|
||
|
|
message: `Sammanställ resultaten för: ${sharedGoal}\n\n${results.successful.map(r => `## ${r.task}\n${r.result}`).join('\n\n')}`,
|
||
|
|
maxTokens: 4096,
|
||
|
|
}], { sharedContext: { system: 'Du är en expert på att syntetisera resultat från flera källor.' } });
|
||
|
|
|
||
|
|
return {
|
||
|
|
plan: plan.successful[0]?.result,
|
||
|
|
results,
|
||
|
|
synthesis: synthesis.successful[0]?.result,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export default { swarmExecute, coordinateAgents };
|