GRÖT Automation

grötfluence.se är en grötblogg. Det är kanske inte är något som kräver en sofistikerad teknisk infrastruktur. Eller?

Det  började som ett manuellt arbetsflöde — fotografera skålen, öppna appen, skriva bildtexten, välja taggar — har automatiserats till en pipeline som involverar en Raspberry Pi 5, en dedikerad AI-accelerator, två språkmodeller, Anthropics API, en Telegram-bot, och en Docker-containeriserad FastAPI-tjänst. Grötens kvalitet har inte förbättrats. Tekniken är rimlig.

Arkitektur

Den fullständiga pipelinen aktiveras genom att skicka ett foto till en Telegram-bot:

  1. Telegram levererar fotot till en webhook-endpoint
  2. En visionsmodell som körs lokalt på en Raspberry Pi 5 genererar en engelsk beskrivning av bilden
  3. Claude Haiku genererar en svensk rubrik, bildtext och metadata i ett enda API-anrop
  4. Bilden och inlägget publiceras till WordPress via REST API
  5. Boten svarar med den publicerade URL:en

Från början till slut tar processen ungefär 60–90 sekunder. Det mesta av det är visionsmodellen.

Hårdvara: Raspberry Pi 5 med Hailo-10H

Visionsinferensen körs på en Raspberry Pi 5 (8 GB) utrustad med ett AI HAT+ 2, som har en Hailo-10H neural processorenhet ansluten via PCIe. Hailo-10H levererar 10 TOPS.

Pi:n kör Debian trixie (aarch64) och hanteras på distans via Tailscale, vilket innebär att den är nåbar via värdnamn från var som helst i mitt skapligt komplexa nätverk utan port-forwarding eller krånglig VPN-konfiguration.

Att hålla beräkningen lokal spelar roll av några anledningar: latensen för visionssteget är förutsägbar, det finns inga kostnader per token, och att skicka frukostfoton till ett moln-API känns onödigt när Pi:n står i hyllan.

Lokal inferens: Ollama och llava-phi3

Visionsmodellen är llava-phi3, en multimodal modell med 4 miljarder parametrar som körs under Ollama. Ollama hanterar modelladdning, kvanisering (Q4_K_M) och exponerar ett enkelt HTTP API.

Modellen är konfigurerad med OLLAMA_KEEP_ALIVE=-1, vilket innebär att den stannar i RAM på obestämd tid. Den första inferensen efter en kallstart är den enda långsamma.

Uppgiften som ges till visionsmodellen är avsiktligt smal: beskriv maten i bilden objektivt — utseende, färger, toppings, konsistens. Ingen svenska, inget humor, ingen tolkning. Bara en saklig engelsk beskrivning som nästa steg kan arbeta utifrån.

POST /api/generate
{
  "model": "llava-phi3",
  "stream": false,
  "prompt": "Describe what you see in this image. Focus on the food...",
  "images": ["<base64>"]
}

Relation till RAG

Det här är ingen RAG-implementation i teknisk bemärkelse — det finns ingen vektordatabas, inga embeddings, ingen semantic search. Men arkitekturellt är logiken densamma: visionsmodellen agerar retriever och hämtar faktainformation ur bilden, som sedan injiceras som kontext i Claude-prompten. Claude genererar utifrån den hämtade informationen snarare än från träningsdata. Retrieval-steget är visuellt istället för textbaserat.

Språk och stil: Claude Haiku

Den engelska beskrivningen skickas till Claude Haiku (claude-haiku-4-5) via Anthropic API. Ett enda anrop returnerar ett JSON-objekt med fyra fält: en kort svensk rubrik, en en-till-två meningar lång bildtext i rösten av en erfaren grötbloggare, en måltidskategori baserad på aktuell tid på dygnet, och en lista med ingredienstaggar.

Systempromten gör ett meningsfullt arbete här. Att generera grammatiskt korrekt svenska är trivialt för en modell av den här storleken — det svårare problemet är ton. Bloggens röst är torr, orimligt rolig och skriven på naturlig talad svenska snarare än översatt engelska. Prompten innehåller explicita exempel och förbud (inga hashtags, ingen uppmuntran, inga emojis) och instruerar modellen att använda svenska idiom snarare än direktöversättningar.

Att dela upp pipelinen i två modeller är ett medvetet val. llava-phi3 är bra på visuell beskrivning och körs lokalt gratis. Claude är bra på nyanserad språkgenerering på andra språk än engelska. Varje modell gör det den faktiskt är bra på. Sen är det rätt fett att köra en del av den på Raspberry som sagt.

Några ytterligare signaler förbättrar resultatet:

  • Aktuell Stockholmstid skickas med så att modellen kan härleda måltidskontext (frukost, lunch, middag, kvällsgröt)
  • Eventuell textbilaga i Telegram-fotot skickas med som ingredienstips
  • Modellen instrueras att som standard anta havregröt, lägga till blåbär för blå toner och lingon för röda

Applikationen: FastAPI i Docker

Orkestreringslagret är en FastAPI-applikation som körs i Docker på en Debian-server. Python 3.11, httpx för all utgående HTTP, Anthropic SDK för Claude-anropet.

Telegram webhook-endpointen returnerar HTTP 200 omedelbart och skickar pipelinen som en FastAPI BackgroundTask. Det är viktigt eftersom Telegram kräver ett svar inom 60 sekunder och den fullständiga pipelinen tar längre tid än så.

Appen exponerar också ett minimalt webb-UI för manuell bildtextgenerering — den ursprungliga funktionaliteten innan Telegram-automatiseringen lades till. Detta skyddas av HTTP Basic Auth. Webhookens säkerhet hanteras med Telegrams inbyggda secret_token-mekanism: en delad hemlighet registreras när webhookens skapas och Telegram inkluderar den som X-Telegram-Bot-Api-Secret-Token i varje förfrågan.

Docker Compose hanterar miljövariabelinjicering och extra_hosts-posten som mappar Pi:ns Tailscale-värdnamn till dess IP, så att containern kan nå Ollama vid namn.

WordPress-integration

Publicering använder WordPress REST API med Application Password-autentisering. Två anrop görs: ett för att ladda upp bilden till mediebiblioteket och ett för att skapa inlägget med det bifogade bilden, titeln, innehållet, kategorin och taggarna.

Kategorier och taggar löses upp efter namn — om en term inte finns ännu skapas den automatiskt, så taxonomin bygger upp sig själv över tid utan manuell konfiguration.

En sak värd att notera: WordPress REST API kräver att snygga permalänkar är aktiverade. Standard-permalänkstrukturen (?p=123) inaktiverar REST API-routingen helt. Det är inte särskilt väldokumenterat.

Källkod

Den fullständiga källkoden finns på GitHub: martindebruin/grotfluence-image-describer.

Det är en grötblogg. Tekniken är möjligen något oproportionerlig. Det är lugnt. Jag lärde mig allt om Overkill av Lemmy.