From 96ad2d8f489bd00a0e32f3b4020c9cf6bcf148c4 Mon Sep 17 00:00:00 2001 From: shni Date: Sat, 4 Oct 2025 04:18:27 -0500 Subject: [PATCH] feat: enhance image model detection logic and improve fallback handling in AI service --- src/commands/messages/AI/aimodels.ts | 43 ----------- src/commands/messages/AI/setimagemodel.ts | 33 --------- src/core/services/AIService.ts | 90 +++++++++++++++-------- 3 files changed, 59 insertions(+), 107 deletions(-) delete mode 100644 src/commands/messages/AI/aimodels.ts delete mode 100644 src/commands/messages/AI/setimagemodel.ts diff --git a/src/commands/messages/AI/aimodels.ts b/src/commands/messages/AI/aimodels.ts deleted file mode 100644 index 00b5cc9..0000000 --- a/src/commands/messages/AI/aimodels.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CommandMessage } from "../../../core/types/commands"; -import { aiService } from "../../../core/services/AIService"; -import logger from "../../../core/lib/logger"; - -export const command: CommandMessage = { - name: 'aimodels', - type: 'message', - aliases: ['listmodels'], - cooldown: 10, - description: 'Lista modelos de imagen disponibles y muestra el actual.', - category: 'IA', - usage: 'aimodels', - run: async (message, args) => { - try { - const models = await aiService.listImageModels(); - const current = (aiService as any).imageModelName || 'No detectado'; - - if (models.length === 0) { - await message.reply({ - content: `**Modelos de imagen disponibles:** Ninguno detectado -**Modelo actual:** ${current} - -Para usar un modelo específico: -\`GENAI_IMAGE_MODEL=imagen-3.0-fast\`` - }); - return; - } - - const modelList = models.map(m => `• ${m}`).join('\n'); - await message.reply({ - content: `**Modelos de imagen disponibles:** -${modelList} - -**Modelo actual:** ${current} - -Para cambiar: \`GENAI_IMAGE_MODEL=nombre_del_modelo\`` - }); - } catch (error: any) { - logger.error(error, 'Error listando modelos'); - await message.reply({ content: `❌ Error: ${error?.message || 'Error desconocido'}` }); - } - } -}; diff --git a/src/commands/messages/AI/setimagemodel.ts b/src/commands/messages/AI/setimagemodel.ts deleted file mode 100644 index 5d8eec9..0000000 --- a/src/commands/messages/AI/setimagemodel.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CommandMessage } from "../../../core/types/commands"; -import { aiService } from "../../../core/services/AIService"; -import logger from "../../../core/lib/logger"; - -export const command: CommandMessage = { - name: 'setimagemodel', - type: 'message', - aliases: ['setmodel'], - cooldown: 5, - description: 'Establece el modelo de imágenes manualmente.', - category: 'IA', - usage: 'setimagemodel ', - run: async (message, args) => { - try { - if (!args || args.length === 0) { - await message.reply({ - content: 'Uso: setimagemodel \nEjemplo: setimagemodel imagen-3.0-fast' - }); - return; - } - - const model = args.join(' ').trim(); - (aiService as any).setImageModel(model); - - await message.reply({ - content: `✅ Modelo de imágenes establecido: \`${model}\`\nPrueba con: \`aiimg un gato astronauta\`` - }); - } catch (error: any) { - logger.error(error, 'Error estableciendo modelo'); - await message.reply({ content: `❌ Error: ${error?.message || 'Error desconocido'}` }); - } - } -}; diff --git a/src/core/services/AIService.ts b/src/core/services/AIService.ts index 9852ab1..52cacdf 100644 --- a/src/core/services/AIService.ts +++ b/src/core/services/AIService.ts @@ -150,61 +150,89 @@ export class AIService { return override; } - // Lista de candidatos conocidos (orden de preferencia) + // Lista de candidatos más amplia y realista para cuentas Pro actuales const candidates = [ - 'gemini-2.5-flash-image', // puede no estar disponible aún en tu proyecto/region/apiVersion + 'gemini-2.5-flash-exp', + 'gemini-2.0-flash-exp', + 'imagen-3.0-generate-001', + 'imagen-3.0-fast-generate-001', + 'imagen-3.0-001', + 'imagegeneration@002', + 'imagegeneration@001', 'imagen-3.0-generate', 'imagen-3.0-fast', 'imagen-3.0', + 'gemini-2.5-flash-image', ]; - // Intentar listar modelos si el SDK lo soporta + // Intentar listar modelos primero try { const listed: any = await (this.genAIv2 as any).models?.listModels?.(); - const models: string[] = Array.isArray(listed?.models) - ? listed.models.map((m: any) => m?.name || m?.model || m?.id).filter(Boolean) - : []; - if (models.length) { - // Buscar modelos que parezcan de imagen - const imageLike = models.filter((id: string) => /imagen|image/i.test(id)); - // Priorizar nuestros candidatos en el orden propuesto; si no, tomar el primero imageLike - for (const c of candidates) { - if (imageLike.some((m) => m.includes(c))) { - this.imageModelName = imageLike.find((m) => m.includes(c))!; - logger.info({ model: this.imageModelName }, 'Modelo de imágenes detectado por listModels (preferencia)'); - return this.imageModelName; + if (listed?.models && Array.isArray(listed.models)) { + const models: string[] = listed.models + .map((m: any) => m?.name || m?.model || m?.id || m?.displayName) + .filter(Boolean) + .map((name: string) => name.replace(/^models\//, '')); // Quitar prefijo models/ + + logger.debug({ availableModels: models }, 'Modelos disponibles detectados'); + + // Buscar modelos de imagen disponibles + const imageModels = models.filter((id: string) => + /imagen|image|generate|vision/i.test(id) && + !/text|chat|embed|code/i.test(id) + ); + + if (imageModels.length > 0) { + // Priorizar según orden de candidatos + for (const candidate of candidates) { + const found = imageModels.find(m => m.includes(candidate.replace(/^models\//, ''))); + if (found) { + this.imageModelName = found; + logger.info({ model: found, source: 'listModels' }, 'Modelo de imágenes detectado automáticamente'); + return found; + } } - } - if (imageLike[0]) { - this.imageModelName = imageLike[0]; - logger.info({ model: this.imageModelName }, 'Modelo de imágenes detectado por listModels'); - return this.imageModelName; + + // Si no coincide con candidatos conocidos, usar el primero disponible + this.imageModelName = imageModels[0]; + logger.info({ model: imageModels[0], source: 'listModels-fallback' }, 'Modelo de imágenes detectado (fallback)'); + return imageModels[0]; } } } catch (e) { - // Continuar con prueba directa de candidatos si listModels no existe o falla - logger.debug({ err: getErrorMessage(e) }, 'listModels no disponible o falló; probando candidatos conocidos'); + logger.debug({ err: getErrorMessage(e) }, 'listModels no disponible'); } - // Probar generar un ping mínimo con candidatos conocidos (sin coste excesivo) + // Fallback: probar modelos uno por uno con generateContent usando responseMimeType for (const candidate of candidates) { try { - // Intento muy ligero: usar generateImages con un prompt mínimo - await (this.genAIv2 as any).models.generateImages({ + // Usar generateContent con responseMimeType image/* como detector + const testRes: any = await (this.genAIv2 as any).models.generateContent({ model: candidate, - prompt: 'ping', - config: { imageSize: '1:1' }, + contents: [{ text: 'test' }], + config: { + responseMimeType: 'image/png', + maxOutputTokens: 1, + } }); + + // Si no lanza error 404, el modelo existe this.imageModelName = candidate; - logger.info({ model: candidate }, 'Modelo de imágenes detectado por prueba directa'); + logger.info({ model: candidate, source: 'direct-test' }, 'Modelo de imágenes detectado por prueba directa'); return candidate; - } catch (e) { - // 404/NOT_FOUND esperado para modelos no disponibles; seguir - continue; + } catch (e: any) { + const msg = getErrorMessage(e); + if (msg.includes('not found') || msg.includes('404')) { + continue; // Modelo no disponible, probar siguiente + } + // Otros errores pueden indicar que el modelo existe pero falló por otra razón + logger.debug({ candidate, err: msg }, 'Modelo podría existir pero falló la prueba'); } } + // No se encontró ningún modelo de imagen this.imageModelName = null; + logger.warn('No se detectó ningún modelo de imagen disponible'); return null; }