AUDIT / src /lib /agentLoop /case1TailBlock.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
7.88 kB
/**
* case1TailBlock.ts — S496: extracted from agentLoop.ts
*
* Post-processing del Case 1 (native tool calls) dopo che tutti i risultati
* dei tool sono stati processati e iniettati nel contesto.
*
* Pipeline (in ordine):
* 1. Stagnation guard — 5 iter consecutive con tutti i tool falliti → action "return"
* (rescue cascade inietta hint a soglia 2 e 4 in agentLoop.ts prima del hard-exit)
* 2. Belief revision per tool in errore → push recovery message
* 3. DONE: signal check — rawText può contenere DONE: anche con tool_calls (Gap C/S122)
* → emitFinalText + action "return"
* 4. Synthesis directive — per query non-code con tool riusciti → cap maxIter
* → action "continue"
*
* @module case1TailBlock
*/
import type { AgentStep } from "../storage";
import type { ApiMsg, ToolCallRaw } from "./networkTools";
import { normalizeLlmText } from "../utils/normalizeLlmOutput";
import { handleToolFailureBelief, shouldReviseBeliefs, buildRevisionPrompt, persistBeliefState } from "../beliefRevision";
import { buildRecoveryMessage, recordAndSuggestFix } from "../agent/errorRecovery";
import { generateSuggestions, type Suggestion } from "../agent/SmartSuggestions";
export interface Case1TailCtx {
results: Array<{ tc: ToolCallRaw; result: string; durationMs: number; args: Record<string, unknown> }>;
failureRecoveries: Array<{ toolName: string; result: ReturnType<typeof recordAndSuggestFix> }>;
anyError: boolean;
consecutiveAllFailIters: number;
effectiveMaxIter: number;
steps: AgentStep[];
loopMessages: ApiMsg[];
iter: number;
isCodeTask: boolean;
rawText: string | null;
signal?: AbortSignal;
lastUserMsg: string;
loopTaskId: string | null;
onChunk: (c: string) => void;
onSteps: (steps: AgentStep[]) => void;
onStatus: (msg: string) => void;
onSuggestions: ((s: Suggestion[]) => void) | undefined;
emitFinalText: (text: string, signal?: AbortSignal) => void;
writeSess: () => void;
}
export type Case1TailAction = "return" | "return-success" | "continue";
export interface Case1TailResult {
action: Case1TailAction;
newConsecutiveAllFailIters: number;
newEffectiveMaxIter: number;
}
export function processCase1Tail(ctx: Case1TailCtx): Case1TailResult {
const {
results, failureRecoveries, anyError, steps, loopMessages,
iter, isCodeTask, rawText, signal, lastUserMsg,
onSteps, onStatus, onSuggestions, emitFinalText, writeSess,
loopTaskId,
} = ctx;
let newConsecutiveAllFailIters = ctx.consecutiveAllFailIters;
let newEffectiveMaxIter = ctx.effectiveMaxIter;
// 1. Stagnation guard: 3 consecutive all-fail iters → return
{
const _c1AllFailed = results.every(r => r.result.startsWith("❌"));
newConsecutiveAllFailIters = _c1AllFailed ? newConsecutiveAllFailIters + 1 : 0;
if (newConsecutiveAllFailIters >= 5) { // S-RESCUE-C1: raise da 3→5 — rescue hints girano a 2 e 4 in agentLoop.ts
// GAP-TRUTH: usa emitFinalText (con gate) invece di onChunk diretto — consistenza con tutti gli altri exit path
emitFinalText(
"_(Stagnazione rilevata: " + newConsecutiveAllFailIters +
" iterazioni consecutive con tutti i tool falliti. Interrompo il loop. Prova a riformulare il task o cambia provider.)_",
signal,
);
writeSess(); // S-ARC3: salva sessione su stagnazione
return { action: "return", newConsecutiveAllFailIters, newEffectiveMaxIter };
}
}
// 2. Belief revision per tool in errore (S103/S351: extracted to beliefRevision.ts)
if (anyError) {
for (const { tc: _brtc, result: _brr } of results) {
if (_brr.startsWith("❌")) {
handleToolFailureBelief({
taskId: loopTaskId ?? "",
lastUserMsg,
toolName: _brtc.function.name,
errorResult: _brr,
iter,
path: "tool_calls",
loopMessages: loopMessages as ApiMsg[],
steps, onSteps, onStatus,
});
}
}
// S351: persiste belief state cross-sessione (case1 path — parità con case2 ReAct)
try { persistBeliefState(loopTaskId ?? "", lastUserMsg.slice(0, 40)); } catch { /* non-blocking */ }
// S-GAP2a (case1 path): belief revision narrative — visibile anche nel path tool_calls
try {
if (shouldReviseBeliefs(loopTaskId ?? "")) {
const _br = buildRevisionPrompt(loopTaskId ?? "", lastUserMsg);
if (_br.shouldRevise) {
const _cats = _br.distinctErrorCategories;
let _narrativeMsg = "Sto ripensando l’approccio al task";
if (_cats.includes("format_error")) _narrativeMsg = "Formato incompatibile — rianalizzando il task da zero";
else if (_cats.includes("not_found")) _narrativeMsg = "Risorse non trovate — ridefinendo la strategia";
else if (_cats.includes("logic_error")) _narrativeMsg = "Errore logico identificato — riparto dall’analisi";
else if (_cats.includes("network_error")) _narrativeMsg = "Problema connessione — cambio approccio";
onStatus(_narrativeMsg);
steps.push({
tool: "__belief_revision__",
args: { categories: _cats },
result: `Revisione attivata: ${_cats.join(", ")}`,
status: "done",
explanation: _narrativeMsg,
durationMs: 0,
} as AgentStep);
onSteps([...steps]);
loopMessages.push({ role: "user", content: _br.revisionPrompt } as ApiMsg);
}
}
} catch { /* non-blocking */ }
const _recoveryMsg = buildRecoveryMessage(failureRecoveries);
loopMessages.push({
role: "user",
content: _recoveryMsg ||
`Alcuni tool hanno fallito: ${results.filter(r => r.result.startsWith("❌")).map(r => r.tc.function.name).join(", ")}. Cambia strategia. Continua il task.`,
} as ApiMsg);
}
// 3. DONE: signal check alongside tool_calls (Gap C/S122)
// F4 fix (S131): worldState.add("build_ok") rimosso — si scrive solo nell'handler DONE: finale
if (rawText) {
const _c1DoneMatch = rawText.match(/^DONE:\s*(.+)$/m);
if (_c1DoneMatch) {
const _c1Sum = _c1DoneMatch[1].trim();
const _c1Final = normalizeLlmText(
rawText
.replace(/<think>[\s\S]*?<\/think>/gi, "")
.replace(/<think>[\s\S]*/gi, "")
.replace(/<action>[\w_]+<\/action>\s*<input>[\s\S]*?<\/input>/gi, "")
.replace(/^(GOAL|DONE_WHEN|OUT_OF_SCOPE)\s*[::]\s*.*/gim, "")
.replace(/^DONE:.*$/m, "")
.trim(),
) || _c1Sum;
if (_c1Final) {
emitFinalText(_c1Final, signal);
try { onSuggestions?.(generateSuggestions(lastUserMsg, steps)); } catch { /* non-blocking */ }
writeSess();
return { action: "return-success", newConsecutiveAllFailIters, newEffectiveMaxIter };
}
}
}
// 4. Synthesis directive (S426-FixC + S434): per query non-code con tool riusciti
if (!isCodeTask && !anyError) {
const _c1RealSuccessSteps = steps.filter(s => !s.tool.startsWith("__") && s.status === "done");
if (_c1RealSuccessSteps.length >= 1) {
loopMessages.push({
role: "user",
content: "Hai ottenuto i dati necessari. Presentali all'utente in modo diretto e completo nella tua prossima risposta. Non chiamare altri tool — scrivi solo la risposta finale.",
} as ApiMsg);
// S434: cap al massimo 1 altra iterazione per la sintesi
newEffectiveMaxIter = Math.min(newEffectiveMaxIter, iter + 2);
}
}
return { action: "continue", newConsecutiveAllFailIters, newEffectiveMaxIter };
}