Production projectlive · adios.hr
A production AI-powered news platform. Ingests RSS feeds from major publishers, runs every article through an LLM analysis and vector-embedding pipeline, and clusters cross-source coverage of the same event. Co-built with a colleague; live at adios.hr, with a multilingual, multi-tenant architecture designed to spin up additional countries by changing a single config flag.
The product is the kind of news experience you'd want for yourself: open one site, see the day's stories grouped by event rather than by publisher, with a short editorial summary written in your language, signals for whether the headline is clickbait or vague, and a "background" panel for anything missing context. Behind the scenes, every article from every source goes through the same pipeline before it ever reaches a reader.
RSS feeds from major publishers come in continuously. Each new article is sent to an LLM for a structured editorial pass, embedded into a vector representation, compared against recent stories from other outlets to figure out which ones are covering the same event, and then surfaced through the reader app via theme-based search. Five separate hosted services run the stages in sequence with bounded channels and per-row locks, so we never double-process an article across replicas.
Each article is sent to OpenAI with a custom prompt that returns strict JSON: an editorial summary, a normalised title (cleaning up the publisher's clickbait headline), an additional-context paragraph for stories that need background, an SEO title and description, a topic set, a tag set, a named-entity set, an affected-country set, and four perceptual scores — clickbait, vagueness, typo, and emotional-language. The prompt is engineered for strict schema compliance and tuned for editorial voice: lightly witty on soft news, sober on hard news, no "according to the source" boilerplate.
The prompt template is language-agnostic. Per-country config injects placeholders for the active language and the local country, so the model never sees the "wrong" language for the deployment. Adding a new country's prompt is one config entry plus a translation pass on the editorial-voice phrasing — the analysis logic itself doesn't change.
Each article is embedded into four separate 1,536-dimension vectors: one for the normalised title, one for a packed semantic-metadata blob, one for the raw publisher title, and one for a sorted-words signature that catches near-duplicates with reordered phrasing. A weighted blend — 0.45 / 0.25 / 0.15 / 0.10 — is combined with text-similarity, shared-tag, and theme-overlap signals. Anything above an 80% combined score is clustered as the same event across sources.
"Embed it four ways" beats "embed it once well" — different vectors catch different kinds of similarity, and the blend filters out the false positives any single one would produce.
Reader queries are embedded and matched against article themes via cosine similarity. The system surfaces the top 40 themes per query out of a 500-article candidate pool — fast enough to feel live, narrow enough to stay relevant.
The five hosted services — RSS crawl, AI analysis, vectorisation, similarity clustering, theme/trending refresh — communicate via bounded channels. Per-row processing locks prevent double-work across replicas; transient failures retry with exponential backoff; clustering requeues an article gracefully when its vectors aren't ready yet. The pipeline keeps moving even when one stage stalls.
A per-country signal dictionary — place names in the active language plus a glossary of foreign-country names also in the active language — decides whether a story is local or international before it reaches the reader. "Italian" looks different in different languages, so the classifier respects whichever language the deployment is running in.
One country equals one deployment equals one database, gated by a single config switch. Adding a new country is roughly a 60-line config file plus a translation resource file plus two dictionary entries — zero code changes to the analysis pipeline. Around 1,500 resource keys are translated per language, covering identity, email, SEO, and AI output. The same model and embedding stack runs everywhere; only the prompt placeholders, the country signal dictionaries, and the translation bundle differ.
Vectors / article
4× 1,536-dim
Resource keys
~1,500per language
Add a country
~60config lines
Live in production at adios.hr. Processing real RSS feeds from major Croatian publishers — 24sata, Index, Slobodna Dalmacija — with continuous LLM annotation, vectorisation, and clustering running 24/7. Architected so the next country is a config change, not an engineering project.
On the roadmap: more publishers per country, an eval harness for the editorial-voice prompts, and a tighter feedback loop from reader engagement back into the clickbait/vagueness scoring.
Happy to share screen on the AI pipeline, multi-tenant design, or the clustering math.
[email protected] →