Spaces:
Sleeping
Sleeping
File size: 4,051 Bytes
95eb75a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import { getGroqTokens } from "@/lib/apiKeys"; // GAP-6: key rotation
import { acquireGroqHigh } from "@/lib/groqSemaphore"; // ORCH-5: priority lane per percorso critico
/**
* debateBlock.ts — S495: extracted from agentLoop.ts
*
* Wrapper per il Dual-Agent Debate (S224).
* Trigger: reasoning_depth="high" + task NON di codice.
* Output: stringa consenso da iniettare come contesto pre-loop, o null.
*
* Separa la logica di orchestrazione del debate dalle variabili di closure
* di agentLoop.ts, rendendo il debate block testabile in isolamento.
*
* @module debateBlock
*/
/**
* iOS-safe AbortSignal con timeout.
* AbortSignal.timeout() richiede iOS Safari >= 16.4 — non disponibile su iOS 15.
* Fallback: AbortController + setTimeout.
*/
function _makeTimedSignal(ms: number): AbortSignal {
if (typeof AbortSignal.timeout === "function") {
try { return AbortSignal.timeout(ms); } catch { /* iOS 15 safeguard */ }
}
const c = new AbortController();
setTimeout(() => c.abort(), ms);
return c.signal;
}
/**
* Esegue il Dual-Agent Debate se le condizioni sono soddisfatte.
* Restituisce il testo del consenso o null (mai lancia — tutto wrapped in try/catch).
*
* @param task — testo del task/messaggio utente
* @param groqToken — Bearer token Groq (null → skip debate)
* @param opts — opzioni: signal, onStatus callback
* @returns — testo consenso o null
*/
export async function runDebateIfNeeded(
task: string,
groqToken: string | null | undefined,
opts?: {
signal?: AbortSignal;
onStatus?: (msg: string) => void;
},
): Promise<string | null> {
if (!groqToken || opts?.signal?.aborted) return null;
try {
opts?.onStatus?.("Valuto da più angolazioni…");
const { debateToText } = await import("../agent/debateRunner");
// Groq 70B per il debate (ragionamento architetturale, non streaming)
// GAP-6: key rotation — tenta tutti gli slot Groq disponibili se 429
const _debateLLM = async (
msgs: Array<{ role: string; content: string }>,
llmOpts?: { signal?: AbortSignal },
): Promise<string> => {
// ORCH-5: acquireGroqHigh — debate è percorso critico, usa priority lane.
// GAP-3: coordina con il semaforo globale (MAX_CONCURRENT_GROQ=3).
// Debate fa 2 chiamate LLM sequenziali (BUILDER + CRITIC) senza passare per providerChain.
const _release = await acquireGroqHigh();
try {
const _pool = [...new Set([...getGroqTokens(), ...(groqToken ? [groqToken] : [])])];
if (_pool.length === 0) return "";
for (let _i = 0; _i < _pool.length; _i++) {
const _tok = _pool[_i];
const r = await fetch("https://api.groq.com/openai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${_tok}`, "Content-Type": "application/json" },
body: JSON.stringify({
model: "llama-3.3-70b-versatile",
messages: msgs,
max_tokens: 1024,
temperature: 0.4,
}),
// iOS-safe: AbortSignal.timeout non disponibile su iOS Safari < 16.4
signal: llmOpts?.signal ?? _makeTimedSignal(22_000),
});
// 429 rate-limit: prova slot successivo se disponibile
if (r.status === 429 && _i < _pool.length - 1) continue;
const d = await r.json() as { choices?: Array<{ message?: { content?: string } }> };
return d.choices?.[0]?.message?.content ?? "";
}
return "";
} finally {
_release();
}
};
const _ct = await debateToText(task, _debateLLM, {
maxRounds: 2,
roundTimeoutMs: 20_000,
totalTimeoutMs: 40_000,
signal: opts?.signal,
});
if (_ct && !_ct.startsWith("❌")) {
opts?.onStatus?.("Analisi completata ✓");
return _ct;
}
return null;
} catch {
// Non-blocking — debate fallisce silenziosamente, loop prosegue invariato
return null;
}
}
|