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)
async function transcribeWithWhisper(file: string): Promise<string> {
const model = process.env.AGY_WHISPER_MODEL ?? "small";
const script = `
from faster_whisper import WhisperModel
import sys
model = WhisperModel('${model}', device='cpu', compute_type='int8')
segments, info = model.transcribe(sys.argv[1], language='it')
for seg in segments:
print(seg.text, end='')
`;
// Trascrizione affidabile e veloce via Gemini API diretta
// (agy CLI non supporta audio; la API supporta audio/wav nativamente)
async function transcribeWithGeminiAPI(file: string): Promise<string> {
// key da env var o file config
let key = process.env.GEMINI_API_KEY ?? "";
if (!key) {
try {
const { stdout } = await execFileAsync("python3", ["-c", script, file], {
timeout: 180_000,
maxBuffer: 10 * 1024 * 1024,
});
return stdout.trim();
key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
} catch {
/* ignora */
}
}
if (!key) return "";
const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
try {
const b64 = fs.readFileSync(file).toString("base64");
const body = {
contents: [
{
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 {
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({
name: "agy_transcribe",
label: "agy transcribe audio",
description:
"Trascrive il contenuto di un file audio (voce, discorso) in testo. " +
"Richiede --yolo (auto-approva gli strumenti).",
"Trascrive il contenuto di un file audio (voce, discorso) in testo usando la Gemini API diretta " +
"(veloce e affidabile; agy CLI non supporta audio). Richiede la key Gemini in ~/.agy-chat/gemini-key o GEMINI_API_KEY.",
parameters: Type.Object({
filePath: Type.String({ description: "Percorso del file audio (wav, mp3, m4a, ecc.)." }),
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) {
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...");
const res = await executeAgy({
prompt,
mode: "analyze",
model: p.model,
stateless: true,
filePaths: [p.filePath],
yolo: true,
signal,
});
const transcript = await transcribeWithGeminiAPI(p.filePath);
if (!transcript) {
return {
content: [{ type: "text", text: res.text }],
details: { filePath: p.filePath, exitCode: res.exitCode },
content: [{ type: "text", text: "Trascrizione fallita: key Gemini mancante o errore API." }],
details: { filePath: p.filePath },
isError: true,
};
}
return {
content: [{ type: "text", text: transcript }],
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");
const optimized = await optimizeAudio(file);
ctx.ui.notify("Trascrizione in corso (faster-whisper)...", "info");
const transcript = await transcribeWithWhisper(optimized);
ctx.ui.notify("Trascrizione in corso (Gemini API)...", "info");
const transcript = await transcribeWithGeminiAPI(optimized);
if (!transcript) {
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;
}