diff --git a/.env.example b/.env.example index 0493550..0fb0128 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,2 @@ 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 0aabf07..57eb50f 100644 --- a/src/gemini.ts +++ b/src/gemini.ts @@ -11,8 +11,7 @@ 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; +export const MODEL = 'gemini-3.5-flash-lite'; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); @@ -26,28 +25,22 @@ 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]; +// Los errores temporales de Gemini pueden resolverse con reintentos del mismo modelo. +const RETRY_DELAYS = [2_000, 4_000, 8_000, 16_000, 32_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); - } + for (let attempt = 0; ; attempt++) { + try { + return await ai.models.generateContent({ model: MODEL, contents }); + } catch (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) throw err; + console.warn(`${MODEL} respondió ${status}; reintento ${attempt + 1}/${RETRY_DELAYS.length} en ${delay / 1000}s`); + await sleep(delay); } } - throw lastErr; } export async function generateNotes(tracks: AudioTrack[], startedAt: Date): Promise { diff --git a/src/index.ts b/src/index.ts index 68aa453..ff88d0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,9 +9,9 @@ import { } from 'discord.js'; import { VoiceConnectionStatus, entersState } from '@discordjs/voice'; import { Recording } from './recorder.js'; -import { FALLBACK_MODEL, MODEL, generateNotes } from './gemini.js'; +import { MODEL, generateNotes } from './gemini.js'; -for (const key of ['DISCORD_TOKEN', 'GEMINI_API_KEY', 'GEMINI_MODEL']) { +for (const key of ['DISCORD_TOKEN', 'GEMINI_API_KEY']) { 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} | respaldo: ${FALLBACK_MODEL ?? '(ninguno)'}`); + console.log(`ready as ${c.user.tag} | modelo único: ${MODEL} | reintentos activos`); }); client.on(Events.InteractionCreate, async (interaction) => {