Spaces:
Sleeping
Sleeping
| /** | |
| * 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 }; | |
| } | |