Blindagem global de IA de texto com fallback triplo (MiniMax -> Groq -> Gemini) para todo o aplicativo
This commit is contained in:
@@ -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' }));
|
||||
|
||||
Reference in New Issue
Block a user