From aa888fe9cc34fc3df5095e2cc78de08034919ddd Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Tue, 11 Aug 2026 20:26:59 +0200 Subject: [PATCH] feat(vocale): interpretazione come analista inter-agent in JSON + guida Perplexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'interpretazione del prompt vocale ora usa un framing inter-agent: Gemini è un analista tecnico/middleware che scrive un briefing per l'orchestratore (NON risponde all'utente), in JSON strutturato. - Nuovo prompt: ruolo analista, destinatario = solo l'agente orchestratore, nessun testo rivolto all'utente, plan-first (nessuna esecuzione). - Contratto JSON: {trascrizione_corretta, intent_analisi, note_per_agent, azioni_raccomandate, prompt_utente_pulito, ricerca_necessaria, suggerimenti_ricerca}. - extractVoiceBriefing(): estrae prompt_utente_pulito (-> finalText) e se ricerca_necessaria=true aggiunge al prompt finale la nota di usare la ricerca web (Perplexity) per best practices/versioni/documentazione. - Testato: parser JSON (valido, con markdown attorno, fallback su testo grezzo). - docs/vocal-workflow.md aggiornato. --- docs/vocal-workflow.md | 21 ++++++++++++++++ extensions/index.ts | 54 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/docs/vocal-workflow.md b/docs/vocal-workflow.md index b4695a9..95cec25 100644 --- a/docs/vocal-workflow.md +++ b/docs/vocal-workflow.md @@ -83,6 +83,27 @@ Con `vocalPlanningMode=true` (default) il flusso di interpretazione vocale è d'azione sintetico** (nessuna esecuzione). Questo evita che l'interpretazione avvii autonomamente loop agentici o modifiche a sorpresa a causa di errori di STT. +L'interpretazione usa un **framing inter-agent**: Gemini agisce come **analista +tecnico/middleware** che scrive un briefing per l'orchestratore (NON risponde +all'utente), in **JSON strutturato**: + +```json +{ + "trascrizione_corretta": "...", + "intent_analisi": "...", + "note_per_agent": "...", + "azioni_raccomandate": ["..."], + "prompt_utente_pulito": "...", + "ricerca_necessaria": true/false, + "suggerimenti_ricerca": ["..."] +} +``` + +Il campo `prompt_utente_pulito` diventa il prompt finale per l'orchestratore. Se +`ricerca_necessaria=true`, al prompt finale viene aggiunta una **nota che +indica all'agente successivo di usare la ricerca web (Perplexity)** per +verificare best practices, versioni o documentazione aggiornate. + Poi un **overlay TUI** mostra trascrizione + piano e attende la conferma: ``` diff --git a/extensions/index.ts b/extensions/index.ts index 27834bd..d4f2034 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -715,6 +715,31 @@ function stageExternalFiles( return { prompt: newPrompt, filePaths: stagedPaths, dirs: [] }; } +// Estrae dal briefing JSON prodotto dall'interpretazione vocale i campi per +// l'orchestratore: prompt pulito, se serve ricerca web e suggerimenti. +function extractVoiceBriefing(text: string): { + cleanPrompt: string; + needsSearch: boolean; + searchHints: string[]; +} { + const result = { cleanPrompt: "", needsSearch: false, searchHints: [] as string[] }; + try { + const start = text.indexOf("{"); + const end = text.lastIndexOf("}"); + if (start >= 0 && end > start) { + const obj = JSON.parse(text.slice(start, end + 1)); + if (typeof obj.prompt_utente_pulito === "string") result.cleanPrompt = obj.prompt_utente_pulito.trim(); + result.needsSearch = String(obj.ricerca_necessaria).toLowerCase() === "true"; + if (Array.isArray(obj.suggerimenti_ricerca)) { + result.searchHints = obj.suggerimenti_ricerca.map(String).filter(Boolean); + } + } + } catch { + /* JSON non parsato: si usa il testo grezzo come fallback */ + } + return result; +} + async function executeAgy(opts: AgyExecOptions) { // Staging dei file esterni (workaround accesso agy a file fuori dal workspace) const staged = stageExternalFiles(opts.prompt, opts.filePaths ?? []); @@ -2154,25 +2179,44 @@ export default function agyExtension(pi: ExtensionAPI) { // e lo combina con la trascrizione audio (comportamento "multimodale"). const editorText = (ctx.ui.getEditorText?.() ?? "").trim(); - // Interpreta con Gemini (testo) — direttiva plan-first: solo piano, no esecuzione + // Interpreta con Gemini (testo) — framing inter-agent: Gemini è un analista + // che scrive un briefing per l'orchestratore (NON risponde all'utente), + // in JSON strutturato, con direttiva plan-first (nessuna esecuzione). ctx.ui.notify("Interpretazione con Gemini...", "info"); const context = getConversationContext(ctx); const res = await executeAgy({ prompt: - `Ecco la trascrizione di un messaggio vocale dell'utente:\n\n${transcript}` + + `[CONTESTO INTERNO — COMUNICAZIONE TRA AGENTI]\n` + + `Sei un analista tecnico/middleware per un agente AI orchestratore. NON rispondere all'utente: il tuo output sarà letto SOLO dall'orchestratore, che poi risponderà all'utente.` + + `\n\nAnalizza la richiesta vocale (e l'eventuale testo dell'editor) e produci un briefing strutturato per l'orchestratore.` + + `\n\nTrascrizione vocale:\n${transcript}` + (editorText ? `\n\nTesto scritto dall'utente nel campo di input (da combinare con la voce):\n${editorText}` : "") + `\n\nContesto della conversazione:\n${context || "(nessuno)"}` + - `\n\nProduce SOLO: 1) la trascrizione corretta, 2) un piano d'azione sintetico.` + - `\nIMPORTANTE: NON eseguire alcuno strumento o azione. Attendi la conferma dell'utente.`, + `\n\nRestituisci UN SOLO oggetto JSON (nessun testo aggiuntivo) con questi campi:` + + `\n{"trascrizione_corretta":"...","intent_analisi":"...","note_per_agent":"...","azioni_raccomandate":["..."],"prompt_utente_pulito":"...","ricerca_necessaria":true/false,"suggerimenti_ricerca":["..."]}` + + `\nRegole:` + + `\n- NON eseguire alcuno strumento o azione; solo analisi e briefing.` + + `\n- Nessun saluto o testo rivolto all'utente: solo JSON tecnico per l'orchestratore.` + + `\n- prompt_utente_pulito = la richiesta rielaborata che l'orchestratore userà come prompt verso l'utente.` + + `\n- ricerca_necessaria=true se serve verificare best practices, versioni, documentazione o dati aggiornati.` + + `\n- suggerimenti_ricerca: se ricerca_necessaria, indica all'orchestratore di usare la ricerca web (Perplexity) e su cosa.`, stateless: true, model: "Gemini 3.6 Flash (Medium)", yolo: true, }); ctx.ui.setStatus("agy-rec", ""); - let finalText = res.text.trim() || transcript; + // Estrae il briefing JSON; fallback sul testo grezzo se non parsato + const briefing = extractVoiceBriefing(res.text); + let finalText = briefing.cleanPrompt || res.text.trim() || transcript; + + // Guida per l'agente successivo: usa la ricerca web/Perplexity se necessario + if (briefing.needsSearch) { + const hints = briefing.searchHints.length ? ` Suggerimenti: ${briefing.searchHints.join("; ")}` : ""; + finalText += `\n\n[Nota per l'agente: per rispondere correttamente, usa la ricerca web (Perplexity) per verificare best practices/versioni/documentazione aggiornate.${hints}]`; + } // Il testo dell'editor è stato consumato: lo svuota per evitare reinvii duplicati. if (editorText) {