LLM provider failover, failover notification, and an assessment-coverage alarm - #718
Open
mircealungu wants to merge 2 commits into
Open
LLM provider failover, failover notification, and an assessment-coverage alarm#718mircealungu wants to merge 2 commits into
mircealungu wants to merge 2 commits into
Conversation
Answering "if we run out of DeepSeek credit, are we back on Anthropic?" — we
were not. _select_provider_and_key switched provider only when the API KEY was
MISSING. A provider that has a key and refuses to serve produced a plain
Exception, nothing caught it, and the article was left unassessed. Exactly the
Anthropic-cap failure, re-armed on the other side the moment the crawl moved to
DeepSeek.
Three parts:
1. Failover. ProviderUnavailable marks "this provider cannot serve us" — out of
credit, capped, rate limited, key rejected, down — and one call is retried on
the other provider. Content failures (paywall, advertorial, wrong language)
are facts about the ARTICLE and are deliberately NOT retried elsewhere; that
would spend twice for the same answer.
Status codes alone are not enough: Anthropic reports its spend cap as a 400
invalid_request_error, so the body is matched too. The exact production
strings are in the tests.
2. Notification, once per process. A dead provider fails over on every article
and the crawl handles ~2000/day, so per-event mail would be 2000 messages and
would train us to filter the alert away. The crawler is a fresh process per
hourly run, so a sustained outage sends about one an hour. Sent only AFTER the
fallback call succeeds — a failover that rescued nothing is not a rescue — and
wrapped so a broken mailer can never turn a successfully assessed article into
a failure.
3. Assessment-coverage check in feed_delivery_health_check. Failover degrades two
providers to one; this is what says so when both are gone. The delivery checks
are a lagging indicator — the August outage ran a week before anyone noticed —
so this watches the upstream step: of articles crawled in the last 24h, how
many got a CEFR level?
The 80% threshold is measured, not guessed:
healthy Aug 15-19: 97, 97, 98, 98, 98 %
capped Aug 12, 13, 20: 15, 38, 37 %
Every healthy day and every broken day is replayed in the tests. Below 50
eligible articles it reports without alerting; "almost nothing was crawled" is
crawler_liveness_check's job, and firing on a quiet window would teach us to
ignore this one.
Not wired into cron here — the crontab lives in the ops repo. The existing
feed-delivery entry picks this up automatically, since the check runs inside it.
80% was derived from healthy days measured under Anthropic + the bare-word prompt, which under-enforced the prompt's own "fewer than 3 paragraphs = likely incomplete" rule: only 2-3% of articles were rejected as paywalled, so coverage sat at 97-98%. DeepSeek with the field-based prompt follows that rule literally. The 29 Aug Danish backfill rejected 118 of 629 (19%) — correctly: the rejected set averages 190 words and 1,043 characters against 598 words and 3,246 characters for the assessed set. That is what a paywall stub looks like, not a false positive. So healthy coverage is now ~81%, and an 80% threshold would have paged on a perfectly good day. 70% keeps a real margin under the new baseline while staying far above every capped day (15-38%), which is what the check exists to catch. An alarm that fires on normal operation is worse than no alarm.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Follow-up to #717, from two questions: "does the fallback work now — if we run out of DeepSeek credit will we be back on Anthropic?" and "we need a check for crawled-but-unassessed too".
The fallback did not work
_select_provider_and_keyswitched providers only when the API key was missing. A provider that has a key and refuses to serve — out of credit, over a spend cap, rate limited — raised a plainExceptionthat nothing caught, and the article was left unassessed.That is precisely the Anthropic-cap failure that starved Danish for a week, and moving the crawl to DeepSeek re-armed it on the other side: running out of DeepSeek credit would look identical from the outside (articles crawled, none assessed, nothing in the logs but a per-article error).
1. Failover
ProviderUnavailablemarks the provider cannot serve us, and one call is retried on the other provider. Content failures — paywall, advertorial, wrong language — are facts about the article and are deliberately not retried elsewhere; that would spend twice for the same answer.Status codes alone are insufficient. Anthropic reports its monthly cap as a 400
invalid_request_error, not a quota status, so the body is matched as well. The exact production strings for both providers are pinned in the tests.2. Notification, once per process
A dead provider fails over on every article, and the crawl handles ~2,000/day. Per-event mail would be 2,000 messages and would train us to filter the alert away. The crawler is a fresh process per hourly run, so a sustained outage sends roughly one mail an hour — enough to notice, few enough to read.
Two details that matter:
3. Assessment-coverage alarm
Failover degrades two providers to one. This is what says so when both are gone.
The existing delivery checks are a lagging indicator — the August outage ran a week before anyone noticed — so this watches the upstream step directly: of the articles crawled in the last 24h, how many got a CEFR level?
The threshold is measured, not guessed:
80% sits far below every healthy day and far above every broken one. Every one of those days is replayed through the real classifier in the tests, including the Aug 14 partial-recovery day (83%) which must not alert.
Below 50 eligible articles it reports without alerting: "almost nothing was crawled" is
crawler_liveness_check's job, and firing on a quiet window would teach us to ignore this one.A failing report is actionable rather than just red:
Notes
490 tests pass.