210a2d15a9
Changed API fallback chain: - Tier 1: Ollama (local, free, always available) - Tier 2: Gemini (free tier with quota limits) - Tier 3: OpenRouter (cheap, flexible fallback) - Tier 4: OpenCode (final backup) This saves costs by using local Ollama for most tasks. OpenRouter only used when Ollama unavailable.
150 lines
4.8 KiB
JavaScript
150 lines
4.8 KiB
JavaScript
/**
|
|
* AI API Fallback System
|
|
* Tries: Ollama (local) → Gemini → OpenRouter → OpenCode
|
|
*/
|
|
|
|
const fetch = require('node-fetch');
|
|
|
|
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
|
|
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'deepseek-v3.2:cloud';
|
|
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY;
|
|
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
|
|
const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1';
|
|
const OPENCODE_API_KEY = process.env.OPENCODE_API_KEY;
|
|
const OPENCODE_BASE_URL = process.env.OPENCODE_BASE_URL || 'https://api.opencode.com/v1';
|
|
|
|
async function generateWithFallback(prompt, options = {}) {
|
|
console.log('🤖 Generating content...');
|
|
|
|
// Tier 1: Try Ollama (local, free)
|
|
try {
|
|
console.log(`📍 Tier 1: Attempting Ollama (${OLLAMA_MODEL})...`);
|
|
const response = await fetch(`${OLLAMA_URL}/api/generate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
timeout: 30000,
|
|
body: JSON.stringify({
|
|
model: OLLAMA_MODEL,
|
|
prompt: prompt,
|
|
stream: false,
|
|
temperature: options.temperature || 0.7
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
console.log('✅ Ollama success');
|
|
return { success: true, provider: 'ollama', data };
|
|
}
|
|
|
|
console.warn(`⚠️ Ollama error: ${response.status}, trying next...`);
|
|
} catch (err) {
|
|
console.warn(`Ollama failed: ${err.message}`);
|
|
}
|
|
|
|
// Tier 2: Try Gemini
|
|
if (GEMINI_API_KEY) {
|
|
try {
|
|
console.log('📍 Tier 2: Attempting Gemini API...');
|
|
const response = await fetch(
|
|
`https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=${GEMINI_API_KEY}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: prompt }] }],
|
|
generationConfig: options.config || {}
|
|
})
|
|
}
|
|
);
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
console.log('✅ Gemini API success');
|
|
return { success: true, provider: 'gemini', data };
|
|
}
|
|
|
|
if (response.status === 429 || response.status === 403) {
|
|
console.warn('⚠️ Gemini quota exceeded, trying next...');
|
|
} else {
|
|
throw new Error(`Gemini error: ${response.status}`);
|
|
}
|
|
} catch (err) {
|
|
console.warn(`Gemini failed: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
// Tier 3: Fallback to OpenRouter
|
|
if (OPENROUTER_API_KEY) {
|
|
try {
|
|
console.log('📍 Tier 3: Attempting OpenRouter API...');
|
|
const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
'HTTP-Referer': 'https://gravl.app'
|
|
},
|
|
body: JSON.stringify({
|
|
model: options.model || 'openai/gpt-4',
|
|
messages: [{ role: 'user', content: prompt }],
|
|
temperature: options.temperature || 0.7,
|
|
max_tokens: options.maxTokens || 2048
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
console.log('✅ OpenRouter API success');
|
|
return { success: true, provider: 'openrouter', data };
|
|
}
|
|
|
|
console.warn(`OpenRouter error: ${response.status}, trying next...`);
|
|
} catch (err) {
|
|
console.warn(`OpenRouter failed: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
// Tier 4: Final fallback to OpenCode
|
|
if (OPENCODE_API_KEY) {
|
|
try {
|
|
console.log('📍 Tier 4: Attempting OpenCode API...');
|
|
const response = await fetch(`${OPENCODE_BASE_URL}/chat/completions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${OPENCODE_API_KEY}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
model: options.model || 'gpt-4',
|
|
messages: [{ role: 'user', content: prompt }],
|
|
temperature: options.temperature || 0.7,
|
|
max_tokens: options.maxTokens || 2048
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
console.log('✅ OpenCode API success');
|
|
return { success: true, provider: 'opencode', data };
|
|
}
|
|
|
|
throw new Error(`OpenCode error: ${response.status}`);
|
|
} catch (err) {
|
|
console.error(`OpenCode failed: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
throw new Error('All generation APIs failed (Ollama → Gemini → OpenRouter → OpenCode)');
|
|
}
|
|
|
|
module.exports = {
|
|
generateWithFallback,
|
|
getAvailableProviders: () => ({
|
|
ollama: true, // Always available locally
|
|
gemini: !!GEMINI_API_KEY,
|
|
openrouter: !!OPENROUTER_API_KEY,
|
|
opencode: !!OPENCODE_API_KEY
|
|
})
|
|
};
|