Trascrizione audio via Gemini API diretta (gemini-3.5-flash): veloce e affidabile
This commit is contained in:
+55
-40
@@ -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 {
|
||||
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 { stdout } = await execFileAsync("python3", ["-c", script, file], {
|
||||
timeout: 180_000,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
});
|
||||
return stdout.trim();
|
||||
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: "Trascrizione fallita: key Gemini mancante o errore API." }],
|
||||
details: { filePath: p.filePath },
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: res.text }],
|
||||
details: { filePath: p.filePath, exitCode: res.exitCode },
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user