Trascrizione audio via Gemini API diretta (gemini-3.5-flash): veloce e affidabile

This commit is contained in:
dev
2026-08-09 22:27:19 +02:00
parent 674819f843
commit 910263d7d7
+55 -40
View File
@@ -308,23 +308,45 @@ async function optimizeAudio(input: string): Promise<string> {
} }
} }
// Trascrizione locale affidabile con faster-whisper (agy CLI non supporta audio) // Trascrizione affidabile e veloce via Gemini API diretta
async function transcribeWithWhisper(file: string): Promise<string> { // (agy CLI non supporta audio; la API supporta audio/wav nativamente)
const model = process.env.AGY_WHISPER_MODEL ?? "small"; async function transcribeWithGeminiAPI(file: string): Promise<string> {
const script = ` // key da env var o file config
from faster_whisper import WhisperModel let key = process.env.GEMINI_API_KEY ?? "";
import sys if (!key) {
model = WhisperModel('${model}', device='cpu', compute_type='int8') try {
segments, info = model.transcribe(sys.argv[1], language='it') key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
for seg in segments: } catch {
print(seg.text, end='') /* ignora */
`; }
}
if (!key) return "";
const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
try { try {
const { stdout } = await execFileAsync("python3", ["-c", script, file], { const b64 = fs.readFileSync(file).toString("base64");
timeout: 180_000, const body = {
maxBuffer: 10 * 1024 * 1024, contents: [
}); {
return stdout.trim(); parts: [
{ text: "Trascrivi fedelmente il contenuto di questo audio in italiano. Restituisci SOLO la trascrizione testuale." },
{ inline_data: { mime_type: "audio/wav", data: b64 } },
],
},
],
};
const res = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(60_000),
},
);
if (!res.ok) return "";
const data: any = await res.json();
return data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
} catch { } catch {
return ""; return "";
} }
@@ -737,40 +759,33 @@ export default function agyExtension(pi: ExtensionAPI) {
}); });
// ========================================================================= // =========================================================================
// TOOL: agy_transcribe — trascrizione audio // TOOL: agy_transcribe — trascrizione audio (via Gemini API diretta)
// ========================================================================= // =========================================================================
pi.registerTool({ pi.registerTool({
name: "agy_transcribe", name: "agy_transcribe",
label: "agy transcribe audio", label: "agy transcribe audio",
description: description:
"Trascrive il contenuto di un file audio (voce, discorso) in testo. " + "Trascrive il contenuto di un file audio (voce, discorso) in testo usando la Gemini API diretta " +
"Richiede --yolo (auto-approva gli strumenti).", "(veloce e affidabile; agy CLI non supporta audio). Richiede la key Gemini in ~/.agy-chat/gemini-key o GEMINI_API_KEY.",
parameters: Type.Object({ parameters: Type.Object({
filePath: Type.String({ description: "Percorso del file audio (wav, mp3, m4a, ecc.)." }), filePath: Type.String({ description: "Percorso del file audio (wav, mp3, m4a, ecc.)." }),
language: Type.Optional(Type.String({ description: "Lingua del contenuto (es. 'italiano', 'english')." })), language: Type.Optional(Type.String({ description: "Lingua del contenuto (es. 'italiano', 'english')." })),
model: Type.Optional(Type.String({ description: "Modello agy." })), model: Type.Optional(Type.String({ description: "Modello Gemini (default: gemini-3.5-flash)." })),
}), }),
async execute(toolCallId, params, signal, onUpdate, ctx) { async execute(toolCallId, params, signal, onUpdate, ctx) {
const p = params as any; const p = params as any;
const lang = p.language ? ` La lingua del contenuto è ${p.language}.` : "";
const prompt =
`Trascrivi il contenuto del file audio al percorso ${p.filePath}.` +
lang +
` Restituisci la trascrizione testuale completa e fedele.`;
onUpdate?.("agy_transcribe: trascrizione audio..."); onUpdate?.("agy_transcribe: trascrizione audio...");
const res = await executeAgy({ const transcript = await transcribeWithGeminiAPI(p.filePath);
prompt, if (!transcript) {
mode: "analyze", return {
model: p.model, content: [{ type: "text", text: "Trascrizione fallita: key Gemini mancante o errore API." }],
stateless: true, details: { filePath: p.filePath },
filePaths: [p.filePath], isError: true,
yolo: true, };
signal, }
});
return { return {
content: [{ type: "text", text: res.text }], content: [{ type: "text", text: transcript }],
details: { filePath: p.filePath, exitCode: res.exitCode }, details: { filePath: p.filePath, model: p.model ?? "gemini-3.5-flash" },
}; };
}, },
}); });
@@ -940,11 +955,11 @@ export default function agyExtension(pi: ExtensionAPI) {
ctx.ui.notify("Registrazione fermata, ottimizzazione audio...", "info"); ctx.ui.notify("Registrazione fermata, ottimizzazione audio...", "info");
const optimized = await optimizeAudio(file); const optimized = await optimizeAudio(file);
ctx.ui.notify("Trascrizione in corso (faster-whisper)...", "info"); ctx.ui.notify("Trascrizione in corso (Gemini API)...", "info");
const transcript = await transcribeWithWhisper(optimized); const transcript = await transcribeWithGeminiAPI(optimized);
if (!transcript) { if (!transcript) {
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify("Trascrizione vuota o errore", "error"); ctx.ui.notify("Trascrizione vuota o errore (key Gemini mancante?)", "error");
return; return;
} }