Skip to the content.

Context

An LLM call sits inside a pipeline that is supposed to leave something behind: a level and a summary on every new article, a lesson script in the learner’s language, a verdict on every translation. The pipeline is monitored the usual way, by watching the calls: errors, timeouts, failover events, latency. The things it leaves behind are read later, by users or by other features, and nobody reads all of them.

Example

Zeeguu’s crawler assesses every new article with one LLM call, which gives it a CEFR level and a summary per level (crawling). In August 2026 Anthropic’s monthly spend cap stopped that call. The errors were in the crawler’s logs, but they looked like the occasional failed call that logs always contain, and the crawl itself kept running and saving articles. Over the following week the share of new articles that got assessed went 0, 0, 15, 38, 37, 4 and 4%, against 95 to 99% on a normal day. The 664 most recent Danish articles went without a level or a summary, and the feed, which needs the level to place an article, quietly had less to offer.

The fix for the cause was provider failover (One Wish per Genie), with an email the first time a run fails over. The fix for the symptom was a separate check that ignores the LLM calls entirely and asks the database what the crawler should have produced: of the original articles crawled in the last 24 hours, how many have a level? It runs from cron, outside the crawler, every six hours, and alarms below 80%, overall and per language, with a minimum volume so a quiet language on a quiet day does not trip it (feed_delivery_health_check.py). Replayed over August, it would have fired on the first day.

Zeeguu had already learned the same lesson once. On 18 August 2026 the check that verifies a summary is written in the article’s language was passing on every call, while the prompt asked for the wrong language on every first attempt; 55 English summaries reached storage before anyone looked. A nightly audit now reads stored summaries back and checks their language, independently of generation. Its crontab entry states the reason: a check that runs inside the thing it is checking cannot catch the case where that thing was never called.

Problem

How do you find out that an LLM-backed pipeline has stopped doing its job, when each call either succeeded, failed in a way that looked routine, or was never made?

Forces

Solution

State, in domain terms, what the pipeline should have produced over a window, and check the stored data against that statement from outside the pipeline. Phrase the expectation as a ratio of what exists to what should exist (“assessed originals / crawled originals in 24 hours”), so it is independent of how many calls were made, which provider served them, or whether any were made at all. Run the check on its own schedule, not as a step of the job it watches, and send its result to a place a person reads.

Calibrate the threshold on recorded healthy days and on a recorded failure, if there is one, and give it a minimum volume, so that small numbers do not fire it. Count intended exclusions back in, or out, explicitly: an article the LLM rejected as paywalled was handled, not missed.

Keep the call-level monitoring as well. The two answer different questions: the outcome check says that something is wrong and how much, the call-level counts say which genie and why.

Consequences

Known Uses

We have not found a documented LLM system that monitors the domain output of its LLM calls this way rather than the calls themselves; the LLM-specific part (partial, plausible failure, and checks that share the prompt’s assumptions) is grounded in Zeeguu.

Notes


💬 Open an issue about this pattern