feat(vocale): interpretazione come analista inter-agent in JSON + guida Perplexity

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.
This commit is contained in:
2026-08-11 20:26:59 +02:00
parent f9a8648c86
commit aa888fe9cc
2 changed files with 70 additions and 5 deletions
+21
View File
@@ -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:
```
+49 -5
View File
@@ -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) {