fixes: dead /contact link + remove paid-AI fallbacks from hybridRouter
- /manufacturers footer "Contact our editors" linked to /contact which doesn't exist → 404. Swapped to mailto:editors@broadcastbeat.com. - hybridAI() was falling through to api.anthropic.com whenever Ollama failed, which logs were tripping on after my earlier no-paid-AI swap (Article suggestions error: ANTHROPIC_API_KEY not configured). Removed both Anthropic call-paths in hybridAI + hybridAIStream. hybridAI now retries Ollama with 0/3/12s backoff and throws a clear error after exhaustion — callers (article-suggestions) already have graceful non-AI fallbacks.
This commit is contained in:
@@ -82,9 +82,9 @@ export default async function ManufacturersIndex() {
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
Are you a manufacturer and want to update your listing?{" "}
|
||||
<Link href="/contact" className="text-[#3b82f6] hover:underline">
|
||||
<a href="mailto:editors@broadcastbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#3b82f6] hover:underline">
|
||||
Contact our editors
|
||||
</Link>
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -197,12 +197,17 @@ export async function hybridAI(
|
||||
const endpoint = getOllamaEndpoint();
|
||||
const ollamaModel = getOllamaModel(taskType);
|
||||
|
||||
// Skip Ollama for low-priority tasks when no endpoint configured
|
||||
// or when priority is 5-6 (translation/classification) — route direct to Anthropic
|
||||
// to avoid queuing behind interactive sessions
|
||||
const skipOllama = !endpoint || !ollamaModel || priority >= 5;
|
||||
if (!endpoint || !ollamaModel) {
|
||||
throw new Error('Ollama not configured (OLLAMA_ENDPOINT or model missing) — refusing to fall back to paid API');
|
||||
}
|
||||
|
||||
if (!skipOllama) {
|
||||
// Retry-with-backoff against Ollama. Per the no-paid-AI policy we never
|
||||
// fall back to Anthropic, so the caller gets a clear error and is expected
|
||||
// to surface its own graceful fallback (e.g. non-AI default).
|
||||
const RETRY_DELAYS_MS = [0, 3000, 12000];
|
||||
let lastErr: any = null;
|
||||
for (let i = 0; i < RETRY_DELAYS_MS.length; i++) {
|
||||
if (RETRY_DELAYS_MS[i] > 0) await new Promise((r) => setTimeout(r, RETRY_DELAYS_MS[i]));
|
||||
const start = Date.now();
|
||||
try {
|
||||
const text = await callOllama(messages, ollamaModel, endpoint, options);
|
||||
@@ -210,24 +215,18 @@ export async function hybridAI(
|
||||
logHealthEvent({ provider: 'ollama', success: true, latencyMs, model: ollamaModel, priority });
|
||||
return { text, provider: 'ollama', model: ollamaModel, latencyMs };
|
||||
} catch (err: any) {
|
||||
lastErr = err;
|
||||
const latencyMs = Date.now() - start;
|
||||
logHealthEvent({
|
||||
provider: 'ollama', success: false, latencyMs, model: ollamaModel, priority,
|
||||
errorMessage: err?.message, isFailover: true,
|
||||
errorMessage: err?.message,
|
||||
});
|
||||
// Fall through to Anthropic
|
||||
// Retry-eligible if message looks like 503/busy/timeout; otherwise bail.
|
||||
if (!/503|busy|pending|queue|timeout|empty|abort/i.test(String(err?.message || ''))) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Anthropic fallback
|
||||
const start = Date.now();
|
||||
const { text, model } = await callAnthropic(messages, options);
|
||||
const latencyMs = Date.now() - start;
|
||||
logHealthEvent({
|
||||
provider: 'anthropic', success: true, latencyMs, model, priority,
|
||||
isFailover: !skipOllama,
|
||||
});
|
||||
return { text, provider: 'anthropic', model, latencyMs };
|
||||
throw lastErr || new Error('Ollama failed after retries');
|
||||
}
|
||||
|
||||
// ─── Streaming variant (for AI Console) ──────────────────────────────────────
|
||||
@@ -272,55 +271,9 @@ export async function hybridAIStream(
|
||||
}
|
||||
}
|
||||
|
||||
// Anthropic streaming fallback
|
||||
const apiKey = getAnthropicKey();
|
||||
if (!apiKey) throw new Error('No AI provider available');
|
||||
|
||||
const systemMsg = messages.find(m => m.role === 'system');
|
||||
const userMessages = messages.filter(m => m.role !== 'system');
|
||||
|
||||
const model = ANTHROPIC_PRIMARY_MODEL;
|
||||
const payload: any = {
|
||||
model,
|
||||
max_tokens: options.maxTokens ?? 2000,
|
||||
stream: true,
|
||||
messages: userMessages.map(m => ({ role: m.role, content: m.content })),
|
||||
};
|
||||
if (systemMsg) payload.system = systemMsg.content;
|
||||
|
||||
let response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Try fallback model
|
||||
if (response.status === 404) {
|
||||
const fallbackPayload = { ...payload, model: ANTHROPIC_FALLBACK_MODEL };
|
||||
const fallbackResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
body: JSON.stringify(fallbackPayload),
|
||||
});
|
||||
if (!fallbackResponse.ok || !fallbackResponse.body) throw new Error('Anthropic unavailable');
|
||||
logHealthEvent({ provider: 'anthropic', success: true, latencyMs: 0, model: ANTHROPIC_FALLBACK_MODEL, priority, isFailover: true });
|
||||
return { stream: fallbackResponse.body, provider: 'anthropic', model: ANTHROPIC_FALLBACK_MODEL };
|
||||
}
|
||||
throw new Error(`Anthropic HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
if (!response.body) throw new Error('No response body from Anthropic');
|
||||
logHealthEvent({ provider: 'anthropic', success: true, latencyMs: 0, model, priority, isFailover: !!endpoint });
|
||||
return { stream: response.body, provider: 'anthropic', model };
|
||||
// No-paid-AI policy: no Anthropic fallback. If Ollama streaming failed,
|
||||
// surface that clearly so the caller can degrade gracefully.
|
||||
throw new Error('Ollama streaming unavailable — no paid-AI fallback per policy');
|
||||
}
|
||||
|
||||
// ─── Ollama model list ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user