Negli ultimi giorni sul metaverso (dalla parte di Mastodon almeno) é apparso un bot molto molto interessante. Si chiama @altbot@fuzzies.wtf e - una volta iscritti al canale, risponde ai tuoi post con immagini proponendo una descrizione fatta con intelligenza artificiale molto ben fatta.

L’invito é di compilare manualmente la descrizione dell’immagine (alt tag) con il testo prodotto.

Visto che é un po’ che gioco con ollama mi é arrivata l’occasione di tirare fuori un client Mastodon che faccia esattamente lo stesso lavoro ma che configuri direttamente lui la descrizione senza ulteriori modifiche a mano.

tl;dr: ecco il repo: AlessandroLorenzi/AlessandroLorenzi/mastodon_alt_text

Cosa é l’ALT Tag

L’alt tag é la descrizione dell’immagine dentro l’html.

É molto importante per facilitare a chi é non vedente o ipovedente la fruizione dei contenuti web. La maggior parte dei social commerciali questo lavoro lo fa in automatico, ma dispongono di una potenza di calcolo maggiore e sono interessati anche dalla miglior indicizzazione dei contenuti che l’alt tag porta.

Cosa é Ollama

Ollama é un servizio che permette di far girare i modelli LLM in locale. Ce ne sono tanti disponibili dedicati a varie esigenze.

É anche possibile “chattare” direttamente con ollama tramite riga di comando, ad esempio per utilizzare il modello llava: ollama run llava:7b.

La cosa piú interessante peró é fare chiamate api o utilizzare una libreria per integrare l’intelligenza artificiale nel software che stiamo sviluppando. Con python in questo progetto ho utilizzato la libreria ufficiale.

Architettura

L’idea é un po’ questa:

sequence Diagram

Riporto anche in modalitá testuale per coerenza con l’argomento:

sequenceDiagram
    Servizio->>+Istanza Mastodon: Nuovo Post?
    Istanza Mastodon->>-Servizio: Yep, eccotelo
    Servizio->>+Ollama: Cosa c'é in questa immagine?
    Ollama->>-Servizio: The pen is on the table
    Servizio->>+Ollama: Me lo dici in italiano?
    Ollama->>-Servizio: La penna é sul tavolo
    Servizio->>+Istanza Mastodon: Aggiorna l'alt dell'immagine: "La penna é sul tavolo"

La scelta dei modelli

La parte piú complicata é stata scegliere i modelli per “vedere” l’immagine e poi tradurre.

I modelli vision candidati finali sono:

I modelli per la traduzione invece:

Qualsiasi consiglio su modelli da testare (ma in realtá su tutto) é sempre bene accetto.

Show me the code!

Un paio di “pezzi” principali di codice:

Il loop principale

Al boot il client richiede gli ultimi 10 post, giusto per vedere se mentre era spento (shit happens) sia stato pubblicato qualche articolo.

if __name__ == "__main__":
    log.info("Starting the bot...")
    me = mastodon.me()
    my_toots = mastodon.account_statuses(me["id"], limit=10)

    for toot in my_toots:
        elaborate_toot(toot)

Fatto questo, una volta che abbiamo l’ultimo post pubblicato iniziamo a fare un loop su tutti i post nuovi che arrivano ogni 60 secondi

    last_seen_id = my_toots[0]["id"]
    while True:
        log.debug("Checking for new toots...")
        new_toots = mastodon.account_statuses(me["id"], since_id=last_seen_id)
        for toot in new_toots:
            elaborate_toot(toot)
            last_seen_id = toot["id"]
        time.sleep(60)

La chiamata a ollama

Questa funzione utilizza la libreria di ollama per chiamare il servizio locale. Gli passiamo il modello (llava:7b), il prompt (“describe the image”) e infine il path dell’immagine.

def extract_description(img_path: str) -> str:
    log.debug(f"Extracting description from image {img_path}")
    res = ollama.chat(
        model="llava:7b",
        messages=[
            {
                "role": "user",
                "content": "Describe the image",
                "images": [img_path],
            }
        ],
    )
    return res["message"]["content"]

Download e cancellazione dell’immagine temporanea

Non avevo mai sviluppato una classe che potesse essere usata con with prima d’ora. Ecco qui!

La classe ImageDownloader scarica l’immagine in un file temporaneo all’interno del metodo __enter__, ripassa il controllo alla funzione chiamante e infine elimina l’immagine temporanea nel metodo __exit__.

Ottimo per non lasciare sporcizia in giro.

class ImageDownloader:
    def __init__(self, url: str):
        self.url = url
        self.path = tempfile.mktemp()

    def __enter__(self) -> str:
        log.debug(f"Downloading image from {self.url}")
        response = requests.get(self.url)

        with open(self.path, "wb") as file:
            file.write(response.content)

        return self.path

    def __exit__(self, exc_type, exc_val, exc_tb):
        log.debug(f"Removing image {self.path}")
        os.remove(self.path)

Come si usa poi:

with ImageDownloader("https://example.com/example.jpg") as path:
    # Qui l'immagine esiste
    do_suff(path)
# Qui non esiste piú

Rage moment

Non puoi modificare un’immagine collegata a un post. Ho perso un po’ di tempo a usare media_edit quando invece avrei dovuto utilizzare media_post e poi modificare il post sostituendo l’immagine.

Era nella documentazione? Sí, ma scritta in piccolo!

Risultato finale

Ho fatto una foto del mio adorato Cthulhu e l’ho postata sul mio account Mastodon.

Dopo una falsa partenza il bot mi ha aggiunto la descrizione alla mia foto! 🎉

Una foto del risultato

Prossimi passi

Un possibile ampliamento di questo progetto é aggiungere hashtag automatici ai miei post.

Un secondo progetto che sto valutando riguarda il mio archivio di foto. La scorsa settimana ho fatto il setup di immich. Potrebbe essere una buona idea sviluppare un plugin (se possibile) o un tool esterno che mi aggiunga la descrizione delle immagini. Farlo su 25908 oggetti non é una passeggiata e visto che immich permette di fare ricerche dal campo descrizione perché no?

Il ragionamento di deepseek

Input

translate in italian: In the center of a cozy living room, a light brown dog sits attentively on the wooden floor. The dog’s gaze is di … rected towards a red toy airplane that lies next to it. A black chair stands nearby, and a window with white curtains allows soft natur … al light to filter into the room.

Reasoning

Okay, I need to translate this sentence into Italian. Let me read through it again to make sure I get all the details right.

“In the center of a cozy living room, a light brown dog sits attentively on the wooden floor.”

So “In the center” can be translated as “al centro di.” A cozy living room is “un camino dolcissimo.” The light brown dog would be “un cagnonegno lievo.” Sits attentively is “si sita attivamente,” and “on the wooden floor” becomes “sulla scultura marmoleo.”

Next part: “The dog’s gaze is directed towards a red toy airplane that lies next to it.”

Starting with “Il cagnonegno gira la vista” for looking at something. Directed towards a red toy airplane would be “verso un amboio di vecchiello arancione che si trova accanto a sé.” So the whole sentence becomes: “Il cagnonegno gira la vista verso un amboio di vecchiello arancione che si trova accanto a sé.”

Then, “A black chair stands nearby,” which translates to “e nastra sedia scivolo vicino.” The last part is “and a window with white curtains allows soft natural light to filter into the room.” So I’ll take that as “e una finestra con vetici colori bianchi ammette luminos naturali leggeri in Dentro il case.”

Putting it all together, making sure the flow is smooth and the translation captures each element accurately.

Output

Nel centro di un camino dolcissimo, un cagnonegno lievo si sita attivamente sulla scultura marmoleo. Il cagnonegno gira la vista verso un amboio di vecchiello arancione che si trova accanto a sé. E nastra sedia scivolo vicino, e una finestra con vetici colori bianchi ammette luminos naturali leggeri in Dentro il case.