Corso
I LLM locali hanno raggiunto un punto di svolta. Con la finestra di contesto lunga, il supporto nativamente multimodale di Gemma 4 e l'accessibilità di Ollama, ora è pratico eseguire un assistente di coding agentico e capace interamente sulla tua macchina.
In questo tutorial ti mostrerò come creare un assistente di coding usando Gemma 4 via Ollama, con un'interfaccia Gradio. L'app ha un layout a pannelli affiancati con un editor di codice live a sinistra e un pannello chat agentico a destra. Puoi caricare immagini o file di codice come contesto, abilitare l'uso di tool così che il modello possa eseguire e validare codice, e attivare una modalità di "pensiero" per i problemi più difficili.
Alla fine di questo tutorial, avrai un'app locale funzionante che può:
- Scrivere, spiegare e fare debug di codice in oltre 15 linguaggi
- Eseguire codice Python in un sottoprocesso isolato e restituire i risultati
- Accettare immagini e file di testo come contesto multimodale
- Trasmettere le risposte in tempo reale da un modello Gemma 4 in esecuzione locale
- Operare come un loop agentico, chiamando tool e iterando in base ai risultati
Il codice completo di questo tutorial è disponibile qui.
Che cos'è Gemma 4?
Gemma 4 è la famiglia di modelli a pesi aperti di Google DeepMind, pensata sia per il deployment locale sia per la ricerca. Si basa sulla linea Gemma con un migliore seguito delle istruzioni, finestre di contesto più lunghe e gestione nativa dell'input multimodale, ed è costruita sulla stessa infrastruttura di ricerca di Gemini 3.

Figura: prestazioni del modello vs dimensione (fonte: blog Gemma 4)
Modelli come Gemma-4-26B(MOE) e Gemma-4-31B raggiungono punteggi Elo paragonabili a modelli molto più grandi, indicando un ottimo rapporto prestazioni-per-parametro. Il modello 31B è attualmente al 3º posto tra i modelli open al mondo nella classifica Arena AI text, e il modello 26B occupa la 6ª posizione. Questo rende Gemma 4 particolarmente adatto a deployment locali e con risorse limitate senza sacrificare le capacità.
La famiglia di modelli Gemma 4
Gemma 4 è rilasciato in quattro dimensioni versatili, tra cui Effective 2B (E2B), Effective 4B (E4B), 26B Mixture of Experts (MoE) e 31B Dense. La famiglia si suddivide in due livelli distinti in base al target di deployment:
|
Modello |
Architettura |
Parametri totali |
Parametri attivi/effettivi |
Lunghezza contesto |
Modalità |
|
Gemma-4-31B |
Dense Transformer |
31B |
31B |
256K token |
Testo, Visione, Video |
|
Gemma-4-26B-A4B |
MoE (128 esperti) |
26B |
3,8B attivi |
256K token |
Testo, Visione, Video |
|
Gemma-4-E4B |
Dense Transformer |
7,9B (con embedding) |
4,5B effettivi |
128K token |
Testo, Audio, Visione, Video |
|
Gemma-4-E2B |
Dense Transformer |
5,1B (con embedding) |
2,3B effettivi |
128K token |
Testo, Audio, Visione, Video |
Entriamo più a fondo in ogni variante:

Figura: guida visiva a Gemma 4 (fonte)
- 31B Dense è il modello di punta ottimizzato per il deployment in data center e carichi di lavoro di ragionamento complessi. Supporta una finestra di contesto da 256K token con una sliding window da 1024 token per un'elaborazione efficiente dei contesti lunghi.
- 26B-A4B (MoE) è il primo modello MoE di Gemma che instrada i token attraverso 128 esperti mantenendo attivi solo 3,8B parametri per forward pass. Offre qualità quasi da 31B a una frazione del costo computazionale per token, rendendolo adatto a serving ad alto throughput.
- E4B ed E2B sono il livello on-device e mobile. A differenza delle varianti più grandi, includono input audio nativo per il riconoscimento vocale insieme a visione e video, rendendoli i modelli più capaci in termini multimodali della famiglia per il deployment edge.
Tutti e quattro i modelli sono disponibili sotto licenza Apache 2.0 e possono essere distribuiti in locale tramite Ollama, vLLM, llama.cpp o Unsloth.
Per i compiti di coding, Gemma 4 eccelle in:
- Scrittura di codice completo e strutturato con spiegazioni
- Ragionamento su codebase esistenti se fornite come contesto
- Uso di tool e workflow agentici se abbinato a un livello di orchestrazione
- Gestione di immagini insieme al codice (es. leggere uno screenshot di UI e generare HTML corrispondente)
In questo tutorial usiamo la variante gemma4:e4b (9,6GB) via Ollama, una versione quantizzata ben adatta all'inferenza locale su hardware consumer.
Eseguire Gemma 4 via Ollama
Ollama gestisce download, quantizzazione, serving del modello e fornisce un'API HTTP compatibile con OpenAI. In questo tutorial, Ollama funge da backend di inferenza e la nostra app comunica con esso su localhost:11434.
Per scaricare ed eseguire il modello:
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma4:e4b
ollama serve
I comandi sopra installano Ollama in locale usando lo script di installazione ufficiale. Poi scarichiamo la variante del modello gemma 4:e4b in locale per l'inferenza on-device. Infine, ollama serve avvia il server Ollama, consentendo alla nostra app di inviare richieste al modello.
Una volta in esecuzione il server, l'app Gradio si connette automaticamente.
Demo Gemma 4: crea un assistente di coding con Ollama
In questa sezione costruiremo passo dopo passo l'assistente di codice. A grandi linee, ecco cosa fa l'app:
-
Accetta un messaggio in linguaggio naturale nel pannello chat, con eventuali allegati di immagini o file
-
Inietta il codice attuale dell'editor come contesto per il modello
-
Invia una richiesta in streaming a Gemma 4 tramite l'endpoint
/api/chatdi Ollama -
Facoltativamente chiama tool (esecuzione di codice, valutazione matematica) in un loop agentico
-
Inserisce nell'editor live i blocchi di codice estratti dalla risposta
Costruiamola passo dopo passo.

Passo 1: installa le dipendenze
L'app richiede un piccolo set di librerie per la UI, la gestione delle immagini e la comunicazione HTTP. Poiché il modello gira in locale tramite Ollama, non ci sono dipendenze da SDK cloud.
pip install gradio requests pillow
In questo progetto useremo:
-
gradioper l'editor a pannelli e l'interfaccia chat -
requestsper comunicare con l'API HTTP di Ollama -
pillowper la gestione delle immagini nella UI
Questo mantiene l'ambiente leggero e funziona su qualsiasi macchina che esegue Ollama, inclusi macOS con Apple Silicon, Linux e Windows via WSL.
Passo 2: configurazione e costanti
Prima di scrivere la logica, definiamo gli import, il nome del modello, la base URL di Ollama, i linguaggi supportati e un prompt di sistema predefinito. Queste costanti controllano il comportamento dell'intera app.
import base64
import json
import math
import os
import re
import subprocess
import tempfile
from pathlib import Path
import gradio as gr
import requests
OLLAMA_BASE = "http://localhost:11434"
MODEL = "gemma4:e4b"
LANGUAGES = [
"python", "javascript", "typescript", "bash", "sql",
"rust", "go", "java", "c", "cpp", "html", "css",
"json", "yaml", "markdown", "plaintext",
]
TEXT_EXTS = {
".py", ".js", ".ts", ".jsx", ".tsx", ".html", ".css",
".json", ".yaml", ".yml", ".toml", ".md", ".txt",
".csv", ".sql", ".sh", ".bash", ".rs", ".go",
".java", ".c", ".cpp", ".h", ".hpp", ".rb", ".php",
}
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"}
DEFAULT_SYSTEM = """\
You are an expert coding assistant. When you write code:
- Always wrap it in a markdown code block with the language tag
- Write complete, working code — not fragments
- Briefly explain what the code does
You have a code-runner tool. Use it to validate logic when helpful.\
"""
Gli insiemi TEXT_EXTS e IMAGE_EXTS gestiscono due comportamenti di allegati diversi. I file di testo/codice vengono letti e iniettati come contesto nel prompt, mentre i file immagine sono base64-encoded e inviati nel campo images per le capacità di visione di Gemma 4.
Il prompt DEFAULT_SYSTEM modella lo stile di generazione del codice del modello, richiedendo specificamente blocchi di codice completi con tag del linguaggio, poiché l'app li analizza per spingere il codice nell'editor.
Passo 3: definisci i tool agentici
L'assistente può operare in modalità agentica chiamando tool durante l'inferenza. Definiamo due tool nello schema standard di function-calling supportato da Ollama:
TOOLS = [
{
"type": "function",
"function": {
"name": "run_code",
"description": (
"Execute Python code in a sandboxed subprocess and return "
"stdout + stderr. Use this to validate, test, or demonstrate code."
),
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to run (max ~50 lines, 5 s timeout).",
}
},
"required": ["code"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression precisely.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Python-compatible math expression, e.g. 'math.sqrt(2) * 100'",
}
},
"required": ["expression"],
},
},
},
]
Lo snippet sopra definisce il tool run_code, che esegue Python in un file temporaneo tramite un sottoprocesso con timeout di 5 secondi e cattura sia stdout che stderr. Questo consente al modello di validare la logica, eseguire esempi o produrre output live, non solo generare testo statico.
Il tool calculate, invece, valuta espressioni matematiche in uno spazio dei nomi ristretto usando solo il modulo math e built-in sicuri. Gestisce i casi in cui il modello necessita di risultati numerici precisi senza avviare un intero processo Python.
Passo 4: esecuzione dei tool
Il layer di esecuzione mappa i nomi dei tool alle loro implementazioni. Viene chiamato durante il loop agentico ogni volta che il modello restituisce un blocco tool_calls.
def _run_python(code: str) -> str:
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
tmp = f.name
try:
r = subprocess.run(
["python3", tmp],
capture_output=True, text=True, timeout=5,
)
out = (r.stdout + r.stderr).strip()
return out[:3000] if out else "(no output)"
except subprocess.TimeoutExpired:
return "⏱ Timed out (>5 s)"
except Exception as e:
return f"Error: {e}"
finally:
os.unlink(tmp)
def _calculate(expr: str) -> str:
ns = {k: getattr(math, k) for k in dir(math) if not k.startswith("_")}
ns.update({"abs": abs, "round": round})
try:
return str(eval(expr, {"__builtins__": {}}, ns)) # noqa: S307
except Exception as e:
return f"Error: {e}"
def execute_tool(name: str, args: dict) -> str:
if name == "run_code":
return _run_python(args.get("code", ""))
if name == "calculate":
return _calculate(args.get("expression", ""))
return f"Unknown tool: {name}"
Alcune scelte progettuali di sicurezza degne di nota nello stack di esecuzione dei tool sono:
-
_run_python()scrive su un file temporaneo e lo elimina, evitando file residui anche in caso di errore -
L'output è limitato a 3.000 caratteri per evitare di inondare il contesto con stampe massive
-
La funzione
_calculate()usaeval()con__builtins__impostato a un dict vuoto, impedendo l'accesso ai built-in di Python al di fuori delle funzioni dimathesplicitamente consentite -
Il timeout è una finestra rigida di 5 secondi, abbastanza lunga per la maggior parte delle validazioni, ma breve per prevenire processi fuori controllo
Passo 5: utility di supporto
Prima di costruire la logica core della chat, ci servono alcune funzioni utility che gestiscano percorsi file, codifica immagini ed estrazione del codice. Queste funzioni di supporto sono chiamate in tutto il pipeline di streaming.
def encode_image(path: str) -> str | None:
if not path:
return None
try:
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
except Exception:
return None
def file_as_context(path: str) -> str | None:
if not path:
return None
p = Path(path)
if p.suffix.lower() not in TEXT_EXTS:
return None
try:
content = p.read_text(encoding="utf-8", errors="replace")[:8000]
lang = p.suffix.lstrip(".")
return f"\n\n**Attached file — {p.name}:**\n```{lang}\n{content}\n```"
except Exception:
return None
def resolve_gradio_path(val) -> str | None:
if val is None:
return None
if isinstance(val, Path):
s = str(val)
return s if s.strip() else None
if isinstance(val, str):
s = val.strip()
return s if s else None
if isinstance(val, dict):
p = val.get("path")
if isinstance(p, str) and p.strip():
return p.strip()
nested = val.get("file")
if isinstance(nested, dict):
np = nested.get("path")
if isinstance(np, str) and np.strip():
return np.strip()
return None
name = getattr(val, "name", None)
if isinstance(name, str) and name.strip():
return name.strip()
return None
def is_image_path(path: str | None) -> bool:
return bool(path) and Path(path).suffix.lower() in IMAGE_EXTS
def extract_last_code_block(text: str) -> tuple[str | None, str]:
blocks = re.findall(r"```(\w*)\n(.*?)```", text, re.DOTALL)
if blocks:
lang, code = blocks[-1]
return code.strip(), lang.strip() or "python"
return None, "python"
def ollama_ok() -> bool:
try:
requests.get(f"{OLLAMA_BASE}/", timeout=2)
return True
except Exception:
return False
def _gradio_content_to_text(content) -> str:
if content is None:
return ""
if isinstance(content, str):
return content
if isinstance(content, list):
parts: list[str] = []
for block in content:
if isinstance(block, dict):
if block.get("type") == "text":
parts.append(str(block.get("text", "")))
elif "text" in block:
parts.append(str(block["text"]))
elif isinstance(block, str):
parts.append(block)
return "".join(parts)
return str(content)
def _append_chat_turn(
history: list | None, user_text: str, assistant_text: str
) -> list:
base = list(history) if history else []
return base + [
{"role": "user", "content": user_text},
{"role": "assistant", "content": assistant_text},
]
def run_code_btn(code: str) -> str:
if not code.strip():
return "Nothing to run."
result = _run_python(code)
return result
Queste utility consentono una gestione fluida di input multimodali e output strutturati:
-
encode_image(): converte un file immagine in una stringabase64così da poterla passare al campoimagesdi Ollama. Questo permette a Gemma di elaborare immagini insieme al testo per il ragionamento multimodale. -
file_as_context(): legge file di testo/codice e li inietta direttamente nel prompt come blocco markdown formattato. Questo evita la necessità di un sistema di retrieval separato, poiché tutto è passato inline come contesto. -
resolve_gradio_path(): normalizza i diversi formati di file restituiti da Gradio in un percorso file utilizzabile. Questo previene bug edge-case nella gestione dei file. -
is_image_path(): controllo rapido per verificare se un dato file è un'immagine o no. Aiuta a instradare correttamente gli input immagine. -
extract_last_code_block(): estraiamo l'ultimo blocco di codice dalla risposta del modello. -
ollama_ok(): verifichiamo che il server locale di Ollama sia in esecuzione prima di effettuare richieste. -
_gradio_content_to_text()e_append_chat_turn(): appiattiamo il formato strutturato dei messaggi di Gradio in testo semplice per un'elaborazione coerente e manteniamo la cronologia della chat. -
run_code_btn(): infine, eseguiamo il codice generato quando l'utente preme il pulsante Run code nella UI usando questa funzione.
Ora che abbiamo predisposto le utility, possiamo implementare la funzione core chat().
Passo 6: generatore di streaming della chat
Questa funzione guida l'intero loop di interazione, dalla preparazione degli input allo streaming delle risposte e alla gestione dell'esecuzione dei tool.
A grandi linee, la funzione chat():
- costruisce la cronologia della conversazione
- arricchisce l'input attuale dell'utente con contesto (codice, file, immagini)
- invia una richiesta in streaming a Ollama
- esegue facoltativamente tool (modalità agentica)
- fornisce risposte parziali per aggiornamenti in tempo reale della UI
def chat(
message, history, image_path, file_path,
editor_code, language, system_prompt,
agentic, thinking, temperature,
):
La funzione segue la seguente sequenza passo dopo passo:
Passo 1: costruisci la cronologia dei messaggi
Iniziamo convertendo i turni precedenti del chatbot nel formato messaggi di Ollama. Il prompt di sistema viene preceduto per guidare il comportamento del modello, garantendo che il modello abbia l'intero contesto conversazionale prima di generare una risposta.
for h in history or []:
if not isinstance(h, dict):
continue
role = h.get("role")
if role not in ("user", "assistant"):
continue
messages.append(
{"role": role, "content": _gradio_content_to_text(h.get("content"))}
)
content = message
Passo 2: componi il turno utente
Poi costruiamo il messaggio utente corrente iniettando contesto aggiuntivo. Se l'utente sta lavorando con del codice, aggiungiamo i contenuti dell'editor:
if editor_code.strip() and editor_code.strip() != STARTER_CODE.strip():
content += (
f"\n\n**Current code in editor ({language}):**\n"
f"```{language}\n{editor_code}\n```"
)
Allo stesso modo, i file caricati sono aggiunti come contesto inline e le immagini vengono codificate in base64 e passate tramite il campo images. Ciò consente al modello di ragionare insieme su testo, codice e immagini.
Passo 3: invia la richiesta a Ollama
Il payload include il nome del modello, la cronologia messaggi, il flag di streaming e le opzioni come segue:
payload = {
"model": MODEL,
"messages": messages,
"stream": True,
"options": options,
}
if agentic:
payload["tools"] = TOOLS
Se la modalità di pensiero è abilitata, viene impostato options["think"] = True, che attiva un ragionamento a catena esteso nei modelli supportati.
Passo 4: gestisci le chiamate ai tool (modalità agentica)
Se la modalità agentica è attiva, il modello può restituire chiamate ai tool invece del testo. Questo accade quando ogni tool viene eseguito e i risultati sono aggiunti alla conversazione. Poi, viene inviata una richiesta di follow-up per continuare la generazione.
if agentic and msg.get("tool_calls"):
for tc in msg["tool_calls"]:
fn_name = tc["function"]["name"]
fn_args = tc["function"]["arguments"]
result = execute_tool(fn_name, fn_args)
if fn_name == "run_code" and fn_args.get("code"):
new_code = fn_args["code"]
messages.append(msg)
messages.append({"role": "tool", "content": result})
resp2 = requests.post(f"{OLLAMA_BASE}/api/chat", ...)
Questo crea un loop agentico a singolo turno in cui il modello chiama un tool, vede il risultato e poi continua a generare la risposta finale. I log delle chiamate ai tool sono anteposti alla chat per l'utente.
Passo 5: estrai il codice e aggiorna l'editor
Infine, una volta completato lo streaming, estraiamo l'ultimo blocco di codice dalla risposta:
extracted, _ = extract_last_code_block(full_response)
if extracted:
new_code = extracted
In sintesi, il modello genera codice, l'editor si aggiorna automaticamente e non serve fare copia-incolla manuale.
Passo 7: layout UI in Gradio
Con la logica core pronta, progettiamo ora l'interfaccia utente con Gradio. L'obiettivo è creare un layout che supporti sia i flussi di lavoro di coding sia l'interazione assistita dall'IA fianco a fianco.
Usiamo gr.Blocks per definire un layout a due colonne:
- il pannello sinistro è focalizzato sull'editing ed esecuzione del codice
- il pannello destro gestisce chat, allegati e controlli del modello
with gr.Blocks(title="Gemma 4 · Code Assistant") as demo:
with gr.Row(equal_height=False):
# LEFT: Code Editor
with gr.Column(scale=11):
with gr.Row():
lang_sel = gr.Dropdown(choices=LANGUAGES, value="python", label="Language")
run_btn = gr.Button("Run Code", elem_classes=["run-btn"])
clear_ed = gr.Button("Clear")
code_editor = gr.Code(
value=STARTER_CODE,
language="python",
label="Editor",
lines=24,
interactive=True,
)
run_output = gr.Textbox(
label="Output",
lines=6,
interactive=False,
elem_id="run-output",
)
# RIGHT: Chat
with gr.Column(scale=9):
chatbot = gr.Chatbot(value=[], elem_id="chatbot", height=430)
with gr.Row():
image_upload = gr.Image(label="Image (vision)", type="filepath")
file_upload = gr.File(label="Code / text file")
with gr.Row():
msg_input = gr.Textbox(placeholder="Ask the agent...", scale=6)
send_btn = gr.Button("Send", variant="primary")
with gr.Row():
agentic_cb = gr.Checkbox(label="Enable Agentic", value=True)
thinking_cb = gr.Checkbox(label="Enable Thinking", value=False)
clear_chat = gr.Button("Clear chat")
with gr.Accordion("Settings", open=False):
sys_prompt = gr.Textbox(value=DEFAULT_SYSTEM, label="System prompt")
temperature = gr.Slider(minimum=0.0, maximum=2.0, value=0.7)
Vediamo come la UI lavora insieme a tutti i componenti descritti sopra:
- Spazio di lavoro del codice: il pannello sinistro include un selettore del linguaggio, un editor di codice e controlli per eseguire/azzerare. L'output dell'esecuzione è mostrato in una casella di testo evidenziata.
- Chat e interazione: il pannello destro contiene il chatbot per la conversazione, la casella di input per le richieste e il supporto per allegati come immagini per il ragionamento multimodale e file per l'iniezione di contesto. Ulteriori interruttori abilitano l'uso di tool agentici e modalità di ragionamento opzionali.
- Layout e usabilità: l'interfaccia usa un layout a due colonne con rapporto 55/45, dando priorità all'editor ma mantenendo la chat facilmente accessibile. Questo equilibrio assicura un flusso continuo tra coding, assistenza IA ed esecuzione.
Passo 8: collegamento degli eventi
Gli eventi di invio e submit condividono gli stessi input e output, quindi sia il click sul pulsante sia la pressione di Invio nella casella di testo attivano lo stesso comportamento:
_inputs = [
msg_input, chatbot, image_upload, file_upload,
code_editor, lang_sel,
sys_prompt, agentic_cb, thinking_cb, temperature,
]
_outputs = [chatbot, code_editor, msg_input]
send_btn.click(fn=respond, inputs=_inputs, outputs=_outputs)
msg_input.submit(fn=respond, inputs=_inputs, outputs=_outputs)
Qui il wrapper respond() risolve gli oggetti percorso file di Gradio in stringhe di percorso file semplici prima di passarli al generatore di streaming. Quindi produce (history, code, "") a ogni chunk in modo che la stringa vuota cancelli l'input del messaggio alla prima emissione.
Passo 9: tema e CSS
Per migliorare usabilità e coerenza visiva, aggiungiamo un tema personalizzato e un layer CSS sopra lo stile predefinito di Gradio. Questo conferisce all'app un aspetto curato ispirato a GitHub, con supporto per modalità chiara e scura. È del tutto opzionale.
Iniziamo definendo un tema base usando gr.themes.Base:
THEME = gr.themes.Base(
primary_hue = "blue",
secondary_hue = "slate",
neutral_hue = "slate",
font = [gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
font_mono = [gr.themes.GoogleFont("JetBrains Mono"), "monospace"],
)
Questo imposta una palette di colori coerente e una tipografia pulita (Inter) con un font monospace orientato agli sviluppatori (JetBrains Mono). Poi sovrascriviamo le principali variabili CSS per controllare l'aspetto dell'app:
gradio-app {
--body-background-fill: #f0f3f7;
--block-background-fill: #ffffff;
--body-text-color: #1f2328;
--button-primary-background-fill: #1a7f37;
}
body.dark gradio-app {
--body-background-fill: #0d1117;
--block-background-fill: #1c2128;
--body-text-color: #e6edf3;
--button-primary-background-fill: #238636;
}
Qui il primo blocco definisce il tema chiaro, mentre il secondo blocco sovrascrive i valori per la modalità scura. Sebbene sia opzionale, aggiungere tema personalizzato e CSS migliora notevolmente l'esperienza complessiva.
Con la UI completamente strutturata e stilizzata, siamo pronti per l'ultimo passo, ovvero avviare l'applicazione.
Passo 10: avvio
Infine, l'app viene avviata con:
if __name__ == "__main__":
print(f"Model : {MODEL}")
print(f"Ollama : {OLLAMA_BASE}")
if not ollama_ok():
print("Ollama not detected — run ollama serve before chatting")
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
theme=THEME,
css=CSS,
)
Qui, server_name="0.0.0.0" rende l'app accessibile sulla tua rete locale e impostare share=False mantiene tutto in locale. Il controllo ollama_ok() effettua una rapida richiesta GET a localhost:11434 e avvisa all'avvio se Ollama non è in esecuzione, invece di far emergere l'errore solo quando l'utente invia il primo messaggio.
Infine, vai su http://localhost:7860 quando l'app è in esecuzione.
Ecco l'intero flusso di esecuzione dalla prospettiva dell'utente:

-
L'utente scrive un messaggio nel pannello chat, caricando facoltativamente un'immagine o un file di codice e attivando le modalità agentic/thinking
-
Al submit, la funzione
respond()costruisce la richiesta completa a Ollama, includendo il codice attuale dell'editor e ogni allegato come contesto -
La richiesta viene trasmessa token per token e le risposte parziali sono inviate al chatbot di Gradio per il rendering dal vivo
-
Se la modalità agentica è attiva e il modello decide di chiamare un tool, il tool viene eseguito in locale, i risultati sono aggiunti alla conversazione e una richiesta successiva trasmette la risposta finale
-
Dopo il completamento dello streaming, l'ultimo blocco di codice delimitato nella risposta viene estratto e inserito automaticamente nell'editor
Conclusione
In questo tutorial abbiamo costruito un assistente di coding IA completamente locale usando Gemma 4, Ollama e Gradio. L'app supporta input multimodale, uso reale di tool, risposte in streaming e un editor di codice live, tutto in esecuzione sulla tua macchina senza API esterne.
L'approccio di full-context, che inietta direttamente il codice dell'editor in ogni prompt, è più semplice del RAG per un workflow a file singolo e funziona particolarmente bene per compiti come spiegare, refactorizzare o estendere il codice su cui stai lavorando attivamente.
Da qui, puoi estendere il progetto in diverse direzioni:
- Aggiungi un pannello ad albero dei file per supportare progetti multi-file e iniettare file selezionati come contesto
- Aggiungi il supporto per ulteriori modelli Ollama tramite un menu a discesa, permettendo agli utenti di passare tra Gemma 4, Llama 3 e altri
- Rendi persistente su disco la cronologia della conversazione così che le sessioni sopravvivano ai riavvii dell'app
Sono una Google Developers Expert in ML (Gen AI), una Kaggle 3x Expert e una Women Techmakers Ambassador con oltre 3 anni di esperienza nel tech. Ho co-fondato una startup health-tech nel 2020 e sto conseguendo un master in informatica al Georgia Tech, con specializzazione in machine learning.
Gemma 4 FAQ
Serve una GPU?
Non necessariamente. Gemma 4 e4b è un modello quantizzato che gira su CPU, anche se una GPU migliorerà significativamente la velocità di inferenza.
Qual è la differenza tra modalità agentica e chat normale?
In modalità normale, il modello trasmette solo testo. In modalità agentica, il modello può chiamare run_code o calculate, verificare i risultati e incorporarli prima di concludere la risposta.
Come si aggiorna automaticamente l'editor quando l'agente scrive codice?
La funzione di supporto extract_last_code_block() scandisce la risposta dell'assistente cercando l'ultimo blocco di codice delimitato e lo inserisce nell'editor una volta terminato lo streaming. Ecco perché il prompt di sistema istruisce il modello a racchiudere sempre il codice in blocchi delimitati con un tag di linguaggio.
Cosa succede se Ollama non è in esecuzione?
Il controllo ollama_ok() lo intercetta all'avvio e a ogni invio in chat. Se Ollama non è raggiungibile, la chat restituisce un messaggio di errore formattato invece di andare in crash.


