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:
@@ -83,6 +83,27 @@ Con `vocalPlanningMode=true` (default) il flusso di interpretazione vocale è
|
|||||||
d'azione sintetico** (nessuna esecuzione). Questo evita che l'interpretazione
|
d'azione sintetico** (nessuna esecuzione). Questo evita che l'interpretazione
|
||||||
avvii autonomamente loop agentici o modifiche a sorpresa a causa di errori di STT.
|
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:
|
Poi un **overlay TUI** mostra trascrizione + piano e attende la conferma:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+49
-5
@@ -715,6 +715,31 @@ function stageExternalFiles(
|
|||||||
return { prompt: newPrompt, filePaths: stagedPaths, dirs: [] };
|
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) {
|
async function executeAgy(opts: AgyExecOptions) {
|
||||||
// Staging dei file esterni (workaround accesso agy a file fuori dal workspace)
|
// Staging dei file esterni (workaround accesso agy a file fuori dal workspace)
|
||||||
const staged = stageExternalFiles(opts.prompt, opts.filePaths ?? []);
|
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").
|
// e lo combina con la trascrizione audio (comportamento "multimodale").
|
||||||
const editorText = (ctx.ui.getEditorText?.() ?? "").trim();
|
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");
|
ctx.ui.notify("Interpretazione con Gemini...", "info");
|
||||||
const context = getConversationContext(ctx);
|
const context = getConversationContext(ctx);
|
||||||
const res = await executeAgy({
|
const res = await executeAgy({
|
||||||
prompt:
|
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
|
(editorText
|
||||||
? `\n\nTesto scritto dall'utente nel campo di input (da combinare con la voce):\n${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\nContesto della conversazione:\n${context || "(nessuno)"}` +
|
||||||
`\n\nProduce SOLO: 1) la trascrizione corretta, 2) un piano d'azione sintetico.` +
|
`\n\nRestituisci UN SOLO oggetto JSON (nessun testo aggiuntivo) con questi campi:` +
|
||||||
`\nIMPORTANTE: NON eseguire alcuno strumento o azione. Attendi la conferma dell'utente.`,
|
`\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,
|
stateless: true,
|
||||||
model: "Gemini 3.6 Flash (Medium)",
|
model: "Gemini 3.6 Flash (Medium)",
|
||||||
yolo: true,
|
yolo: true,
|
||||||
});
|
});
|
||||||
ctx.ui.setStatus("agy-rec", "");
|
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.
|
// Il testo dell'editor è stato consumato: lo svuota per evitare reinvii duplicati.
|
||||||
if (editorText) {
|
if (editorText) {
|
||||||
|
|||||||
Reference in New Issue
Block a user