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
+41 -1
View File
@@ -10,7 +10,7 @@
* 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 os from "node:os";
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
// (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
interface TranscriptResult {
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>)" };
// 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";
try {
// Comprimi in MP3 per evitare HTTP 413 su registrazioni lunghe
@@ -461,6 +492,9 @@ async function transcribeWithGeminiAPI(file: string): Promise<TranscriptResult>
if (res.ok) {
const data: any = await res.json();
const text = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
if (!text) {
return { text: "", error: "La Gemini API ha restituito una risposta vuota" };
}
return { text };
}
lastErr = `HTTP ${res.status}`;
@@ -519,6 +553,12 @@ async function transcribeWithEnne2(file: string): Promise<TranscriptResult> {
const baseUrl = getConfig("sttUrl") ?? "https://ai.enne2.net";
const model = getConfig("sttModel") ?? "gemma4:E4B";
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 {
const b64 = fs.readFileSync(file).toString("base64");
const body = {