Spaces:
Sleeping
Sleeping
File size: 7,875 Bytes
cc11e77 | 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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | /**
* 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 };
}
|