From 89fc33d9548fabf91c2a2e918fc899b5cedc0ad1 Mon Sep 17 00:00:00 2001 From: JuanMaldini Date: Mon, 28 Sep 2026 12:26:14 -0300 Subject: [PATCH] d --- .env.example | 2 ++ src/gemini.ts | 41 ++++++++++++++++++++++++++++++++++++----- src/index.ts | 4 ++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 8fd846c..0493550 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ DISCORD_TOKEN= GEMINI_API_KEY= GEMINI_MODEL= +# Opcional: se usa si GEMINI_MODEL sigue saturado después de los reintentos +GEMINI_FALLBACK_MODEL= diff --git a/src/gemini.ts b/src/gemini.ts index c0c3d79..0aabf07 100644 --- a/src/gemini.ts +++ b/src/gemini.ts @@ -1,9 +1,18 @@ -import { GoogleGenAI, FileState, createPartFromUri, createUserContent, type File } from '@google/genai'; +import { + ApiError, + FileState, + GoogleGenAI, + createPartFromUri, + createUserContent, + type Content, + type File, +} from '@google/genai'; import type { AudioTrack } from './recorder.js'; import { buildPrompt } from './prompt.js'; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); export const MODEL = process.env.GEMINI_MODEL!; +export const FALLBACK_MODEL = process.env.GEMINI_FALLBACK_MODEL; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -17,6 +26,30 @@ async function upload(track: AudioTrack): Promise { return file; } +// 429/500/503 suelen ser picos de demanda: reintenta y, si hay GEMINI_FALLBACK_MODEL, prueba con ese +const RETRY_DELAYS = [5_000, 15_000, 30_000]; + +async function generateWithRetry(contents: Content) { + const models = [MODEL, FALLBACK_MODEL].filter((m): m is string => !!m); + let lastErr: unknown; + for (const model of models) { + for (let attempt = 0; attempt <= RETRY_DELAYS.length; attempt++) { + try { + return await ai.models.generateContent({ model, contents }); + } catch (err) { + lastErr = err; + const status = err instanceof ApiError ? err.status : 0; + if (![429, 500, 503].includes(status)) throw err; + const delay = RETRY_DELAYS[attempt]; + if (delay === undefined) break; + console.warn(`${model} respondió ${status}, reintento en ${delay / 1000}s`); + await sleep(delay); + } + } + } + throw lastErr; +} + export async function generateNotes(tracks: AudioTrack[], startedAt: Date): Promise { const files: File[] = []; try { @@ -26,10 +59,8 @@ export async function generateNotes(tracks: AudioTrack[], startedAt: Date): Prom `Pista ${i + 1}: ${track.name}`, createPartFromUri(files[i].uri!, files[i].mimeType!), ]); - const res = await ai.models.generateContent({ - model: MODEL, - contents: createUserContent([buildPrompt(tracks.map((t) => t.name), startedAt), ...parts]), - }); + const contents = createUserContent([buildPrompt(tracks.map((t) => t.name), startedAt), ...parts]); + const res = await generateWithRetry(contents); const text = res.text?.trim(); if (!text) throw new Error('Gemini devolvió una respuesta vacía'); return text.replace(/^```(?:markdown|md)?\n([\s\S]*)\n```$/, '$1'); diff --git a/src/index.ts b/src/index.ts index 604627f..68aa453 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import { } from 'discord.js'; import { VoiceConnectionStatus, entersState } from '@discordjs/voice'; import { Recording } from './recorder.js'; -import { MODEL, generateNotes } from './gemini.js'; +import { FALLBACK_MODEL, MODEL, generateNotes } from './gemini.js'; for (const key of ['DISCORD_TOKEN', 'GEMINI_API_KEY', 'GEMINI_MODEL']) { if (!process.env[key]) throw new Error(`Falta la variable de entorno ${key}`); @@ -62,7 +62,7 @@ async function autoFinish(guildId: string, reason: string) { client.once(Events.ClientReady, async (c) => { await c.application.commands.set(commands.map((cmd) => cmd.toJSON())); - console.log(`ready as ${c.user.tag} | modelo: ${MODEL}`); + console.log(`ready as ${c.user.tag} | modelo: ${MODEL} | respaldo: ${FALLBACK_MODEL ?? '(ninguno)'}`); }); client.on(Events.InteractionCreate, async (interaction) => {