Sida

GRÖT Automation

grötfluence.se är en grötblogg. Det kräver inte en sofistikerad teknisk infrastruktur. Eller?

Det som 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 finfjusterad visionsmodell, en stor språkmodell, en Telegram-bot, en Docker-containeriserad FastAPI-tjänst och en dedikerad AI-server. 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. Qwen2.5-VL 3B — finfjusterad på bloggens egna bilder — analyserar bilden och identifierar grötsort och toppings på svenska
  3. Mistral Small 3.1 24B tar beskrivningen och genererar svensk rubrik, bildtext och metadata i ett enda API-anrop
  4. Bilden och inlägget publiceras till Directus via REST API
  5. Boten svarar med den publicerade URL:en

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

Hårdvara

All inferens körs lokalt på en dedikerad maskin med AI-kapacitet. Att hålla beräkningen lokal är ett aktivt val: latensen är förutsägbar, det finns inga kostnader per token, och frukostfoton hör hemma på den egna hårddisken.

Finjustering: att lära en modell läsa gröt

Den generiska Qwen2.5-VL 3B visste vad havregröt är. Den visste däremot inte att det heter kallgröt när det är kallt, att en viss typ av gryn kallas fiberberikad i det här sammanhanget, eller att beskrivningen ska följa ett specifikt format med kategori och tillbehör i rätt ordning. Den kunskapen finns bara i bloggen själv.

Lösningen var att finjustera modellen på bloggens egna bilder. Träningsdata skapades i fem steg:

  1. Skrapning — alla 550+ blogginlägg hämtades med titel, taggar och bild
  2. Augmentering — varje bild utökades till tio varianter (beskärning, ljusjustering, spegling) — 586 bilder blev ~6 400 träningsexempel
  3. Datasetskapande — varje bild parades ihop med en träningsfras i Qwen-chattformat: "Analysera bilden..." → "Detta är havregröt med lingonsylt och banan."
  4. LoRA-träning — fyra timmars träning; bara adapter-vikterna uppdaterades, inte hela modellen
  5. Export — adaptern mergades in i basmodellen och kvantiserades till GGUF Q4_K_M för effektiv inferens via llama.cpp

Resultatet är en modell som känner igen vokabulär och svarar i rätt format utan ytterligare instruktioner. Exempelutdata:

"Detta är havregröt (variant: kallgröt, lingon) med fiberberikad, frukost och lingon."

Visionsmodell: Qwen2.5-VL 3B (finfjusterad)

Qwen2.5-VL 3B (Q4_K_M, ~1.8 GiB) är en multimodal modell som körs via llama.cpp med ROCm HIP-backend. Eftersom llama.cpp:s bildavkodare inte stöder WebP körs ett tunt proxylager som konverterar inkommande bilder till JPEG innan de vidarebefordras — Telegram skickar foton i JPEG, så i praktiken är det sällan relevant, men det är bra att ha.

Modellen genererar ~77 token/sekund med en svarstid på ungefär 725 ms per anrop. Uppgiften är smal och specifik: identifiera grötsorten och toppings och returnera resultatet i bloggens eget format. Finjusteringen innebär att modellen svarar på svenska och känner till taxonomin utan att behöva instrueras om det i prompten.

POST /v1/chat/completions
{
  "model": "qwen2.5vl:3b-gpu",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Analysera bilden och beskriv exakt vilken grötsort och vilka toppings/tillbehör som syns." },
      { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,<base64>" } }
    ]
  }]
}

Språkmodell: Mistral Small 3.1 24B

Mistral Small 3.1 24B (Q4_K_M, ~13.4 GiB) körs via llama.cpp med ett kontextfönster på 131 072 token — väl tilltaget för en grötbeskrivning. Genereringshastigheten är ~14 token/sekund, vilket är acceptabelt för ett icke-interaktivt flöde.

Den svenska bildbeskrivningen skickas som kontext och ett enda API-anrop returnerar ett JSON-objekt med rubrik, bildtext, kategori och taggar. Systempromten är det som faktiskt gör jobbet: grammatiskt korrekt svenska är trivialt för en modell i den här storleksklassen — problemet är ton. Bloggens röst är torr, måttligt absurd och skriven på naturlig talad svenska. Prompten innehåller explicita stilexempel och explicita förbud (inga hashtags, inga emojis, ingen uppmuntrande ton) och instruerar modellen att använda svenska idiom snarare än direktöversättningar från engelska.

Att dela upp pipelinen i två modeller är ett medvetet val. Qwen2.5-VL 3B är kompakt och snabb på visuell förståelse, finjusterad för just den här domänen. Mistral Small 3.1 24B är bättre på nyanserad textgenerering på andra språk än engelska. Varje modell gör det den faktiskt är bra på.

Applikationen: FastAPI i Docker

Orkestreringslagret är en FastAPI-applikation som körs i Docker på en separat server. Python, httpx för all utgående HTTP.

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

Directus-integration

Publicering sker via Directus REST API. Bilden laddas upp till Directus filhantering och inlägget skapas med titel, brödtext, kategori och taggar i ett enda anrop. Kategorier och taggar skapas automatiskt om de inte redan finns — taxonomin bygger upp sig själv över tid.

Källkod

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

Det är en grötblogg. Tekniken är oproportionerlig. Det är lugnt, jag har lärt mig allt om Overkill från Lemmy.

© 2026