Vai al contenuto principale

Tutorial Gemma 4: crea un agente di coding IA locale con Gradio e Ollama

Scopri come creare un assistente di coding IA completamente locale e multimodale con Gemma 4 via Ollama, con tool agentici integrati in una UI Gradio.
Aggiornato 29 set 2026  · 12 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

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.

Model performance vs Size

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:

Visual Guide to Gemma 4

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/chat di 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.

Final demo

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:

  • gradio per l'editor a pannelli e l'interfaccia chat

  • requests per comunicare con l'API HTTP di Ollama

  • pillow per 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() usa eval() con __builtins__ impostato a un dict vuoto, impedendo l'accesso ai built-in di Python al di fuori delle funzioni di math esplicitamente 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 stringa base64 così da poterla passare al campo images di 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:

Demo with code generation

  • 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

Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

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.

Argomenti
Intelligenza artificiale

Impara con DataCamp

Corso

AI pratica con Google Gemini e NotebookLM

2 ore
9.8K
Impara a usare Gemini e NotebookLM per automatizzare le attività, aumentare la produttività e lavorare in modo più intelligente nell'ecosistema AI di Google.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

Mostra AltroMostra Altro