Work/Fact-Checker
A prototype became a working service in five days, at a third of the cost
Made secure first, then opened to other software.
- A third of the running cost
- 3 times faster per claim
- 5 days
- Kind
- My own build
- Built in
- 5 days
- Result
- 70% fewer model calls
In one paragraph
A fact-checking prototype lived in a Jupyter notebook. It accepted any input, with no limit on requests. It paid for the same model call every time it was asked the same question, and only a person at a keyboard could run it. Fact-Checker turned it into a service. It takes text or a link, breaks it into single claims, finds evidence on the web and asks the model for a verdict of true, false or uncertain. Results are kept for 24 hours, so the service makes about 70% fewer model calls. Each claim takes three to five seconds instead of ten to fifteen. In five days, I built the backend and frontend in about 3,500 lines.
The problem I started from
Research code and production code fail differently. The notebook worked, but it had no security controls, no rate limiting, no caching, no API and no error handling. That meant it worked exactly once per run, for one person, at whatever the model charged that day.
The job was to make the existing fact-checking safe to expose, not to improve it.
What I decided
Security before features. Before the service took its first request, it already checked every link against a strict list of allowed sites, accepted five requests a minute, cleaned text on the way in and encoded it on the way out. Adding security to a service that is already running is where incidents come from.
A cache that keeps answers for 24 hours. The same claims come up again and again, and a question asked a day later is still the same question. Redis in front of the model cut its calls by about 70%. Twenty-four hours was where fresh answers and cost balanced out.
A 30-second limit on every outside call. The web search and the model both belong to other companies. If either one hangs, the service stops waiting after 30 seconds instead of hanging with it.
Organized by feature, not by layer. The code is split into four parts: pulling out claims, finding evidence, judging each claim, and the pipeline that joins them. Each part owns its input, its logic and its output. That convention comes from SeedFW, my own framework, and it kept a five-day build easy to find your way around.
A request ID on everything. Each request carries one ID through every line of the logs. When something fails, it can be traced from start to finish without guessing.
What exists
| Flow | Input → claim extraction → evidence retrieval (Parallel Web API) → cache check → evaluation (Cerebras) → verdict |
|---|---|
| Security | Allowlist URL validation, 5 req/min rate limit, sanitization, output encoding, global error middleware, 30 s timeouts |
| Cache | Redis, 24 h TTL, ~70% reduction in model calls |
| Speed | 3 to 5 seconds per claim, from 10 to 15 in the notebook |
| Code | ~2,000 lines Python (FastAPI), ~1,500 lines TypeScript (Next.js, React) |
| Deploy | Docker Compose, one command, health checks |
| Time | Five days |
What I would do differently
Stream the progress. A claim takes seconds, and the user watches a spinner. Server-sent events would show each step as it happens: pulling out claims, finding evidence and judging them.