d
This commit is contained in:
1 parent
04f5d6ec75
commit
89fc33d954
3 files changed
+40
-7
No files matched your search
@@ -1,3 +1,5 @@
|
|||||||
DISCORD_TOKEN=
|
DISCORD_TOKEN=
|
||||||
GEMINI_API_KEY=
|
GEMINI_API_KEY=
|
||||||
GEMINI_MODEL=
|
GEMINI_MODEL=
|
||||||
|
# Opcional: se usa si GEMINI_MODEL sigue saturado después de los reintentos
|
||||||
|
GEMINI_FALLBACK_MODEL=
|
||||||
+36
-5
@@ -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 type { AudioTrack } from './recorder.js';
|
||||||
import { buildPrompt } from './prompt.js';
|
import { buildPrompt } from './prompt.js';
|
||||||
|
|
||||||
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
||||||
export const MODEL = process.env.GEMINI_MODEL!;
|
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));
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
@@ -17,6 +26,30 @@ async function upload(track: AudioTrack): Promise<File> {
|
|||||||
return file;
|
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<string> {
|
export async function generateNotes(tracks: AudioTrack[], startedAt: Date): Promise<string> {
|
||||||
const files: File[] = [];
|
const files: File[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -26,10 +59,8 @@ export async function generateNotes(tracks: AudioTrack[], startedAt: Date): Prom
|
|||||||
`Pista ${i + 1}: ${track.name}`,
|
`Pista ${i + 1}: ${track.name}`,
|
||||||
createPartFromUri(files[i].uri!, files[i].mimeType!),
|
createPartFromUri(files[i].uri!, files[i].mimeType!),
|
||||||
]);
|
]);
|
||||||
const res = await ai.models.generateContent({
|
const contents = createUserContent([buildPrompt(tracks.map((t) => t.name), startedAt), ...parts]);
|
||||||
model: MODEL,
|
const res = await generateWithRetry(contents);
|
||||||
contents: createUserContent([buildPrompt(tracks.map((t) => t.name), startedAt), ...parts]),
|
|
||||||
});
|
|
||||||
const text = res.text?.trim();
|
const text = res.text?.trim();
|
||||||
if (!text) throw new Error('Gemini devolvió una respuesta vacía');
|
if (!text) throw new Error('Gemini devolvió una respuesta vacía');
|
||||||
return text.replace(/^```(?:markdown|md)?\n([\s\S]*)\n```$/, '$1');
|
return text.replace(/^```(?:markdown|md)?\n([\s\S]*)\n```$/, '$1');
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ import {
|
|||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { VoiceConnectionStatus, entersState } from '@discordjs/voice';
|
import { VoiceConnectionStatus, entersState } from '@discordjs/voice';
|
||||||
import { Recording } from './recorder.js';
|
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']) {
|
for (const key of ['DISCORD_TOKEN', 'GEMINI_API_KEY', 'GEMINI_MODEL']) {
|
||||||
if (!process.env[key]) throw new Error(`Falta la variable de entorno ${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) => {
|
client.once(Events.ClientReady, async (c) => {
|
||||||
await c.application.commands.set(commands.map((cmd) => cmd.toJSON()));
|
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) => {
|
client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
|
|||||||
Reference in new issue
Block a user