fix(gemini): usar modelos flash de alta capacidad y fallback ante limite de cuota
This commit is contained in:
parent
8d526bea18
commit
1a56049a43
@ -18,15 +18,49 @@ export interface ProcessedPublication {
|
||||
image_selection_reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback parser in case Gemini API is rate-limited (429) or unavailable
|
||||
*/
|
||||
function fallbackRuleBasedParser(text: string, mediaCount: number): ProcessedPublication {
|
||||
const clean = (text || '').trim();
|
||||
const lines = clean.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
|
||||
let rawTitle = lines[0] || 'Nueva Publicación';
|
||||
rawTitle = rawTitle.replace(/^[#*_\-•]+\s*/, '').replace(/[*_#]/g, '').trim();
|
||||
if (rawTitle.length > 70) {
|
||||
rawTitle = rawTitle.substring(0, 67) + '...';
|
||||
}
|
||||
|
||||
const lower = clean.toLowerCase();
|
||||
let tipo: 'noticia' | 'evento' | 'aviso' = 'noticia';
|
||||
if (lower.includes('evento') || lower.includes('invitación') || lower.includes('invitacion') || lower.includes('jornada') || lower.includes('taller') || lower.includes('festival') || lower.includes('feria') || lower.includes('convocatoria')) {
|
||||
tipo = 'evento';
|
||||
} else if (lower.includes('se vende') || lower.includes('se arrienda') || lower.includes('clasificado') || lower.includes('empleo') || lower.includes('vacante') || lower.includes('servicio')) {
|
||||
tipo = 'aviso';
|
||||
}
|
||||
|
||||
const seo = clean.replace(/\n+/g, ' ').substring(0, 150) || rawTitle;
|
||||
|
||||
// Format content as clean HTML paragraphs
|
||||
const paragraphs = lines.map(l => `<p>${l}</p>`).join('');
|
||||
const contenido = paragraphs || `<p>${clean || rawTitle}</p>`;
|
||||
|
||||
return {
|
||||
titulo: rawTitle,
|
||||
tipo: tipo,
|
||||
seo_descripcion: seo,
|
||||
contenido: contenido,
|
||||
fecha_vencimiento: null,
|
||||
selected_image_index: mediaCount > 0 ? 0 : undefined,
|
||||
image_selection_reason: mediaCount > 0 ? 'Foto destacada principal' : undefined
|
||||
};
|
||||
}
|
||||
|
||||
export async function processWhatsAppMessage(
|
||||
text: string,
|
||||
mediaParam?: string | MediaInput[] | MediaInput,
|
||||
mimeTypeParam?: string
|
||||
): Promise<ProcessedPublication> {
|
||||
if (!process.env.GEMINI_API_KEY) {
|
||||
throw new Error('No GEMINI_API_KEY configured');
|
||||
}
|
||||
|
||||
// Normalize mediaParam into an array of MediaInput
|
||||
let mediaList: MediaInput[] = [];
|
||||
if (Array.isArray(mediaParam)) {
|
||||
@ -37,6 +71,11 @@ export async function processWhatsAppMessage(
|
||||
mediaList = [mediaParam as MediaInput];
|
||||
}
|
||||
|
||||
if (!process.env.GEMINI_API_KEY) {
|
||||
console.warn('[Gemini] Sin GEMINI_API_KEY configurada, usando parser de respaldo');
|
||||
return fallbackRuleBasedParser(text, mediaList.length);
|
||||
}
|
||||
|
||||
const systemInstruction = `
|
||||
Eres un asistente editorial experto y editor gráfico de Almeira Web.
|
||||
Analiza los mensajes y recursos multimedia recibidos desde WhatsApp para estructurarlos como publicaciones del portal.
|
||||
@ -68,32 +107,47 @@ export async function processWhatsAppMessage(
|
||||
});
|
||||
});
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model: 'gemini-3.6-flash',
|
||||
contents: contents,
|
||||
config: {
|
||||
systemInstruction: systemInstruction,
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
titulo: { type: Type.STRING },
|
||||
tipo: { type: Type.STRING, enum: ["noticia", "evento", "aviso"] },
|
||||
seo_descripcion: { type: Type.STRING, description: "Máximo 150 caracteres" },
|
||||
contenido: { type: Type.STRING, description: "Contenido en HTML" },
|
||||
fecha_vencimiento: { type: Type.STRING, description: "Fecha opcional YYYY-MM-DD", nullable: true },
|
||||
selected_image_index: { type: Type.INTEGER, description: "Índice base 0 de la mejor imagen seleccionada", nullable: true },
|
||||
image_selection_reason: { type: Type.STRING, description: "Motivo de selección de imagen", nullable: true }
|
||||
},
|
||||
required: ["titulo", "tipo", "seo_descripcion", "contenido"]
|
||||
}
|
||||
}
|
||||
});
|
||||
const candidateModels = [
|
||||
'gemini-2.5-flash',
|
||||
'gemini-2.0-flash',
|
||||
'gemini-1.5-flash',
|
||||
'gemini-2.0-flash-lite'
|
||||
];
|
||||
|
||||
if (!response.text) {
|
||||
throw new Error('Gemini devolvió respuesta vacía');
|
||||
for (const modelName of candidateModels) {
|
||||
try {
|
||||
const response = await ai.models.generateContent({
|
||||
model: modelName,
|
||||
contents: contents,
|
||||
config: {
|
||||
systemInstruction: systemInstruction,
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
titulo: { type: Type.STRING },
|
||||
tipo: { type: Type.STRING, enum: ["noticia", "evento", "aviso"] },
|
||||
seo_descripcion: { type: Type.STRING, description: "Máximo 150 caracteres" },
|
||||
contenido: { type: Type.STRING, description: "Contenido en HTML" },
|
||||
fecha_vencimiento: { type: Type.STRING, description: "Fecha opcional YYYY-MM-DD", nullable: true },
|
||||
selected_image_index: { type: Type.INTEGER, description: "Índice base 0 de la mejor imagen seleccionada", nullable: true },
|
||||
image_selection_reason: { type: Type.STRING, description: "Motivo de selección de imagen", nullable: true }
|
||||
},
|
||||
required: ["titulo", "tipo", "seo_descripcion", "contenido"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (response.text) {
|
||||
const result = JSON.parse(response.text) as ProcessedPublication;
|
||||
return result;
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.warn(`[Gemini] Error con modelo ${modelName}:`, err?.message);
|
||||
}
|
||||
}
|
||||
|
||||
const result = JSON.parse(response.text) as ProcessedPublication;
|
||||
return result;
|
||||
// Si todos los modelos de Gemini superaron cuota (429), usamos el fallback seguro para no fallar el webhook
|
||||
console.warn('[Gemini] Modelos de IA agotados por cuota, activando fallback editorial inteligente');
|
||||
return fallbackRuleBasedParser(text, mediaList.length);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user