{"id":52,"date":"2026-05-12T21:30:00","date_gmt":"2026-05-12T19:30:00","guid":{"rendered":"https:\/\/rpi.temporiti.net\/wordpress\/?p=52"},"modified":"2026-05-30T14:05:58","modified_gmt":"2026-05-30T12:05:58","slug":"qwen-coder-opencode-debian","status":"publish","type":"post","link":"https:\/\/rpi.temporiti.net\/wordpress\/?p=52","title":{"rendered":"Code Review Locale con Qwen2.5-Coder e opencode su Debian"},"content":{"rendered":"<p>Qwen2.5-Coder \u00e8 diventato il mio revisore fisso per le code review che faccio in homelab. Avevo bisogno di un modello che capisse il codice senza che ogni snippet finisse fuori dalla mia rete, perch\u00e9 tanti progetti che mi passano davanti contengono pezzi di configurazione, token di servizio e hostname interni che non voglio veder uscire. La variante 14B su una scheda con 16 GB di VRAM regge una sessione di mezza giornata senza farmi pensare alla latenza, ed \u00e8 ormai la mia scelta principale per qualsiasi review che tocchi file di una certa lunghezza.<\/p>\n<p>Il modello \u00e8 rilasciato da Alibaba con licenza Apache 2.0, quindi posso usarlo liberamente anche per lavoro a pagamento. Lo tengo su un mini server Debian 12 headless, raggiungibile via SSH dal mio desktop. Ollama gira come servizio systemd, si lega di default su 127.0.0.1:11434, e su quella stessa macchina ho opencode installato. Quando devo fare review entro in SSH, apro tmux e lavoro l\u00ec dentro per tutto il tempo necessario.<\/p>\n<p>I parametri che mi interessano sono pochi: 14B di parametri, finestra di contesto base 32k token (la spingo a 16k effettivi via Modelfile per non sprecare VRAM), quantizzazione Q4_K_M predefinita di Ollama. Il knowledge cutoff \u00e8 met\u00e0 2024 e si sente quando arrivano librerie nuove, ma per code review su codice mio non \u00e8 un problema.<\/p>\n<h2>Setup base con Ollama<\/h2>\n<p>Su Debian 12 lo script ufficiale di Ollama tira gi\u00f9 tutto in un colpo, compreso il service unit systemd.<\/p>\n<pre><code class=\"language-bash\">\ncurl -fsSL https:\/\/ollama.com\/install.sh | sh\nsudo systemctl enable --now ollama\nollama pull qwen2.5-coder:14b\nollama run qwen2.5-coder:14b\n<\/code><\/pre>\n<p>Con 8 GB di VRAM la 14B fatica davvero, e in quel caso scendo alla 7B che resta comunque dignitosa per la review riga per riga. Quando devo dargli in pasto file lunghi, parto dal Modelfile esistente e ne creo una variante con un contesto pi\u00f9 ampio.<\/p>\n<pre><code class=\"language-bash\">\nollama show qwen2.5-coder:14b --modelfile &gt; \/tmp\/qwen-coder.Modelfile\n<\/code><\/pre>\n<p>Apro il file con il mio editor, aggiungo <code>PARAMETER num_ctx 16384<\/code> e ricostruisco la variante con <code>ollama create qwen-coder-long -f \/tmp\/qwen-coder.Modelfile<\/code>. La uso solo quando serve davvero, perch\u00e9 la finestra grande mangia VRAM e mi costringe a tenere meno modelli caricati in parallelo.<\/p>\n<h2>Setup avanzato con opencode<\/h2>\n<p>opencode \u00e8 una TUI agentica che si appoggia a un provider OpenAI-compatible, e per le code review fa esattamente quello che mi serve: monta la working directory, mi permette di passare file in contesto con un comando, e mantiene la cronologia della sessione su disco. Ollama da tempo espone un endpoint compatibile su <code>\/v1<\/code>, quindi basta dichiararlo come provider locale dentro <code>~\/.config\/opencode\/opencode.json<\/code>.<\/p>\n<pre><code class=\"language-json\">\n{\n  \"provider\": {\n    \"ollama-local\": {\n      \"npm\": \"@ai-sdk\/openai-compatible\",\n      \"options\": {\n        \"apiKey\": \"ollama\",\n        \"baseURL\": \"http:\/\/localhost:11434\/v1\"\n      },\n      \"models\": {\n        \"qwen2.5-coder:14b\": { \"name\": \"Qwen 2.5 Coder 14B\" }\n      }\n    }\n  }\n}\n<\/code><\/pre>\n<p>Salvato il file, mi sposto nella cartella del progetto e lancio la sessione agentica.<\/p>\n<pre><code class=\"language-bash\">\ncd ~\/Documenti\/progetti\/router-config\nopencode . --model ollama-local\/qwen2.5-coder:14b\n<\/code><\/pre>\n<p>A quel punto sono nella TUI con la working directory gi\u00e0 montata, senza wrapper intermedi e senza dovermi inventare integrazioni a parte.<\/p>\n<h2>Un esempio di sessione reale<\/h2>\n<p>Marted\u00ec pomeriggio, verso le 16, dovevo rivedere un retry decorator scritto in Python da una persona del team che gestisce una pipeline ETL. Il decorator avvolgeva chiamate HTTP verso un endpoint che ogni tanto restituiva 502 transitori, e la sensazione era che facesse troppi retry e troppo rapidi, saturando il rate limit a valle. Ho aperto la cartella del progetto in opencode, ho chiesto a Qwen2.5-Coder di leggere il file <code>retry.py<\/code> e di valutare se il backoff fosse implementato correttamente.<\/p>\n<p>La risposta \u00e8 arrivata in una trentina di secondi e ha colto due cose che mi erano sfuggite: il jitter era applicato solo al primo tentativo (per un errore di indentazione del randint dentro il loop), e il decorator non distingueva tra eccezioni di rete e errori HTTP 4xx, finendo per ritentare anche su 401 e 403. Ho chiesto una proposta di refactoring tenendo le firme pubbliche identiche e ho ottenuto una versione con backoff esponenziale corretto, jitter su ogni tentativo, e una whitelist esplicita di status code retriabili. Il merge request \u00e8 uscito in serata.<\/p>\n<h2>Cosa fa bene<\/h2>\n<p>Sul codice scritto in linguaggi mainstream (Python, TypeScript, Go, bash, SQL) la qualit\u00e0 della review \u00e8 solida. Coglie pattern di concorrenza fragile, vede dove manca la gestione delle eccezioni, suggerisce refactor di funzioni che fanno troppe cose. Sui Dockerfile e sui file Ansible \u00e8 meno brillante ma sempre utile, soprattutto per stanare uso di <code>latest<\/code> come tag e privilegi superflui nei container.<\/p>\n<h2>Cosa fa meno bene<\/h2>\n<p>Su librerie uscite dopo la met\u00e0 del 2024 inventa firme che sembrano plausibili ma non esistono, quindi le sue proposte vanno sempre verificate contro la documentazione vera. La 14B in Q4 ogni tanto perde il filo su file molto lunghi anche con <code>num_ctx<\/code> alzato, e la latenza per risposta lunga non \u00e8 paragonabile a quella di un servizio cloud su acceleratori dedicati: con prompt da 6k token la prima parola arriva in 4-5 secondi.<\/p>\n<h2>Privacy: tutto resta sul mio host<\/h2>\n<p>Qwen2.5-Coder gira interamente dentro il mio server: nessun provider terzo coinvolto, nessun upload, nessuna telemetria di default. Ollama si lega su 127.0.0.1:11434 e non espone nulla all&#8217;esterno. Gli unici log sono in <code>~\/.ollama\/logs\/<\/code>, posso cancellarli quando voglio con un <code>rm<\/code>, e non c&#8217;\u00e8 alcuna policy esterna da rileggere a ogni aggiornamento. Rispetto ai modelli cloud, dove ogni snippet passa per i server di chi offre il servizio (con retention variabile e, in qualche caso, riuso per training se non si fa opt-out), qui la differenza \u00e8 radicale: il codice non esce mai dal mio host. La licenza Apache 2.0 di Qwen 2.5 Coder mi lascia libero di usarlo anche in contesti professionali senza ulteriori vincoli.<\/p>\n<h2>In pratica<\/h2>\n<p>Qwen2.5-Coder 14B \u00e8 il modello che apro quando la review tocca codice mio o di progetti che gestisco direttamente. Per ragionamento esplicito su scelte architetturali passo a DeepSeek-R1 7B, per domande generiche e veloci sul laptop mi appoggio a qwen3:8b, e per risposte brevi e multimodali sul Mac Mini tengo Gemma3 4B. La scelta dipende da quanto contesto serve, da quanto tempo voglio aspettare, e da quale macchina \u00e8 accesa in quel momento.<\/p>\n<hr>\n<blockquote>\n<p>Immagine generata con Cloudflare Workers AI \/ FLUX.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Qwen2.5-Coder \u00e8 diventato il mio revisore fisso per le code review che faccio in homelab. Avevo bisogno di un modello che capisse il codice senza che ogni snippet finisse fuori dalla mia rete, perch\u00e9 tanti progetti che mi passano davanti contengono pezzi di configurazione, token di servizio e hostname interni che non voglio veder uscire. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":53,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-52","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-locale"],"_links":{"self":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/52","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=52"}],"version-history":[{"count":5,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/52\/revisions"}],"predecessor-version":[{"id":362,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/52\/revisions\/362"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=\/wp\/v2\/media\/53"}],"wp:attachment":[{"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=52"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=52"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rpi.temporiti.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=52"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}