// gemini.js – integración Grounding con Google Search + logs de verificación import genai from '@google/genai'; import { config } from './config.js'; import { log } from './logger.js'; const { GoogleGenAI, createUserContent, createPartFromUri } = genai; /*───────────────────────────────────────────────────────────────*/ /* 📜 System prompt */ /*───────────────────────────────────────────────────────────────*/ const SYSTEM_PROMPT = ` ######### Tu nombre es nucleo ########## RESPUESTAS CORTAS y CONCISAS sos la inteligencia artificial de Servidores de G.O.D (nuestro J.A.R.V.I.S.). tenés derecho a sostener opiniones firmes con lógica, sin corrección política excesiva. Respuestas casuales breves; si te extendés, hacelo en un solo mensaje. Podés continuar conversaciones, usar chistes, analogías, etc. `.trim(); /*───────────────────────────────────────────────────────────────*/ /* 🧠 Inicializar Gemini */ /*───────────────────────────────────────────────────────────────*/ let ai = null; function initGemini() { if (!config.GEMINI_KEY) throw new Error('🔑 GEMINI_API_KEY no configurada'); if (!ai) { ai = new GoogleGenAI({ apiKey: config.GEMINI_KEY }); log('info', `🧠 Gemini SDK inicializado (${config.GEMINI_MODEL_ID})`); } return ai; } /*───────────────────────────────────────────────────────────────*/ /* 🔍 Construir tools de búsqueda */ /*───────────────────────────────────────────────────────────────*/ function buildSearchTools() { const model = config.GEMINI_MODEL_ID; // Los objetos literales cumplen con el esquema Tool del SDK. if (/^gemini-2\./.test(model) || /^gemini-2\.5/.test(model)) { return [{ google_search: {} }]; // Search‑as‑a‑tool } if (/^gemini-1\.5/.test(model)) { return [{ google_search_retrieval: { dynamic_retrieval_config: { mode: 'MODE_DYNAMIC', dynamic_threshold: 0.3, }, }, }]; } return []; } /*───────────────────────────────────────────────────────────────*/ /* 🚀 askGemini */ /*───────────────────────────────────────────────────────────────*/ export async function askGemini(historial, files = {}) { try { const client = initGemini(); // 1️⃣ Construir "contents" let contents; if (typeof historial === 'string') { contents = historial; } else if (Array.isArray(historial)) { const parts = []; for (const m of historial) { if (m.type === 'document') continue; if (m.type === 'chat') { parts.push(`${m.senderName}: ${m.text} -- ${m.date}`); continue; } const up = files[m.msgId?.toLowerCase?.()]; if (up?.uri) { parts.push( createPartFromUri(up.uri, up.mimeType), `archivo ${m.type} de ${m.senderName}: ${m.caption || m.text} -- ${m.date}` ); } } contents = createUserContent(parts); } else { throw new Error('Formato de historial no soportado'); } // 2️⃣ Herramientas const tools = buildSearchTools(); // 3️⃣ Llamar al modelo const response = await client.models.generateContent({ model: config.GEMINI_MODEL_ID, contents, config: { systemInstruction: SYSTEM_PROMPT, maxOutputTokens: 4096, temperature: 0.3, // menor → mayor factualidad tools, response_modalities: ['TEXT'], }, }); // 4️⃣ Log de grounding const candidate = response?.candidates?.[0]; if (candidate?.groundingMetadata) { log('info', '🔗 GroundingMetadata presente:', JSON.stringify(candidate.groundingMetadata.webSearchQueries)); } else { log('warn', 'ℹ️ Sin groundingMetadata en la respuesta'); } if (!candidate) return '⚠️ Sin candidato.'; return candidate.content.parts.map(p => p.text).join('').trim() || '⚠️ Respuesta vacía.'; } catch (e) { log('error', 'Gemini falló:', e.message); if (e.response?.status === 429) return '🚦 Límite alcanzado. Probá más tarde.'; return '⚠️ No se pudo obtener respuesta de Gemini.'; } }