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)
|
// 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')
|
|
||||||
segments, info = model.transcribe(sys.argv[1], language='it')
|
|
||||||
for seg in segments:
|
|
||||||
print(seg.text, end='')
|
|
||||||
`;
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execFileAsync("python3", ["-c", script, file], {
|
key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
|
||||||
timeout: 180_000,
|
} catch {
|
||||||
maxBuffer: 10 * 1024 * 1024,
|
/* ignora */
|
||||||
});
|
}
|
||||||
return stdout.trim();
|
}
|
||||||
|
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 {
|
} 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",
|
|
||||||
model: p.model,
|
|
||||||
stateless: true,
|
|
||||||
filePaths: [p.filePath],
|
|
||||||
yolo: true,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: "Trascrizione fallita: key Gemini mancante o errore API." }],
|
||||||
details: { filePath: p.filePath, exitCode: res.exitCode },
|
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");
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user