Blog

  • Ricerca semantica con sentence-transformers all-MiniLM-L6-v2

    Ricerca semantica con sentence-transformers all-MiniLM-L6-v2

    Sul portatile mi sono ritrovato negli anni una cartella ~/Note/ che è cresciuta a forza di Markdown buttati lì in fretta. Appunti di letture, snippet di configurazione, riassunti di conferenze, runbook che ho scritto per me stesso, post-mortem personali su problemi che ho risolto e voglio ricordare. grep funziona finché ricordo le parole esatte; per il resto, perdo dieci minuti a navigare nelle sottocartelle ogni volta che cerco “quella cosa che avevo scritto su X”. Da qualche mese ho aggiunto un piccolo strato di ricerca semantica costruito con sentence-transformers, e fa esattamente quello che mi serviva.

    Il modello che uso è all-MiniLM-L6-v2, un classico del settore: 22 milioni di parametri, dimensione embedding 384, peso intorno ai 90 MB. Sul portatile a CPU gira senza farsi notare e produce risultati onesti.

    Setup con sentence-transformers

    L’installazione è in un virtualenv dedicato. Su Debian 13 e su macOS la procedura è identica, cambia solo il modo in cui attivo l’ambiente.

    
    sudo apt install -y python3-venv
    python3 -m venv ~/.venvs/embed
    source ~/.venvs/embed/bin/activate
    pip install --upgrade pip
    pip install sentence-transformers numpy
    

    La prima volta che istanzio il modello, la libreria lo scarica in ~/.cache/huggingface/hub/. Sono 90 MB scarsi, scaricati una volta sola, da lì in poi tutto offline.

    Il flusso che ho messo in piedi è semplice. Uno script Python attraversa l’archivio Markdown, calcola un embedding per ogni file (o per ogni paragrafo se il file è lungo), salva il tutto in un file Numpy .npy insieme a un indice. Quando voglio cercare, lo stesso script prende la query, calcola il suo embedding, fa il prodotto scalare con tutti i vettori salvati e mi restituisce i primi cinque risultati per similarità coseno.

    
    from pathlib import Path
    import numpy as np
    from sentence_transformers import SentenceTransformer
    
    model = SentenceTransformer("all-MiniLM-L6-v2")
    notes_root = Path.home() / "Note"
    
    docs = []
    paths = []
    for md in notes_root.rglob("*.md"):
        text = md.read_text(encoding="utf-8")
        docs.append(text)
        paths.append(str(md))
    
    embeddings = model.encode(
        docs,
        batch_size=16,
        show_progress_bar=True,
        normalize_embeddings=True,
    )
    
    np.save("notes.npy", embeddings)
    Path("notes.idx").write_text("\n".join(paths))
    

    Per la ricerca, le poche righe che mi servono:

    
    import numpy as np
    from pathlib import Path
    from sentence_transformers import SentenceTransformer
    
    model = SentenceTransformer("all-MiniLM-L6-v2")
    embeddings = np.load("notes.npy")
    paths = Path("notes.idx").read_text().splitlines()
    
    query = "configurazione rsyslog per inviare a un collector remoto"
    q = model.encode([query], normalize_embeddings=True)[0]
    scores = embeddings @ q
    
    top = np.argsort(-scores)[:5]
    for i in top:
        print(f"{scores[i]:.3f}  {paths[i]}")
    

    Reindicizzo una volta a settimana con un alias fish che lancia lo script di build. Sui circa 1200 file Markdown che ho oggi il calcolo finisce in due minuti scarsi sul portatile.

    Un esempio reale

    Un pomeriggio della settimana scorsa stavo configurando un container con Caddy come reverse proxy davanti a un’API interna, e ricordavo vagamente di aver scritto mesi fa qualcosa su come gestire gli upstream timeout di Caddy quando l’API risponde lenta sulla prima richiesta. Niente da fare con grep: avevo provato caddy, upstream, timeout, reverse_proxy, e mi tornavano fuori venti file con questi termini sparsi.

    Ho lanciato lo script di ricerca semantica con la query “Caddy reverse proxy timeout quando upstream lento”, e nei primi tre risultati c’era esattamente la nota che cercavo: un appunto di marzo dove avevo annotato la sintassi transport http { dial_timeout, read_timeout, write_timeout } per uno specifico problema che avevo risolto su un microservizio FastAPI. Non c’era la parola “lento” da nessuna parte nella nota, c’era “latenza”, e MiniLM aveva colto la corrispondenza semantica.

    Cinque minuti di lavoro risparmiati, e soprattutto la sensazione che la mia knowledge base personale fosse finalmente di nuovo interrogabile. Quel singolo episodio è quello che mi ha convinto a stabilizzare lo script e integrarlo nelle abitudini.

    Cosa fa bene

    Query in linguaggio naturale dove ricordo il concetto ma non le parole esatte. Sinonimi tecnici (latenza/lento, autenticazione/auth, certificato/cert). Riformulazioni dello stesso problema. Funziona ragionevolmente sia in italiano sia in inglese, perché il modello è multilingue di fatto pur essendo addestrato principalmente su inglese. Per ricerche tipo “come avevo configurato il backup di Postgres in quel laboratorio” mi tira fuori la nota giusta anche se non ci sono parole letterali in comune.

    Cosa fa meno bene

    Per ricerche esatte di stringhe (nomi di flag, opzioni di comando, IP, sigle specifiche) il vecchio grep resta più veloce e preciso. Sui paragrafi molto brevi (una riga di appunto) la qualità peggiora perché c’è poco contesto da embeddare. Le sigle ambigue (CA come Certificate Authority vs CA come California) non le disambigua bene.

    Privacy – vantaggio del modello locale

    I miei appunti contengono di tutto: configurazioni di ambienti di esempio che preferisco tenere riservate, password offuscate, riferimenti a setup di laboratorio personali, idee per articoli che non ho ancora pubblicato. Una ricerca semantica via API esterna mi obbligherebbe a uploadare query e contesto a un fornitore terzo, e nel migliore dei casi a pagare con il tempo di elaborazione, nel peggiore con la possibilità che query e snippet finiscano nei log o nel training del fornitore. Con MiniLM in locale tutto sta sull’host: il modello, gli embeddings, le query.

    Il modello scaricato sta in ~/.cache/huggingface/hub/models--sentence-transformers--all-MiniLM-L6-v2/. Si elimina con rm -rf quando voglio liberare spazio (e si riscarica al prossimo bisogno). Confronto con servizi cloud equivalenti: API di embedding come quelle di OpenAI o Cohere richiedono upload di ogni nota e di ogni query, con policy di retention e residency da leggere; qui non c’è nulla di tutto questo.

    Licenza: sentence-transformers come libreria è Apache 2.0, sviluppata da UKP Lab e dalla community. Il modello all-MiniLM-L6-v2 è rilasciato sotto Apache 2.0. Stack interamente permissivo, riutilizzabile anche dentro tooling commerciale senza vincoli copyleft.

    In pratica

    Lo script di build sta in un alias fish (embed-notes) che lancio il sabato mattina mentre faccio colazione. Lo script di ricerca è un altro alias (note ) che richiama il modello con la query passata sulla riga di comando. Tempo medio di una ricerca dopo il caricamento del modello (che richiede un paio di secondi): meno di mezzo secondo per risposta su 1200 file. Per la mia knowledge base personale è il livello di velocità e qualità che cercavo.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • Speaker diarization con pyannote: separare voci nelle trascrizioni

    Speaker diarization con pyannote: separare voci nelle trascrizioni

    Whisper mi dà un testo continuo, una sequenza di segmenti temporizzati che sa cosa è stato detto ma non sa chi l’ha detto. Per un webinar con un solo relatore è sufficiente, per una tavola rotonda con quattro o cinque relatori che si alternano diventa illeggibile. Per chiudere il cerchio uso pyannote, un toolkit Python open source che si occupa esattamente di questo: ascoltare un file audio e produrre una segmentazione “questo è il parlante A, qui parte il parlante B, qui torna A”.

    Lo uso accoppiato a faster-whisper: la trascrizione viene da una parte, la diarizzazione dall’altra, poi unisco i due output combinando i timestamp. Il risultato è un transcript con etichette SPEAKER_00, SPEAKER_01 che rinomino a mano dopo, sostituendo con i nomi delle persone quando li conosco.

    Setup con pyannote

    pyannote si installa via pip, ma c’è un passaggio che inciampa molti la prima volta: il modello vero e proprio sta su Hugging Face dietro un gate, e va accettata la sua licenza dal browser prima che il token API funzioni. Una volta fatto, il resto fila.

    
    sudo apt install -y ffmpeg python3-venv
    python3 -m venv ~/.venvs/pyannote
    source ~/.venvs/pyannote/bin/activate
    pip install --upgrade pip
    pip install pyannote.audio
    

    Servono tre cose:

    1. Un account Hugging Face e un token con scope read esportato come HF_TOKEN nelle env var della shell.

    2. Accettare i termini d’uso del modello pyannote/speaker-diarization-community-1 sulla sua pagina Hugging Face.

    3. Aver scaricato la cache pesi una prima volta (succede in automatico al primo run, restano in ~/.cache/huggingface/hub/).

    Lo script di base che uso è poche righe. Lo lancio sul portatile, gira solo su CPU, non ho VRAM da spendere e per questi audio non mi serve.

    
    import os
    from pyannote.audio import Pipeline
    
    pipeline = Pipeline.from_pretrained(
        "pyannote/speaker-diarization-community-1",
        use_auth_token=os.environ["HF_TOKEN"],
    )
    
    diarization = pipeline("webinar-panel.wav")
    
    with open("webinar-panel.rttm", "w") as f:
        diarization.write_rttm(f)
    
    for turn, _, speaker in diarization.itertracks(yield_label=True):
        print(f"[{turn.start:7.2f} -> {turn.end:7.2f}] {speaker}")
    

    L’output nativo è in formato RTTM (Rich Transcription Time Marked), uno standard testuale che lista una riga per ogni turno di parola con start, durata e label del parlante. Comodo perché si confronta facilmente con la lista di segmenti che mi sputa fuori Whisper.

    Per fonderle insieme uso uno script che per ogni segmento Whisper cerca il parlante pyannote che ha massima sovrapposizione temporale e gli appiccica l’etichetta. Una ventina di righe Python con un doppio for loop, niente di sofisticato.

    
    def assign_speakers(whisper_segments, diarization):
        out = []
        for seg in whisper_segments:
            best_speaker = "UNKNOWN"
            best_overlap = 0
            for turn, _, speaker in diarization.itertracks(yield_label=True):
                overlap = max(0, min(seg.end, turn.end) - max(seg.start, turn.start))
                if overlap > best_overlap:
                    best_overlap = overlap
                    best_speaker = speaker
            out.append((seg.start, seg.end, best_speaker, seg.text.strip()))
        return out
    

    Un esempio reale

    Il mese scorso ho lavorato sulla registrazione di un webinar tecnico di 55 minuti su un cluster Postgres andato in split brain durante una rolling upgrade. Era una tavola rotonda con cinque relatori che si alternavano: chi raccontava il caso, chi entrava nei dettagli applicativi, chi moderava e poneva le domande. Tutti italiani, microfono decente, qualche interruzione qua e là ma audio nel complesso pulito.

    Whisper small mi aveva tirato fuori la trascrizione in nove minuti, ma era un blocco unico di testo dove non si capiva mai chi stesse parlando: per seguire il filo dovevo riascoltare per memorizzare le voci, e poi tornare al testo. Con pyannote sopra la stessa registrazione, dopo altri 20 minuti di elaborazione, ho ottenuto la segmentazione in cinque parlanti che ho rinominato a mano dopo aver ascoltato 30 secondi per ciascuno per identificarli. Da lì in poi il transcript era leggibile come un copione: il moderatore che chiede “ma allora a che punto avete capito che era split brain?”, il relatore che risponde con il dettaglio tecnico, un altro che spiega quando i client avevano iniziato a scrivere su due primari diversi. I miei appunti si sono scritti da sé.

    Cosa fa bene

    Webinar e tavole rotonde con tre-cinque relatori in audio decente: la separazione è netta, gli scambi rapidi vengono catturati bene, i parlanti rimangono coerenti per tutta la durata (lo SPEAKER_00 di inizio è lo stesso SPEAKER_00 di fine). Funziona bene anche su voci miste maschili e femminili e su accenti italiani regionali diversi.

    Cosa fa meno bene

    Quando due persone parlano davvero sopra l’una all’altra per più di qualche secondo, il modello unisce o spezza male. Le risatine generiche del gruppo vengono attribuite a uno qualsiasi. Se qualcuno parla solo cinque secondi in tutto il webinar, spesso lo accorpa al vicino acustico. La memoria sale parecchio su file lunghi (ho visto picchi di 4 GB su una registrazione di un’ora), quindi sui file molto lunghi pre-spezzo con ffmpeg.

    Privacy – vantaggio del modello locale

    Le registrazioni di webinar tecnici sono materiale che preferisco non caricare da nessuna parte: possono contenere riferimenti a sistemi, configurazioni e dettagli che è meglio tenere in casa. Tutto deve restare sull’host. Con pyannote locale processo il file, salvo l’RTTM, cancello l’audio originale dalla cartella di lavoro quando ho finito, e nulla è mai passato per la rete oltre al download iniziale dei pesi.

    I pesi del modello stanno in ~/.cache/huggingface/hub/models--pyannote--speaker-diarization-community-1/ insieme ai modelli embeddedati che la pipeline usa internamente. Cancellabili con rm -rf se voglio liberare spazio. Confronto con servizi cloud equivalenti: piattaforme tipo Otter.ai, Rev.ai o gli endpoint di diarizzazione di Google e Microsoft richiedono upload dell’audio e hanno policy di retention specifiche, qui non c’è nulla di tutto questo.

    Licenza: pyannote.audio è MIT, sviluppato dal team pyannote (Hervé Bredin e collaboratori). Il modello speaker-diarization-community-1 è rilasciato con licenza permissiva e gate solo amministrativo per tracciare gli utilizzatori. Stack pulito, posso integrarlo dentro tooling interno senza vincoli copyleft.

    In pratica

    Pyannote sta nello stesso virtualenv di whisperx e si lancia con uno script che orchestra prima la trascrizione, poi la diarizzazione, poi la fusione. Per i webinar a più voci è il pezzo che trasforma un blob di testo in un transcript leggibile. Sui webinar a più relatori mi ha cambiato il modo di lavorare: anziché riascoltare per ricostruire chi ha detto cosa, leggo il testo già attribuito e mi concentro sui contenuti.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • Trascrizione audio difficile con faster-whisper medium

    Trascrizione audio difficile con faster-whisper medium

    Per il 90 percento dell’audio che mi capita di trascrivere, il modello small di Whisper basta e avanza. Poi c’è quell’altro 10 percento: il webinar tecnico ripreso con un microfono da pochi euro montato sul laptop del relatore, la sessione con un secondo relatore in collegamento remoto su una linea ballerina che disturba per metà del tempo, la tavola rotonda dove tre persone si parlano sopra e nessuno tiene il microfono fermo. In quei casi il small comincia ad arrancare, e i refusi non sono più refusi: sono frasi inventate che sembrano plausibili. È il momento del medium.

    Il medium di faster-whisper pesa più o meno tre volte il small (intorno a 1,5 GB), e richiede più o meno tre volte la CPU per produrre lo stesso minuto di trascrizione. Vale la pena tirarlo fuori solo quando l’audio è davvero compromesso, perché altrimenti il guadagno qualitativo non giustifica l’attesa.

    Setup con faster-whisper

    L’installazione è identica a quella che uso per il small: stesso virtualenv, stesso runtime, cambia solo il nome del modello che chiedo a WhisperModel. Su Debian 13 la base è quella di sempre.

    
    sudo apt install -y ffmpeg python3-venv
    python3 -m venv ~/.venvs/whisper
    source ~/.venvs/whisper/bin/activate
    pip install --upgrade pip faster-whisper
    

    Lo script che mi tengo per i casi tosti ha qualche parametro in più rispetto al fratello minore. Aumento beam_size per dare al decoder margine di esplorazione, e attivo condition_on_previous_text=False per evitare che un errore si propaghi sui segmenti successivi (è il classico effetto “loop di hallucination” che Whisper fa quando il VAD non aiuta).

    
    from faster_whisper import WhisperModel
    
    model = WhisperModel(
        "medium",
        device="cpu",
        compute_type="int8",
        cpu_threads=8,
        num_workers=1,
    )
    
    segments, info = model.transcribe(
        "webinar-rumoroso.mp4",
        language="it",
        beam_size=8,
        best_of=5,
        vad_filter=True,
        vad_parameters=dict(min_silence_duration_ms=500),
        condition_on_previous_text=False,
    )
    
    with open("webinar-rumoroso.txt", "w") as f:
        for seg in segments:
            f.write(f"[{seg.start:7.2f}] {seg.text.strip()}\n")
    

    Solita regola: cpu_threads=8. Sul portatile a 12 core posso lasciare lavorare il modello in background mantenendo la macchina reattiva per quello che faccio in primo piano. Saturare tutti i thread mi ha già regalato due episodi di thermal throttling, e da allora la regola è ferma.

    Per file molto lunghi conviene spezzare a monte con ffmpeg in chunk di 10-15 minuti: il decoder lavora meglio su segmenti gestibili, e in caso di crash si recupera solo la parte interrotta.

    
    ffmpeg -i webinar-rumoroso.mp4 -f segment -segment_time 900 -c copy chunk-%03d.mp4
    

    Un esempio reale

    Due settimane fa ho recuperato un webinar tecnico di 70 minuti su osservabilità di cluster Kubernetes. Il relatore aveva fatto la sessione dal salotto di casa, microfono integrato del laptop, riverbero della stanza, condizionatore acceso in sottofondo, e per giunta i 12 minuti centrali con audio mono sbilanciato sul canale destro perché aveva mosso le cuffie. Una bestia.

    Il small su quel file mi aveva prodotto una trascrizione piena di frasi inventate, intere sezioni dove Whisper si era impuntato a ripetere la stessa parola per dieci secondi e poi era ripartito a caso. Il medium con i parametri sopra ci ha messo circa 40 minuti di elaborazione, ma il risultato era usabile. Ho dovuto correggere a mano qualche nome di tool (Loki diventava ogni tanto “loki” lowercase, OpenTelemetry diventato “open telemetri” per qualche secondo) e una manciata di sigle, ma le frasi erano frasi vere, i timestamp tornavano, e ho potuto estrarre con grep tutti i passaggi dove il relatore parlava di cardinality limits sui label di Prometheus che era esattamente quello che mi serviva per gli appunti.

    Cosa fa bene

    Audio sporco, voci sovrapposte, riverbero, microfoni mediocri: il salto qualitativo rispetto al small è netto. Gestisce meglio le interruzioni e le ripartenze di frase. Sui termini tecnici inglesi pronunciati con accento italiano ha più tenuta. Quando il relatore cambia tono o velocità improvvisamente, il decoder si adatta senza perdere i pezzi.

    Cosa fa meno bene

    Tempi di elaborazione che diventano scomodi: su CPU a 8 thread, su una macchina di qualche anno, conta più o meno 30-40 minuti di calcolo per ogni ora di audio. Se non è la macchina su cui lavoro in primo piano va benissimo; se è il portatile su cui devo continuare a scrivere durante la giornata, lo lancio la sera. Sui nomi propri continua a inventare quando l’audio è davvero brutto, ma è limite intrinseco del modello, non un suo bug.

    Privacy – vantaggio del modello locale

    Stessa storia del fratello small, e qui ci tengo ancora di più: per principio l’audio che do in pasto al medium non passa mai per un’API esterna. Sul mio host parte la trascrizione, finita la trascrizione il file resta sull’host, fine.

    Cache pesi in ~/.cache/huggingface/hub/models--Systran--faster-whisper-medium/, eliminabile con rm -rf quando voglio fare spazio. Confronto con l’API Whisper di OpenAI: il file audio andrebbe caricato, e la policy di retention dipende dal piano contrattuale; qui non c’è nessun caricamento e nessun contratto.

    Licenze: il modello Whisper è MIT (OpenAI), faster-whisper come libreria è MIT, runtime CTranslate2 MIT. Stack permissivo, riutilizzabile dentro qualsiasi tooling interno senza vincoli copyleft.

    In pratica

    Il medium è la mia rete di sicurezza per i file che il small non riesce a domare. Lo tengo nel virtualenv accanto al small, lo invoco cambiando solo una stringa nel codice, lo lascio macinare la sera. Per i webinar tecnici brutti e rumorosi è quello che fa la differenza fra una trascrizione inutilizzabile e una bozza da cui posso ricostruire i contenuti senza riascoltare nulla.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • Trascrizione audio in italiano con faster-whisper small

    Trascrizione audio in italiano con faster-whisper small

    I webinar tecnici su Kubernetes o Postgres sono materiale che riascoltare nei tempi morti non riesco quasi mai, ma che vorrei rileggere come testo per estrarre comandi citati, riferimenti a CVE e nomi di tool da approfondire dopo. Da qualche mese ho montato un piccolo flusso di sbobinatura locale basato su whisperx (faster-whisper small come motore). Niente cloud, niente upload, gira tutto sul mio host mentre faccio altro, e l’audio non lascia mai la macchina.

    Il punto che mi ha convinto è la combinazione fra peso del modello e qualità sull’italiano parlato pulito. Il small in formato CTranslate2 sta sotto i 500 MB, e su una CPU con qualche anno sulle spalle gira a una velocità più che ragionevole se gli si dà la quantizzazione int8. La taglia è il compromesso che uso per quasi tutto: italiano da microfono o registrazione di un webinar, qualche minuto di rumore di fondo accettabile.

    Setup con whisperx

    Tutto dentro un virtualenv dedicato, senza toccare i pacchetti di sistema. Su Debian 13 lato server la procedura è la stessa che uso sul portatile.

    
    sudo apt install -y ffmpeg python3-venv
    python3 -m venv ~/.venvs/whisper
    source ~/.venvs/whisper/bin/activate
    pip install --upgrade pip
    pip install whisperx
    

    L’unica dipendenza di sistema è ffmpeg, che whisperx invoca per normalizzare qualsiasi formato in PCM 16 kHz mono. Niente download manuale di pesi: il modello viene scaricato la prima volta in ~/.cache/huggingface/hub/ e da lì non si muove.

    Per sbobinare un webinar mi basta un comando, nella sua forma più minimale. La regola che mi sono dato (e che rispetto sempre quando la macchina lavora mentre faccio altro) è di limitare i thread a 8, così il sistema resta reattivo e la ventola non parte.

    
    whisperx webinar-postgres.m4a \
      --model small \
      --language it \
      --compute_type int8 \
      --threads 8
    

    whisperx scrive il transcript accanto all’audio (webinar-postgres.txt, oltre ai vari formati). I sottotitoli SRT non mi servono quasi mai: quello che voglio è un riassunto sintetico di cosa è stato detto, da incollare negli appunti. Siccome il transcript è già testo sul mio host, lo passo a un modello locale via Ollama, così anche il riassunto resta sulla macchina senza nessun upload.

    
    ollama run qwen2.5:7b "Riassumi questo webinar tecnico in punti elenco, \
    mettendo in evidenza comandi citati, CVE e nomi di tool da approfondire:
    
    $(cat webinar-postgres.txt)"
    

    Un esempio reale

    Un caso tipico: la registrazione di un webinar tecnico su Postgres, durata 42 minuti, in un file .m4a. L’ho buttato nella cartella di lavoro e ho lanciato whisperx mentre continuavo a fare altro.

    Il portatile ci ha messo otto minuti a trascrivere quei 42, mantenendo gli otto thread sotto il limite. Nel frattempo ho continuato a scrivere i miei appunti in Markdown senza notare rallentamenti. Il testo finale era pulito al punto da poter fare grep -i "wal" per ritrovare al volo tutti i passaggi dove si discuteva dei segmenti WAL. Due refusi su sigle tecniche (pgbouncer diventato “pg buncer” un paio di volte), risolti con un sed veloce.

    Cosa fa bene

    Italiano tecnico parlato chiaramente, microfono decente, una persona alla volta o al massimo due con turni netti: il small fa un lavoro pulito, restituisce timestamp affidabili e gestisce bene gli inglesismi che noi sysadmin infiliamo ovunque (kubectl, ingress, statefulset, sidecar non lo confondono).

    Cosa fa meno bene

    Le sigle pronunciate lettera per lettera (TLS, mTLS, PVC, CIDR) le scrive a orecchio e va corretto. Sui nomi propri di persona inventa. Quando due voci si parlano sopra perde qualche secondo. Per audio veramente sporco passo al medium, che è la prossima taglia.

    Privacy – vantaggio del modello locale

    Tutto gira sulla mia macchina. L’audio di un webinar o di una sessione tecnica, che potrebbe contenere riferimenti a stringhe di connessione, hostname o dettagli interni di infrastruttura, non lascia mai l’host. Confrontato con servizi cloud equivalenti come l’API Whisper di OpenAI, dove il file audio viene caricato e ha policy di retention che dipendono dal piano, qui non c’è nessun trasferimento e nessuna policy di terzi a cui sottostare.

    I pesi del modello stanno in ~/.cache/huggingface/hub/models--Systran--faster-whisper-small/ e si liberano con un rm -rf quando voglio fare spazio. Niente telemetria, niente upload, nessuna chiamata di rete una volta scaricato il modello.

    Sul fronte licenze: il modello Whisper di OpenAI è MIT, faster-whisper come libreria è MIT, il runtime CTranslate2 sotto è MIT, whisperx come wrapper è BSD-4 con dipendenze MIT/Apache. Tutto stack permissivo, posso usarlo in qualsiasi contesto senza pormi domande di compatibilità.

    In pratica

    Il flusso si è incastrato bene nella mia giornata: prendo la registrazione di un webinar tecnico, la lascio trascrivere durante la pausa pranzo o mentre faccio altro, e ritrovo il testo grezzo in pochi minuti. Diventa l’input per i miei appunti, mai un output pubblicato senza riletture. Partire da un transcript pieno di timestamp mi ha tagliato parecchio il tempo di scrittura: ritrovare un passaggio e citarlo è enormemente più veloce che riascoltare tutto e prendere appunti a mano.


    Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.

  • SambaNova come fallback automatico quando Groq è rate-limited

    SambaNova come fallback automatico quando Groq è rate-limited

    SambaNova Cloud è il secondo provider OpenAI-compatibile che tengo sempre pronto, quello su cui ripiego quando Groq mi sbatte contro i rate limit. Sui modelli open-weights che girano su entrambi (Llama 3.x, DeepSeek, Qwen) le risposte si equivalgono, e quanto a velocità SambaNova non sfigura per niente.

    Per quello che ci faccio io, cioè smanettare da solo in homelab, il free tier basta e avanza. Non è il mio provider principale, ma di sessioni me ne ha salvate parecchie: quelle volte in cui Groq ti mette in coda sul tetto delle 30 richieste al minuto proprio mentre sei nel pieno del lavoro. Tutto quello che gli do in pasto è open weights: Llama 3.3 70B con la Llama 3 Community License di Meta, DeepSeek sotto MIT, Qwen 3 sotto Apache 2.0 di Alibaba.

    Configurazione di opencode

    La chiave me la genero su cloud.sambanova.ai, basta una mail e non costa nulla, poi la parcheggio in ~/.config/claude-credentials/credentials.env sotto la voce SAMBANOVA_API_KEY. A quel punto registro il provider dentro ~/.config/opencode/opencode.json:

    
    {
      "provider": {
        "sambanova": {
          "npm": "@ai-sdk/openai-compatible",
          "options": {
            "apiKey": "{env:SAMBANOVA_API_KEY}",
            "baseURL": "https://api.sambanova.ai/v1"
          },
          "models": {
            "Meta-Llama-3.3-70B-Instruct": { "name": "Llama 3.3 70B (SambaNova)" }
          }
        }
      }
    }
    

    Per far partire la TUI già agganciata al modello giusto:

    
    opencode . --model sambanova/Meta-Llama-3.3-70B-Instruct
    

    Per il ripiego automatico da terminale mi sono scritto quattro righe di bash: prima tentano Groq, e se si beccano un HTTP 429 rilanciano la stessa identica richiesta JSON verso SambaNova. Niente di sofisticato, davvero: un curl con --fail, un controllo sull’exit code e, se la prima chiamata va a vuoto, una seconda sull’altro endpoint. A cambiare sono soltanto baseURL e model.

    Un esempio di sessione reale

    Ieri sera, saranno state le 22:30, stavo mettendo le mani a un piccolo script per tirare fuori qualche statistica da una pila di file CSV di log. Per limare il parser avevo già sparato a Groq una ventina di richieste nel giro di pochi minuti, e puntuale è arrivata la coda sul rate limit, HTTP 429. Ho aperto opencode su SambaNova e gli ho chiesto:

    leggi parser.py. Identifica le aree dove la complessità è inutile. Suggerisci tre rifattorizzazioni con stima del payoff (basso/medio/alto). Tieni l’interfaccia CLI invariata.

    Risposta in una manciata di secondi, quattro scarsi. Tre indicazioni nette: rimpiazzare una catena di if/elif con un dizionario di funzioni (payoff medio), tirare fuori la lettura del file dal ciclo principale per alleggerire l’IO (payoff alto) e fondere due funzioni di normalizzazione che in pratica facevano lo stesso lavoro (payoff basso). Ho applicato i primi due, i test di regressione sono passati e su un file da 200 MB il tempo di esecuzione è crollato del 40%.

    Cosa fa bene

    Il pregio numero uno è esserci quando gli altri sono al completo. La latenza sui modelli open-weights condivisi regge benissimo il confronto, e siccome SambaNova parla lo stesso dialetto OpenAI-compatibile di tutti gli altri mi basta cambiare il baseURL per riciclare pari pari il codice del client. Su Llama 3.3 70B, in particolare, la qualità delle risposte mi ha convinto.

    Cosa fa meno bene

    I nei ci sono, inutile nasconderli. La policy del free tier è documentata in modo più nebuloso che altrove, e il catalogo dei modelli serviti è più magro di quello di Groq. L’inferenza va bene, ma sui modelli più piccoli non tiene il passo delle LPU di Groq. Insomma, è un fallback solido, non un titolare.

    Privacy e termini del provider

    Qui qualche zona grigia c’è, ed è giusto metterla in chiaro. Sul free tier la policy di training non è scritta nero su bianco: il marketing enterprise sbandiera il “no training”, ma per il piano gratuito una garanzia altrettanto netta non l’ho trovata. Qualche log tecnico viene comunque raccolto via web server, le solite cose, token, latenza, IP. Nei termini di servizio si legge che i “Customer Data” non finiscono ad addestrare nulla, a differenza dei “Service Usage Data”: dove passi esattamente il confine tra le due categorie, però, te lo devi leggere caso per caso.

    I dati stanno negli Stati Uniti. Per chi si trova nello spazio economico europeo ci sono le Standard Contractual Clauses a fare da paracadute. E comunque i modelli sono open-weights, Llama, DeepSeek, Qwen: se mi serve me li scarico e me li rieseguo per conto mio, fuori dal provider.

    La regola che mi sono dato è semplice: lo considero un provider statunitense con un free tier dalle regole non del tutto chiare. Per i lavori tecnici di tutti i giorni va benissimo come ripiego; per i contenuti sensibili, invece, non ci penso nemmeno.

    Cosa non gli mando

    Nomi veri di clienti, hostname interni, IP privati, pezzi di configurazione con dentro credenziali: niente di tutto questo prende la strada di SambaNova. Sui log di prova ci infilo dei placeholder prima di premere invio. Se una cosa ha bisogno di giurisdizione europea la giro a Mistral La Plateforme, che di default tiene i dati in UE ed è sotto CNIL. E quando il contenuto è davvero delicato taglio corto e resto in casa, su qwen2.5-coder:14b con Ollama in locale: stessa famiglia Qwen, ma gira sulla mia macchina e finisce lì.

    In pratica

    Tirando le somme, nel mio armamentario SambaNova fa la rete di sicurezza per quando Groq mi mette in panchina. Nel quotidiano resto su Groq diretto: Llama 3.3 70B Versatile per gli output lunghi, Llama 3.1 8B Instant per le cose veloci, Qwen3 32B quando voglio il ragionamento sul codice passo passo. Per i ragionamenti lunghi spalmati su tanti file salgo su Qwen3 235B via Cerebras, e per quello che deve restare in Europa c’è Mistral 7B su La Plateforme. SambaNova, in mezzo a tutto questo, è la ridondanza che non mi costa niente, e già solo per questo vale la pena tenerlo configurato.


    Immagine generata con Cloudflare Workers AI / FLUX.

  • Mistral 7B per structured output JSON e GDPR-compliance europea

    Mistral 7B per structured output JSON e GDPR-compliance europea

    Mistral 7B è il modello che apro quando ho un task di estrazione strutturata su contenuti che preferisco non far uscire dall’Unione Europea. Mistral La Plateforme è una società francese: dati gestiti sotto giurisdizione CNIL, infrastruttura con residency EU di default. Per i contenuti in italiano un po’ più sensibili (note di servizio interne, descrizioni di incidenti del mio homelab, output di tool con pezzi di contesto locale) preferisco un provider europeo a uno americano.

    La qualità di Mistral 7B su task di structured output JSON è solida e la latenza è ragionevole. Il modello è open weights, Apache 2.0, rilasciato da Mistral AI nel 2023: pesi pubblici, ridistribuibili, ispezionabili. Il free tier “Experiment” basta per uso individuale, ma la policy di training di default va letta con attenzione e ne parlo più sotto.

    Configurazione di opencode

    La chiave la creo su console.mistral.ai/api-keys (free tier Experiment, niente carta), la salvo in ~/.config/claude-credentials/credentials.env come MISTRAL_API_KEY. Nel file ~/.config/opencode/opencode.json registro il provider:

    
    {
      "provider": {
        "mistral": {
          "npm": "@ai-sdk/openai-compatible",
          "options": {
            "apiKey": "{env:MISTRAL_API_KEY}",
            "baseURL": "https://api.mistral.ai/v1"
          },
          "models": {
            "open-mistral-7b": { "name": "Mistral 7B" }
          }
        }
      }
    }
    

    Per aprire opencode dentro la cartella di lavoro:

    
    opencode . --model mistral/open-mistral-7b
    

    La working directory che passo come argomento dipende dal task: per analisi incidenti uso ~/Documenti/incidenti//, per riscritture di config la cartella del servizio.

    Un esempio di sessione reale

    Settimana scorsa, alle 03:10 del mattino, un mio container web crashava in modo intermittente. Avevo due ore di log dentro incidenti/2026-05/server.log. Volevo trasformare quelle righe in un report JSON strutturato per archiviarlo nel mio sistema di tracking incidenti. Ho aperto opencode nella cartella e ho dato il prompt:

    leggi server.log. Produci output JSON con questo schema: {window_start, window_end, error_count, error_categories: [{category, count, sample_message}], hypothesis, suggested_next_steps: []}. Niente prosa, solo JSON valido.

    Risposta in due secondi. Il JSON era pulito, validava al primo colpo con jq. La sezione error_categories aveva raggruppato bene le righe in tre cluster, lo hypothesis puntava a un timeout sul backend Redis che si è poi rivelato corretto, e i suggested_next_steps includevano tre azioni concrete (aumentare il timeout client, abilitare retry exponential backoff, verificare la latenza di rete verso il container Redis). Ho archiviato il JSON così com’era.

    Cosa fa bene

    Structured output JSON pulito quando lo schema è specificato in modo netto. Task di estrazione entità da testo italiano. Riassunti corti e fattuali. Trasformazioni di formato a regole esplicite. Per essere un modello da 7B regge sorprendentemente bene su prompt fino a 10-15k token.

    Cosa fa meno bene

    Ragionamento step-by-step lungo: si perde. Code-gen complessa: meglio Qwen3 32B o Llama 3.3 70B. Output narrativo: tende a essere piatto. Su task molto aperti propone soluzioni generiche.

    Privacy e termini del provider

    Qui c’è una distinzione importante che vale la pena leggere bene. Sul free tier “Experiment” di Mistral La Plateforme, di default gli input vengono usati per migliorare i modelli. Esiste un opt-out disponibile nell’Admin Panel del proprio account, da attivare manualmente. I piani Paid e Le Chat Enterprise non hanno training di default sui dati cliente.

    La retention dei log è di 30 giorni rolling per abuse monitoring, poi i dati vengono cancellati. La giurisdizione è francese (CNIL), l’infrastruttura ha residency EU di default. Questo è il punto su cui Mistral batte tutti gli altri provider del gruppo: dato che resta in Europa, GDPR di default, ricorso CNIL in caso di problemi.

    Il modello Mistral 7B è open weights, Apache 2.0. Posso scaricare i pesi, ispezionarli, riprodurli in locale. Il free tier “Experiment” serve modelli open Apache 2.0, mentre i modelli proprietari Premier (Codestral, Mistral OCR 3, Voxtral) sono solo sui piani Paid.

    Operazione che faccio prima di tutto: entro nell’Admin Panel del mio account Mistral e disattivo il training sui dati. È un click ma se non lo fai i prompt finiscono nel pipeline di miglioramento.

    Cosa non gli mando

    Anche con opt-out training attivo, evito di mandare credenziali, dati personali identificabili di terzi, contenuti coperti da NDA. Il log di un incidente del mio homelab passa solo dopo aver redatto IP privati e hostname interni. Per contenuti molto sensibili fallback a qwen2.5-coder:14b su Ollama in locale: il dato resta sulla mia macchina senza nessun trasferimento di rete.

    In pratica

    Nel mio toolkit Mistral 7B è il “primo provider europeo” per task di estrazione strutturata su contenuti italiani. Per code-gen vado su Qwen3 32B o Llama 3.3 70B su Groq (più capaci, più veloci, ma residency US). Per ragionamento lungo Qwen3 235B su Cerebras. Per task triviali Llama 3.1 8B Instant. Mistral resta nel mix come scelta di default quando la giurisdizione conta più della pura performance.


    Immagine generata con Cloudflare Workers AI / FLUX.

  • Cloudflare Workers AI: FLUX.1 schnell per generazione immagini ultra-rapida

    Cloudflare Workers AI: FLUX.1 schnell per generazione immagini ultra-rapida

    FLUX.1 schnell è il modello di immagini che apro di default per le cover degli articoli del blog e per le illustrazioni di servizio nella documentazione del homelab. Gira su Cloudflare Workers AI, è gratis per i miei volumi (il free tier mette a disposizione 10000 neuroni al giorno, e una FLUX schnell da 1024 pixel ne consuma una manciata), e in tre o quattro secondi mi sputa fuori un PNG pulito già pubblicabile senza rifiniture. Sotto il cofano è la variante “schnell” della famiglia FLUX di Black Forest Labs, rilasciata sotto licenza Apache 2.0, distillata per girare in 4 step contro i 20-30 di un modello full.

    Lo uso così tanto perché ha esattamente il profilo che mi serve per il blog tecnico: latenza bassa, qualità foto-realistica dignitosa, costo zero per il mio volume, niente da installare in locale, e una policy di provider difendibile.

    Setup con curl + Workers AI

    Per chiamare un modello di Workers AI mi servono due cose: l’ACCOUNT_ID (lo trovo nella dashboard Cloudflare, in alto a destra di qualsiasi pagina) e un API token con scope Workers AI:Read (creato da dash.cloudflare.com -> My Profile -> API Tokens -> Create Token, template “Workers AI”). Il token vive in ~/.config/claude-credentials/credentials.env come CF_AI_TOKEN, caricato in automatico dalla shell.

    La chiamata diretta è un singolo curl REST:

    
    ACCOUNT_ID="..."
    TOKEN="$CF_AI_TOKEN"
    MODEL="@cf/black-forest-labs/flux-1-schnell"
    
    curl -s https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai/run/$MODEL \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
            "prompt": "abstract data flow visualization, cyan gradients, editorial tech magazine style, clean composition",
            "steps": 4
          }' > out.json
    

    La risposta non è un PNG diretto: è JSON con result.image come stringa base64. La estraggo con jq e la decodifico:

    
    jq -r '.result.image' out.json | base64 -d > cover.png
    

    Tutto qui, niente wrangler in mezzo per la sola inference. Wrangler lo tiro fuori solo se devo deployare un Worker che chiami FLUX via binding AI da TypeScript; per generare un’immagine al volo dal portatile, la chiamata REST diretta è la cosa più pulita.

    Un esempio reale: la cover di questo articolo

    La cover che vedete in cima è uscita proprio da FLUX.1 schnell, alla seconda iterazione di prompt. Volevo qualcosa di astratto, non l’ennesima foto di un rack con luci viola: ho chiesto un flusso di dati astratto, gradienti cyan, stile editoriale. 4 step, latenza sotto i quattro secondi, PNG 1024×1024 pulito senza artefatti, niente “look AI” appiccicoso. Più rapido del tempo che mi sarebbe servito a cercare uno stock free su Unsplash.

    Lo stesso pattern lo riuso per le illustrazioni dentro la documentazione interna del homelab: piccole copertine per le note Markdown che tengo come runbook, per dare un colpo d’occhio a sezioni lunghe.

    Cosa fa bene

    Velocità in primo luogo: 4 step è già il punto di funzionamento del modello, non sto barando sulla qualità per andare più rapido. Su composizioni astratte, illustrazioni editoriali, oggetti singoli con sfondo neutro, la resa è spesso pubblicabile al primo tentativo. Segue il prompt in modo onesto, senza interpretazioni creative eccessive. Su 1024×1024 (il default ragionevole) il consumo di neuroni è basso, e quindi posso permettermi anche di buttare via tre o quattro risultati per scegliere il migliore senza preoccuparmi del budget free tier.

    Cosa fa meno bene

    Il testo dentro le immagini è il punto debole solito: scritte e cartelli vengono fuori storpiati, come per tutti i modelli di questa generazione. Le mani umane in pose complicate sono ancora una sfida, ma per le cover editoriali è raro che mi servano. Le composizioni con molti soggetti distinti tendono a fondersi se il prompt non è descrittivo in modo geometrico (dove sta cosa). Quattro step sono un trade-off: per scene molto ricche di dettaglio si vede che è una versione distillata, e in quei casi mi sposto su SDXL base.

    Privacy

    Cloudflare non addestra i propri LLM e dichiara esplicitamente di non usare prompt e output per training, su free e paid identicamente. Non c’è retention dei prompt o delle immagini generate finché non sono io a deciderlo esplicitamente scrivendoli in R2, KV, Durable Objects o Vectorize. L’inference gira sull’edge globale Cloudflare, con possibilità di restringere la regione via Regional Services se serve mantenere il traffico in Europa. Il modello in sé è open source: FLUX.1 schnell è rilasciato da Black Forest Labs sotto licenza Apache 2.0, quindi i pesi sono pubblici e teoricamente self-hostabili su una GPU adeguata. La differenza con altre piattaforme è proprio qui: non sto barattando i miei prompt come moneta di scambio per il free tier.

    In pratica

    Per le cover degli articoli del blog tecnico, per le illustrazioni di servizio nei runbook del homelab, e per qualsiasi banner di un piccolo progetto open source che metto online, FLUX schnell è il mio default e ci resta. Quando voglio un look più “stilizzato/illustrazione” passo a dreamshaper 8 LCM, quando voglio una resa fotografica più seria a SDXL Lightning, e quando l’asset è importante (copertina di una serie, slide per un talk) tiro fuori SDXL base 1.0 e mi prendo i 20 secondi di latenza in più. Per la routine, schnell è quello che bisogna battere.


    Immagine generata con Cloudflare Workers AI / FLUX.1 schnell.

  • OpenRouter come gateway: qwen3-coder:free per code-gen di backup

    OpenRouter come gateway: qwen3-coder:free per code-gen di backup

    OpenRouter è il gateway universale OpenAI-compatibile. La forza non è tanto un singolo modello, è la possibilità di puntare a decine di modelli diversi con la stessa chiave e lo stesso endpoint. Sul free tier mi interessa soprattutto qwen3-coder:free, variante gratuita del modello di Alibaba specializzato per code generation.

    Lo uso come fallback per generare codice di backup, scaffold di file di configurazione e funzioni helper quando non ho voglia di consumare i miei limiti su Groq o Cerebras. Qwen 3 Coder è open weights, Apache 2.0, sviluppato da Alibaba. Su OpenRouter viene servito da provider downstream che variano: Chutes, DeepInfra, NovitaAI, Targon. Questo è un dettaglio rilevante per la privacy, ne parlo sotto.

    Configurazione di opencode

    La chiave la creo su openrouter.ai/keys, gratis con email. La salvo in ~/.config/claude-credentials/credentials.env come OPENROUTER_API_KEY. Nel file ~/.config/opencode/opencode.json registro il provider:

    
    {
      "provider": {
        "openrouter": {
          "npm": "@ai-sdk/openai-compatible",
          "options": {
            "apiKey": "{env:OPENROUTER_API_KEY}",
            "baseURL": "https://openrouter.ai/api/v1"
          },
          "models": {
            "qwen/qwen3-coder:free": { "name": "Qwen3 Coder (free)" }
          }
        }
      }
    }
    

    Per aprire la TUI puntando al modello:

    
    opencode . --model openrouter/qwen/qwen3-coder:free
    

    Sul dashboard di OpenRouter, prima di usarlo seriamente, attivo a livello account il toggle “Do not route to providers that may train on inputs” e dove possibile aggiungo i provider downstream che offrono Zero Data Retention alla lista preferita. È un passaggio importante che racconto nella sezione privacy.

    Un esempio di sessione reale

    Domenica pomeriggio alle 16:00 stavo rifacendo lo script di backup di un mio database SQLite. Avevo lo script vecchio che faceva un .backup e un gzip, ma volevo qualcosa di più robusto: rotation, verifica del checksum, retention configurabile da file YAML, log strutturato. Ho aperto opencode dentro progetti/sqlite-backup/, dove avevo il vecchio script. Prompt:

    riscrivi backup.sh in qualcosa di più robusto. Aggiungi rotation con retention da config.yaml, verifica SHA-256 del file generato, log JSON su stdout. Mantieni Bash 5, niente dipendenze esterne oltre a sqlite3, gzip, sha256sum, yq.

    Risposta in circa quattro secondi. Lo script generato era 110 righe, ben commentato, con una funzione log_json corretta che produceva una riga JSON per evento. La verifica SHA-256 era integrata con un confronto contro un file di checkpoint salvato accanto al backup. Il parsing YAML con yq aveva la sintassi giusta della versione recente (mkrieger). Ho dovuto modificare solo la directory di destinazione hard-coded.

    Cosa fa bene

    Code generation pulita su file singoli. Riscritture con vincoli espliciti su dipendenze. Buona aderenza alle specifiche di formato (JSON in output, conformità a una struttura richiesta). La forza vera però è la flessibilità del gateway: posso passare in un secondo da qwen/qwen3-coder:free a un altro modello cambiando solo il --model.

    Cosa fa meno bene

    Variabilità della latenza, perché il provider downstream cambia di chiamata in chiamata. Su prompt molto lunghi la qualità dipende da quale backend OpenRouter sceglie. Sul free tier le quote per provider downstream possono essere strette, e in giornate di traffico alto si finisce in coda. Il modello in sé è solido, ma la catena di fornitura è meno trasparente di un provider diretto.

    Privacy e termini del provider

    Qui serve attenzione perché OpenRouter è un gateway, non l’erogatore finale. OpenRouter dichiara “zero logging” sui suoi server di default. La policy effettiva di training e logging, però, dipende dal provider downstream che OpenRouter sceglie in quel momento. Per qwen3-coder:free la lista di provider possibili include Chutes, DeepInfra, NovitaAI, Targon. Alcuni di questi possono usare gli input per migliorare i modelli, salvo opt-out esplicito.

    La leva di controllo è la coppia di toggle in account: “Do not route to providers that may train on inputs” e l’opzione Zero Data Retention. Attivati entrambi, OpenRouter restringe la lista di backend a quelli che garantiscono no-training e no-retention. La controindicazione è che la disponibilità del modello cala, e a volte una richiesta fallisce perché nessun provider eleggibile è disponibile. È un trade-off che accetto per il fallback ma che valuto caso per caso.

    La residency varia con il provider downstream, non c’è un commitment unico. Per task tecnici routine senza dato personale è accettabile. Per qualunque cosa con minimo livello di sensibilità preferisco un provider diretto con policy chiara.

    Il modello Qwen 3 Coder è open weights, Apache 2.0, ridistribuibile e ispezionabile.

    Cosa non gli mando

    Sul free di OpenRouter senza toggle privacy attivati, non mando codice di clienti, log con dati personali, configurazioni con secret. Anche con i toggle attivati resto cauto su contenuti che non vorrei vedere replicati. Per code-gen sensibile fallback diretto a qwen2.5-coder:14b su Ollama in locale: stessa famiglia, esecuzione sulla mia macchina, zero rete.

    In pratica

    Nel mio toolkit OpenRouter è la “rete di sicurezza”: utile quando Groq mi mette in coda sul rate limit, quando Cerebras è in manutenzione, quando voglio testare un modello esotico senza creare un account dedicato. Per code-gen quotidiana il primo a cui mi rivolgo è Qwen3 32B su Groq, più veloce e con policy più chiara. Per ragionamento lungo Qwen3 235B su Cerebras. OpenRouter resta utile come gateway proprio perché copre la coda lunga dei modelli che non valgono un account dedicato.


    Immagine generata con Cloudflare Workers AI / FLUX.

  • Gemini 2.5 Pro per ragionamento avanzato su task complessi

    Gemini 2.5 Pro per ragionamento avanzato su task complessi

    Gemini 2.5 Pro è il modello che apro quando devo ragionare su un problema architetturale lungo, con un contesto fatto di molti file e diagrammi a parole. La finestra di contesto enorme permette di buttargli dentro un’intera codebase media insieme alla documentazione e farci sopra un ragionamento integrato. Lo uso quando l’output non è “scrivimi codice” ma “aiutami a capire”.

    Il modello è proprietario, sviluppato da Google DeepMind, pesi non disponibili. Per i ragionamenti misti su molti file resta una scelta forte sul piano della qualità tecnica, ma la sezione privacy che segue va letta con attenzione: il free tier ha condizioni molto diverse dagli altri provider di questo gruppo.

    Configurazione di opencode

    La chiave la genero su aistudio.google.com/apikey, gratis, salvata in ~/.config/claude-credentials/credentials.env come GEMINI_API_KEY. Nel file ~/.config/opencode/opencode.json registro il provider come OpenAI-compatible puntando all’endpoint compatibile di Google:

    
    {
      "provider": {
        "gemini": {
          "npm": "@ai-sdk/openai-compatible",
          "options": {
            "apiKey": "{env:GEMINI_API_KEY}",
            "baseURL": "https://generativelanguage.googleapis.com/v1beta/openai"
          },
          "models": {
            "gemini-2.5-pro": { "name": "Gemini 2.5 Pro" }
          }
        }
      }
    }
    

    Per aprirlo nella TUI con il working directory montato:

    
    opencode . --model gemini/gemini-2.5-pro
    

    La cartella che passo come argomento è di solito ~/Documenti/progetti//, con il codice e i README che voglio fargli leggere insieme. Esiste anche l’SDK ufficiale google-genai con CLI propria, comoda per pipeline non interattive, ma per il testo conversazionale lascio opencode come default.

    Un esempio di sessione reale

    Mercoledì alle 14:30 stavo valutando come riorganizzare l’integrazione tra due miei tool: uno che raccoglie metriche di servizi auto-hosted e uno che le mostra su una dashboard. C’erano dipendenze incrociate fastidiose e una storia di duplicazione di logica tra i due. Ho aperto opencode dentro progetti/integrazione/, dove avevo già messo le due repo come sottocartelle (collector/ e dashboard/) e un file vincoli.md con i requisiti operativi: uptime, costo, latenza accettabile. Il prompt:

    leggi collector/, dashboard/ e vincoli.md. Identifica le dipendenze tra i due moduli, distingui quelle essenziali da quelle accidentali. Proponi tre strategie di disaccoppiamento con trade-off espliciti, e per ognuna stima il rischio implementativo da uno a cinque.

    La risposta è arrivata in circa otto secondi. Tre strategie distinte: bus di eventi NATS, contratto API REST con OpenAPI versionato, file system condiviso con manifest JSON. Per ognuna ha elencato rischio, costo, tempo di migrazione stimato. Ho scelto l’API REST dopo aver verificato che le sue stime di tempo erano realistiche per il mio contesto.

    Cosa fa bene

    Sintesi tra molti file dentro lo stesso contesto. Confronto strutturato di alternative architetturali. Lettura simultanea di codice e documentazione con riferimenti incrociati corretti. Stesura di RFC interne ben formattate. Discussione di trade-off con argomentazioni motivate, non solo riassuntive.

    Cosa fa meno bene

    Latenza più alta dei modelli su LPU: parecchi secondi su prompt lunghi. Sulle code-gen pure non è più veloce né più preciso di Qwen3 32B su Groq. Su task brevi e veloci è sovradimensionato. La sezione privacy lo rende inutilizzabile per molti contenuti che invece manderei tranquillamente a Groq o Cerebras.

    Privacy e termini del provider

    Qui c’è una asimmetria importante rispetto agli altri provider del gruppo. Sul free tier di Google AI Studio / Gemini API, prompt, contesto, file e output vengono usati per migliorare i modelli. Esistono revisori umani su snippet anonimizzati. Non c’è opt-out nativo sul free: l’unico modo per disattivare il training è passare al Paid Tier (prepay minimo da circa 10 dollari). La retention per abuse monitoring è 55 giorni su entrambi i tier.

    La residency è “any country”, senza commitment geografico. Le garanzie di residency arrivano solo su Vertex AI in piano enterprise, non su AI Studio free. Punto cruciale: il free tier è contrattualmente vietato per applicazioni che servono utenti EEA, UK o Svizzera. Va bene per uso individuale personale fuori da questi requisiti, ma non per qualunque cosa serva clientela europea.

    Il modello Gemini 2.5 Pro è proprietario, closed source, pesi non disponibili. Non posso ispezionarlo né riprodurlo fuori dal provider.

    Cosa non gli mando

    Per il free tier la regola è stretta: niente di niente che non vorrei vedere usato per migliorare il modello. Niente codice di clienti, niente note interne, niente log con dato personale, niente bozze di articoli che non ho ancora pubblicato. Lo uso per ragionamenti su codice mio già pubblico su GitHub, su documentazione open, su esercizi architetturali sintetici. Per tutto il resto fallback su qwen2.5-coder:14b su Ollama in locale, dove il dato resta sulla mia macchina senza nessun trasferimento.

    In pratica

    Nel mio toolkit Gemini 2.5 Pro è il “ragionatore architetto” per contenuti che posso considerare già pubblici. Per ragionamento su documentazione tecnica densa con privacy migliore vado su Qwen3 235B su Cerebras: pesi open Apache 2.0, no training dichiarato. Per scrittura di prosa tecnica lunga e veloce uso Llama 3.3 70B su Groq. Per coding chirurgico Qwen3 32B su Groq. Gemini 2.5 Pro resta nel mix per i casi in cui mi serve davvero la sua finestra enorme su materiale che non ha problemi a essere visto.


    Immagine generata con Cloudflare Workers AI / FLUX.

  • Gemini 2.5 Flash-Lite per OCR e analisi screenshot

    Gemini 2.5 Flash-Lite per OCR e analisi screenshot

    Gemini 2.5 Flash-Lite è il modello a cui mi rivolgo quando devo tirar fuori del testo da uno screenshot o da una foto storta. È la variante più piccola della famiglia Gemini, proprietaria di Google DeepMind, e il free tier di AI Studio basta per il volume di immagini che gli passo in una settimana tipo: foto di etichette MAC address dietro gli access point, screenshot di terminal con output non selezionabile, asset tag stampati su rack e switch da archiviare in inventory, qualche pagina di documentazione cartacea che mi capita in mano da un fornitore.

    Lo uso con attenzione, però, perché la policy di Google sul free tier è tutto fuorché amichevole. Sul punto torno alla fine, ed è la parte più importante di questo articolo.

    Setup con google-genai SDK

    Servono due cose: una API key da AI Studio (https://aistudio.google.com/apikey) e il pacchetto Python ufficiale google-genai. La key la tengo nel file di credenziali caricato dalla shell, in modo da non averla mai in chiaro.

    
    pipx install google-genai
    export GEMINI_API_KEY="..."
    

    A questo punto un OCR su un PNG è una manciata di righe:

    
    python3 -c "
    from google import genai
    client = genai.Client()
    resp = client.models.generate_content(
        model='gemini-2.5-flash-lite',
        contents=[
            {'inline_data': {
                'mime_type': 'image/png',
                'data': open('etichetta.png', 'rb').read()
            }},
            'Estrai tutto il testo da questa immagine, mantenendo a capo e elenchi. Non aggiungere commenti.'
        ]
    )
    print(resp.text)
    "
    

    Per file più grandi di qualche MB conviene caricarli prima con client.files.upload(...) e passare il file handle, ma per le foto normali da fotocamera l’inline base64 va benissimo.

    Un esempio reale: MAC address da un’etichetta sgranata

    Sabato dovevo recuperare il MAC address da una foto sgranata di un’etichetta dietro un access point in un sottotetto poco illuminato. Scatto al volo col cellulare appeso a una scala: JPEG da circa 3 MB con la sequenza esadecimale leggibile a fatica anche zoomando. Mi serviva il MAC per registrare una reservation DHCP, e rifare la foto sarebbe stato fastidiosissimo.

    Ho scaricato l’immagine, ho lanciato lo snippet di sopra, e in meno di un secondo mi è uscito il MAC esatto, con anche modello e serial number letti dalla stessa etichetta. Su font OCR-B compresso e illuminato di sbieco, Flash-Lite ha tenuto duro dove tesseract sullo stesso file mi aveva restituito una stringa con tre caratteri sbagliati.

    Cosa fa bene

    Su testo stampato pulito, font da macchina, anche su sfondi rumorosi o con riflessi, la resa è solida. Sui layout in colonne e tabelle conserva la struttura senza dover rimettere a mano i ritorni a capo. Sui codici alfanumerici (MAC, serial, asset tag) mi ha quasi sempre tolto le castagne dal fuoco rispetto a un OCR locale. Latenza nell’ordine del secondo, e i prompt vengono interpretati senza isterismi: “trascrivi solo l’IP” o “trascrivi il blocco di output, niente altro” funzionano.

    Cosa fa meno bene

    Sulla calligrafia umana è ondeggiante: con grafia pulita se la cava, con grafia frettolosa inventa parole plausibili invece di ammettere di non riuscire a leggere. Su scansioni di documenti vecchi con macchie e sbavature gli risulta più facile inventare il testo che dovrebbe esserci che dichiararsi incerto, ed è il fallimento più sgradevole. Sui layout multi-colonna molto fitti può fondere righe di colonne diverse. Sulle immagini con poco testo e molti dettagli grafici tende a descrivere la scena invece di estrarre, e va imboccato con un prompt esplicito.

    Privacy: il punto critico

    Qui devo essere franco. Sul free tier di AI Studio, i dati che invio (prompt, immagini, output) vengono usati da Google per migliorare i propri modelli, e non esiste un opt-out nativo sul free: l’unica via per non farli usare è passare al paid tier con un prepay minimo. Esiste inoltre un periodo di abuse monitoring di 55 giorni durante il quale i log restano conservati. Il free tier è contrattualmente vietato a chi opera in EEA, UK e Svizzera per servire utenti di quelle aree, quindi se l’app è destinata a un pubblico europeo il free non è una scelta legale.

    Il modello in sé è proprietary closed, non self-hostabile: una chiamata a Flash-Lite vuol dire necessariamente un round-trip verso Google, con le regole di Google.

    Cosa non gli mando

    Per coerenza con quanto sopra, non gli passo mai screenshot con IP privati di reti che gestisco, hostname interni, password o token in chiaro, output di log non pubblici, asset di clienti, contenuti riservati. Se l’immagine ha porzioni sensibili, le copro a colpi di rettangolo nero prima di mandarla, oppure ripiego sul fallback locale.

    Il fallback è doppio: per OCR puro su immagini semplici uso tesseract sul portatile, gratis e offline; per immagini più ostiche dove serve un minimo di “comprensione” del layout, mi rivolgo a un modello vision-capable locale via Ollama sul server di casa. Latenza più alta, qualità un po’ sotto Flash-Lite sulle calligrafie, ma tutto resta in casa.

    In pratica

    Flash-Lite lo apro quando il testo da estrarre è pubblico per natura (etichette di hardware in commercio, screenshot di output che finiranno comunque sul blog, foto di pagine di datasheet reperibili online) e voglio una resa che tesseract non mi dà. Per tutto ciò che ha contesto sensibile, anche solo di striscio, tesseract o Ollama in casa, senza eccezioni. La comodità non vale lo scambio.


    Immagine generata con Cloudflare Workers AI / FLUX.