Rilevamento silenzio (evita allucinazioni su audio vuoto) + gestione risposte vuote/malformate

This commit is contained in:
dev
2026-08-10 13:04:27 +02:00
parent c2924fee63
commit cb726a498e
+45 -5
View File
@@ -10,7 +10,7 @@
* L'agente (pi) decide quale strumento usare in base al task. * L'agente (pi) decide quale strumento usare in base al task.
*/ */
import { execFile, spawn } from "node:child_process"; import { execFile, execFileSync, spawn } from "node:child_process";
import * as fs from "node:fs"; import * as fs from "node:fs";
import * as os from "node:os"; import * as os from "node:os";
import * as path from "node:path"; import * as path from "node:path";
@@ -395,6 +395,32 @@ async function optimizeAudio(input: string): Promise<string> {
// Trascrizione affidabile e veloce via Gemini API diretta // Trascrizione affidabile e veloce via Gemini API diretta
// (agy CLI non supporta audio; la API supporta audio/wav nativamente) // (agy CLI non supporta audio; la API supporta audio/wav nativamente)
// Rileva se l'audio contiene parlato reale (non solo silenzio)
// Ritorna true se c'è segnale, false se è silenzio
function audioHasSpeech(file: string): boolean {
try {
const { stdout } = execFileSync(
"ffmpeg",
["-i", file, "-af", "volumedetect", "-f", "null", "-"],
{ timeout: 30_000, encoding: "utf8" },
);
const maxMatch = stdout.match(/max_volume: ([\-0-9.]+) dB/);
const meanMatch = stdout.match(/mean_volume: ([\-0-9.]+) dB/);
if (maxMatch) {
const max = parseFloat(maxMatch[1]);
// max < -35dB ≈ silenzio quasi totale
if (max < -35) return false;
}
if (meanMatch) {
const mean = parseFloat(meanMatch[1]);
if (mean < -45) return false;
}
return true;
} catch {
return true; // se non possiamo verificare, assumiamo che ci sia parlato
}
}
// Risultato trascrizione con dettaglio errore // Risultato trascrizione con dettaglio errore
interface TranscriptResult { interface TranscriptResult {
text: string; text: string;
@@ -415,6 +441,11 @@ async function transcribeWithGeminiAPI(file: string): Promise<TranscriptResult>
} }
if (!key) return { text: "", error: "Key Gemini mancante (imposta con /agy:config set geminiApiKey <chiave>)" }; if (!key) return { text: "", error: "Key Gemini mancante (imposta con /agy:config set geminiApiKey <chiave>)" };
// rileva silenzio prima di chiamare l'API (evita allucinazioni su audio vuoto)
if (!audioHasSpeech(file)) {
return { text: "", error: "Nessun parlato rilevato nell'audio (silenzio o volume troppo basso)" };
}
const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash"; const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
try { try {
// Comprimi in MP3 per evitare HTTP 413 su registrazioni lunghe // Comprimi in MP3 per evitare HTTP 413 su registrazioni lunghe
@@ -458,11 +489,14 @@ async function transcribeWithGeminiAPI(file: string): Promise<TranscriptResult>
signal: AbortSignal.timeout(120_000), signal: AbortSignal.timeout(120_000),
}, },
); );
if (res.ok) { if (res.ok) {
const data: any = await res.json(); const data: any = await res.json();
const text = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? ""; const text = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
return { text }; if (!text) {
return { text: "", error: "La Gemini API ha restituito una risposta vuota" };
} }
return { text };
}
lastErr = `HTTP ${res.status}`; lastErr = `HTTP ${res.status}`;
const errBody = await res.text().catch(() => ""); const errBody = await res.text().catch(() => "");
try { try {
@@ -519,6 +553,12 @@ async function transcribeWithEnne2(file: string): Promise<TranscriptResult> {
const baseUrl = getConfig("sttUrl") ?? "https://ai.enne2.net"; const baseUrl = getConfig("sttUrl") ?? "https://ai.enne2.net";
const model = getConfig("sttModel") ?? "gemma4:E4B"; const model = getConfig("sttModel") ?? "gemma4:E4B";
const apiKey = getConfig("enne2ApiKey") ?? process.env.ENNE2_API_KEY ?? ""; const apiKey = getConfig("enne2ApiKey") ?? process.env.ENNE2_API_KEY ?? "";
// rileva silenzio
if (!audioHasSpeech(file)) {
return { text: "", error: "Nessun parlato rilevato nell'audio (silenzio o volume troppo basso)" };
}
try { try {
const b64 = fs.readFileSync(file).toString("base64"); const b64 = fs.readFileSync(file).toString("base64");
const body = { const body = {