Blindagem global de IA de texto com fallback triplo (MiniMax -> Groq -> Gemini) para todo o aplicativo

This commit is contained in:
2026-08-31 14:51:27 +00:00
parent e994ac1c40
commit 93fc6a1123
+104 -25
View File
@@ -28,34 +28,113 @@ async function callMinimax({ messages, temperature = 0.5, max_tokens = 1500, sys
? [{ role: 'system', content: system }, ...messages]
: messages;
const res = await fetch(`${baseUrl}/anthropic/v1/messages`, {
method: 'POST',
headers: {
'X-Api-Key': process.env.MINIMAX_API_KEY,
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: chosenModel,
max_tokens,
temperature,
messages: finalMessages
})
});
// Tentativa 1: MiniMax M3 com timeout de 45s
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 45000);
if (!res.ok) {
const txt = await res.text();
throw new Error(`MiniMax respondeu ${res.status}: ${txt}`);
const res = await fetch(`${baseUrl}/anthropic/v1/messages`, {
method: 'POST',
headers: {
'X-Api-Key': process.env.MINIMAX_API_KEY,
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: chosenModel,
max_tokens,
temperature,
messages: finalMessages
}),
signal: controller.signal
});
clearTimeout(timeoutId);
if (res.ok) {
const data = await res.json();
const text = (data.content || [])
.filter(b => b.type === 'text')
.map(b => b.text)
.join('')
.trim();
if (text) return { text, raw: data, provider: 'minimax' };
} else {
const txt = await res.text();
console.warn(`[AI Text] MiniMax respondeu ${res.status}: ${txt}. Acionando fallback...`);
}
} catch (err) {
console.warn('[AI Text] Falha/Timeout no MiniMax (' + err.message + '). Acionando fallback...');
}
const data = await res.json();
// Extrai só os blocos de texto (ignora thinking blocks)
const text = (data.content || [])
.filter(b => b.type === 'text')
.map(b => b.text)
.join('')
.trim();
return { text, raw: data };
// Fallback 1: Groq (Llama 3.3 70b / Llama 3.1 8b)
if (process.env.GROQ_API_KEY) {
try {
console.log('[AI Text Fallback] Acionando Groq...');
const groqMessages = system ? [{ role: 'system', content: system }, ...messages] : messages;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'llama-3.3-70b-versatile',
messages: groqMessages,
temperature,
max_tokens
}),
signal: controller.signal
});
clearTimeout(timeoutId);
if (res.ok) {
const data = await res.json();
const text = data.choices?.[0]?.message?.content?.trim();
if (text) return { text, raw: data, provider: 'groq' };
}
} catch (groqErr) {
console.warn('[AI Text Fallback] Falha no Groq:', groqErr.message);
}
}
// Fallback 2: Google AI Gemini
if (process.env.GOOGLE_AI_API_KEY) {
try {
console.log('[AI Text Fallback] Acionando Google AI Gemini...');
const geminiModel = process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash';
const promptText = (system ? `System: ${system}\n\n` : '') + messages.map(m => `${m.role}: ${m.content}`).join('\n');
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${geminiModel}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: promptText }] }],
generationConfig: { temperature, maxOutputTokens: max_tokens }
}),
signal: controller.signal
});
clearTimeout(timeoutId);
if (res.ok) {
const data = await res.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim();
if (text) return { text, raw: data, provider: 'gemini' };
}
} catch (gemErr) {
console.warn('[AI Text Fallback] Falha no Gemini:', gemErr.message);
}
}
throw new Error('Todas as IAs de geração de texto estão indisponíveis no momento. Por favor, tente novamente.');
}
app.use(express.json({ limit: '50mb' }));