Ga naar hoofdinhoud

Gemma 4-tutorial: bouw een lokale AI coding agent met Gradio en Ollama

Leer hoe je een volledig lokale, multimodale AI coding assistant bouwt, aangedreven door Gemma 4 via Ollama, met agentische tool-use in een Gradio-UI.
Bijgewerkt 2 okt 2026  · 12 min lezen

Verken met AI

ChatGPTClaudePerplexity

Lokale LLM’s zitten op een keerpunt. Met Gemma 4 met z’n lange contextwindow, native multimodale ondersteuning en de toegankelijkheid van Ollama is het nu praktisch haalbaar om een capabele, agentische coding assistant volledig op je eigen machine te draaien.

In deze tutorial laat ik je zien hoe je een coding assistant bouwt met Gemma 4 via Ollama, met een Gradio-interface. De app heeft een split-pane lay-out met links een live code-editor en rechts een agentisch chatpaneel. Je kunt afbeeldingen of codebestanden uploaden als context, tools inschakelen zodat het model code kan uitvoeren en valideren, en een denkmode inschakelen voor lastigere problemen.

Aan het einde van deze tutorial heb je een werkende lokale app die kan:

  • Code schrijven, uitleggen en debuggen in 15+ talen
  • Python-code uitvoeren in een gesandboxte subprocess en resultaten retourneren
  • Afbeeldingen en tekstbestanden accepteren als multimodale context
  • Antwoorden in realtime streamen vanuit een lokaal draaiend Gemma 4-model
  • Draaien als een agentische loop, tools aanroepen en opvolgen op basis van resultaten

De volledige code voor deze tutorial is hier beschikbaar.

Wat is Gemma 4?

Gemma 4 is de open-weights modelfamilie van Google DeepMind, ontworpen voor zowel lokale deployment als onderzoek. Het bouwt voort op de Gemma-lijn met verbeterde instructieopvolging, langere contextwindows en native multimodale inputafhandeling en is gebouwd op dezelfde onderzoeksinfrastructuur als Gemini 3.

Model performance vs Size

Figuur: Modelprestatie vs grootte (bron: Gemma 4-blog)

Modellen zoals Gemma-4-26B(MOE) en Gemma-4-31B behalen Elo-scores die vergelijkbaar zijn met veel grotere modellen, wat wijst op sterke performance-per-parameter. Het 31B-model staat momenteel als 3e open model ter wereld op de Arena AI text leaderboard, en het 26B-model bezet de 6e plek. Dit maakt Gemma 4 bijzonder geschikt voor lokale en resource-beperkte deployments zonder capaciteitsverlies.

De Gemma 4-modelfamilie

Gemma 4 is uitgebracht in vier veelzijdige groottes, waaronder Effective 2B (E2B), Effective 4B (E4B), 26B Mixture of Experts (MoE) en 31B Dense-modellen. De familie valt uiteen in twee duidelijke tiers op basis van het deploymentdoel:

Model

Architectuur

Totaal aantal params

Actieve/Effectieve params

Contextlengte

Modaliteiten

Gemma-4-31B

Dense Transformer

31B

31B

256K tokens

Tekst, Visie, Video

Gemma-4-26B-A4B

MoE (128 experts)

26B

3,8B actief

256K tokens

Tekst, Visie, Video

Gemma-4-E4B

Dense Transformer

7,9B (met embeddings)

4,5B effectief

128K tokens

Tekst, Audio, Visie, Video

Gemma-4-E2B

Dense Transformer

5,1B (met embeddings)

2,3B effectief

128K tokens

Tekst, Audio, Visie, Video

Laten we dieper ingaan op elk variant:

Visual Guide to Gemma 4

Figuur: Visuele gids voor Gemma 4 (bron)

  • 31B Dense is het vlaggenschipmodel, geoptimaliseerd voor datacenter-deployment en complexe redeneertaken. Het ondersteunt een contextwindow van 256K tokens met een sliding window van 1024 tokens voor efficiënte long-contextverwerking.
  • 26B-A4B (MoE) is Gemma’s eerste MoE-model dat tokens routeert via 128 experts, terwijl slechts 3,8B parameters actief zijn per forward pass. Dit levert kwaliteit die bijna gelijk is aan 31B tegen een fractie van de rekenkosten per token, waardoor het goed geschikt is voor high-throughput serving.
  • E4B en E2B vormen de on-device en mobile tier. In tegenstelling tot de grotere varianten ondersteunen ze native audio-input voor spraakherkenning naast visie en video, waardoor het de meest multimodaal capabele modellen in de familie zijn voor edge-deployment.

Alle vier de modellen zijn beschikbaar onder de Apache 2.0-licentie en kunnen lokaal worden gedeployed via Ollama, vLLM, llama.cpp of Unsloth.

Voor codeertaken blinkt Gemma 4 uit in:

  • Volledige, gestructureerde code schrijven met uitleg
  • Redeneren over bestaande codebases wanneer die als context worden aangeleverd
  • Tool-use en agentische workflows in combinatie met een orkestratielaag
  • Afbeeldingen verwerken naast code (bijv. een UI-screenshot lezen en bijpassende HTML genereren)

In deze tutorial gebruiken we de gemma4:e4b (9,6GB)-variant via Ollama, een gequantiseerde versie die goed geschikt is voor lokale inferentie op consumentenhardware. 

Gemma 4 draaien via Ollama

Ollama verzorgt het downloaden, quantization, serving van modellen en biedt een OpenAI-compatibele HTTP-API. In deze tutorial fungeert Ollama als de inferentie-backend en communiceert onze app ermee via localhost:11434.

Om het model te pullen en te draaien:

curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma4:e4b 
ollama serve

Bovenstaande commando’s installeren Ollama lokaal met het officiële installatiescript. Vervolgens downloaden we de gemma 4:e4b  modelvariant lokaal voor on-device inferentie. Tot slot start ollama serve de Ollama-server, zodat onze app verzoeken naar het model kan sturen.

Zodra de server draait, maakt de Gradio-app er automatisch verbinding mee.

Gemma 4-demo: bouw een coding assistant met Ollama

In dit onderdeel bouwen we stap voor stap de code-assistent. Op hoofdlijnen doet de app het volgende:

  • Accepteert een bericht in natuurlijke taal in het chatpaneel, met optionele afbeelding- of bestandsbijlagen

  • Injecteert de huidige code uit de editor als context voor het model

  • Stuurt een streamingverzoek naar Gemma 4 via Ollama’s /api/chat-endpoint

  • Roept optioneel tools aan (code-uitvoering, wiskundige evaluatie) in een agentische loop

  • Pusht uit de respons geëxtraheerde codeblokken naar de live editor

Laten we dit stap voor stap opbouwen.

Final demo

Stap 1: Installeer dependencies

De app heeft een kleine set libraries nodig voor de UI, afbeeldingsverwerking en HTTP-communicatie. Omdat het model lokaal via Ollama draait, zijn er geen cloud-SDK-dependencies.

pip install gradio requests pillow

In dit project gebruiken we:

  • gradio voor de split-pane editor en chat-UI

  • requests om te communiceren met de Ollama HTTP-API

  • pillow voor ondersteuning van afbeeldingsverwerking in de UI

Dit houdt de omgeving lichtgewicht en werkt op elke machine met Ollama, waaronder macOS met Apple Silicon, Linux en Windows via WSL.

Stap 2: Configuratie en constanten

Voordat we logica schrijven, definiëren we de imports, de modelnaam, de Ollama basis-URL, ondersteunde talen en een standaard systeemprompt. Deze constanten bepalen het gedrag van de hele 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.\
"""

De sets TEXT_EXTS en IMAGE_EXTS sturen twee verschillende bijlagegedragingen aan. Tekst-/codebestanden worden gelezen en als context in de prompt geïnjecteerd, terwijl afbeeldingsbestanden base64-encoded worden en in het images-veld worden verzonden voor de visiecapaciteiten van Gemma 4.

De DEFAULT_SYSTEM-prompt vormt de codegeneratiestijl van het model, met name de vraag om complete codeblokken met taal-tags, omdat de app deze parseert om code naar de editor te pushen.

Stap 3: Agentische tools definiëren

De assistant kan in een agentische modus werken waarin hij tijdens inferentie tools aanroept. We definiëren twee tools in het standaard functie-aanroep-schema dat Ollama ondersteunt:

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"],
            },
        },
    },
]

De bovenstaande code definieert de tool run_code, die Python uitvoert in een tijdelijk bestand via een subprocess met een time-out van 5 seconden, en zowel stdout als stderr vastlegt. Zo kan het model logica valideren, voorbeelden draaien of live output produceren, niet alleen statische tekst genereren.

De tool calculate daarentegen evalueert wiskundige expressies in een beperkte namespace met alleen de math-module en veilige built-ins. Dit dekt gevallen waarin het model precieze numerieke resultaten nodig heeft zonder een volledig Python-proces op te starten.

Stap 4: Toolexecutie

De executielaag koppelt toolnamen aan hun implementaties. Deze wordt aangeroepen tijdens de agentische loop wanneer het model een tool_calls-blok retourneert.

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}"

Enkele noemenswaardige veiligheidskeuzes uit de toolexecutiestack:

  • _run_python() schrijft naar een tijdelijk bestand en ruimt dit op, zodat er geen restbestanden achterblijven, zelfs niet bij fouten

  • Output wordt afgekapt op 3.000 tekens om te voorkomen dat de context overspoeld wordt met enorme print-dumps

  • De functie _calculate() gebruikt eval() met __builtins__ als lege dict, zodat geen toegang is tot Python built-ins buiten de expliciet toegestane math-functies

  • De time-out is een harde 5-secondenlimiet: lang genoeg voor de meeste validatietaken, kort genoeg om runaway-processen te voorkomen

Stap 5: Helperutilities

Voordat we de kernlogica van de chat bouwen, hebben we een paar hulpfuncties nodig die bestands­paden, afbeeldings­codering en code-extractie afhandelen. Deze helpers worden door de hele streamingpipeline heen aangeroepen.

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

Deze utilities zorgen voor soepele afhandeling van multimodale input en gestructureerde output:

  • encode_image()-functie: Deze functie converteert een afbeelding naar een base64-string zodat deze kan worden doorgegeven aan het images-veld van Ollama. Hierdoor kan Gemma afbeeldingen naast tekst verwerken voor multimodaal redeneren.

  • file_as_context()-functie: Deze leest tekst-/codebestanden en injecteert ze rechtstreeks in de prompt als een opgemaakte markdown-blok. Dit voorkomt dat je een apart retrievalsysteem nodig hebt, omdat alles inline als context wordt meegegeven.

  • resolve_gradio_path()-functie: Deze functie normaliseert verschillende bestandsformaten die Gradio teruggeeft naar een bruikbaar pad. Dit voorkomt edge-case bugs bij bestandsafhandeling.

  • is_image_path()-functie: Dit is een snelle check of een bestand een afbeelding is. Zo worden afbeeldingsinputs correct gerouteerd.

  • extract_last_code_block()-functie: Vervolgens extraheren we het laatste codeblok uit de respons van het model. 

  • ollama_ok()-functie: We verifiëren dat de lokale Ollama-server draait voordat we verzoeken sturen.

  • _gradio_content_to_text() en _append_chat_turn()-functies: We vlakken Gradio’s gestructureerde berichtformaat af naar platte tekst voor consistente verwerking en we behouden de chathistorie.

  • run_code_btn()-functie: Tot slot voeren we de gegenereerde code uit wanneer de gebruiker op de knop Run code klikt in de UI.

Nu onze helpers klaarstaan, kunnen we de kernfunctie chat() implementeren.

Stap 6: Kern-chat streaminggenerator

Deze functie stuurt de hele interactieloop aan, van inputvoorbereiding tot streamingresponses en toolexecutie.

Op hoofdlijnen doet chat() het volgende:

  • bouwt de conversatiegeschiedenis op
  • verrijkt de huidige gebruikersinput met context (code, bestanden, afbeeldingen)
  • stuurt een streamingverzoek naar Ollama
  • voert optioneel tools uit (agentische modus)
  • levert deelresponses voor realtime UI-updates
def chat(
    message, history, image_path, file_path,
    editor_code, language, system_prompt,
    agentic, thinking, temperature,
):

De functie volgt de volgende stap-voor-stap-sequentie:

Stap 1: Berichtgeschiedenis opbouwen

We beginnen met het converteren van eerdere chatbotbeurten naar het berichtenformaat van Ollama. De systeemprompt wordt vooraan geplaatst om het gedrag van het model te sturen, zodat het model over de volledige conversatiecontext beschikt voordat het een antwoord genereert.

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

Stap 2: De gebruikerbeurt samenstellen

Vervolgens construeren we het huidige gebruikersbericht door extra context in te voegen. Als de gebruiker met code werkt, voegen we de inhoud van de editor toe:

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```"
    )

Op dezelfde manier worden geüploade bestanden toegevoegd als inline context en worden afbeeldingen base64-gecodeerd en via het images-veld meegegeven. Zo kan het model samen over tekst, code en afbeeldingen redeneren.

Stap 3: Het Ollama-verzoek versturen

De payload bevat de modelnaam, berichtgeschiedenis, streamingvlag en opties als volgt:

payload = {
    "model": MODEL,
    "messages": messages,
    "stream": True,
    "options": options,
}

if agentic:
    payload["tools"] = TOOLS

Als denkmode is ingeschakeld, wordt options["think"] = True gezet, wat uitgebreide chain-of-thought reasoning activeert in ondersteunde modellen.

Stap 4: Tool-calls afhandelen (agentische modus)

Als de agentische modus is ingeschakeld, kan het model in plaats van tekst tool-calls teruggeven. In dat geval wordt elke tool uitgevoerd en worden de resultaten aan de conversatie toegevoegd. Vervolgens wordt een vervolgaanvraag verzonden om de generatie voort te zetten.

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", ...)

Dit creëert een agentische loop van één beurt waarin het model een tool aanroept, het resultaat ziet en vervolgens het eindoordeel verder genereert. Tool-call-logs worden voor de gebruiker aan de chat toegevoegd.

Stap 5: Code extraheren en editor updaten

Tot slot, zodra het streamen voltooid is, extraheren we het laatste codeblok uit de respons:

extracted, _ = extract_last_code_block(full_response)

if extracted:
    new_code = extracted

Kortom: het model genereert code, de editor wordt automatisch bijgewerkt en handmatig kopiëren-plakken is niet nodig.

Stap 7: Gradio UI-lay-out

Met de kernlogica op zijn plek ontwerpen we nu de gebruikersinterface met Gradio. Het doel is een lay-out te maken die zowel coding-workflows als AI-ondersteunde interactie naast elkaar ondersteunt.

We gebruiken gr.Blocks om een tweekoloms lay-out te definiëren:

  • het linkerpaneel richt zich op codebewerking en -uitvoering
  • het rechterpaneel verzorgt chat, bijlagen en modelbediening
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)

Laten we begrijpen hoe de UI samenwerkt met alle hierboven beschreven componenten:

  • Codewerkruimte: Het linkerpaneel bevat een taalselector, een code-editor en knoppen voor uitvoeren/leegmaken. De uitvoer wordt getoond in een gemarkeerd tekstvak.
  • Chat en interactie: Het rechterpaneel bevat de chatbot voor gesprekken, een invoerveld voor vragen en ondersteuning voor bijlagen zoals afbeeldingen (voor multimodaal redeneren) en bestanden (voor contextinjectie). Extra toggles schakelen agentische tool-use en optionele denkmodes in.
  • Lay-out en gebruiksgemak: De interface gebruikt een tweekoloms lay-out met een 55/45-verdeling, waarbij de code-editor prioriteit krijgt terwijl de chat makkelijk toegankelijk blijft. Deze balans zorgt voor een naadloze workflow tussen coderen, AI-assistentie en uitvoering.

Stap 8: Event-wiring

De verzend- en submit-events delen dezelfde inputs en outputs, dus zowel het klikken op de knop als het indrukken van Enter in het tekstvak triggert identiek gedrag:

_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)

De wrapper respond() zet hier Gradio-bestandspadobjecten om naar gewone padstrings voordat ze aan de streaminggenerator worden doorgegeven. Vervolgens geeft hij bij elke chunk (history, code, "") terug zodat de lege string het berichtveld bij de eerste yield wist.

Stap 9: Theming en CSS

Om de bruikbaarheid en visuele consistentie te verbeteren, voegen we een custom theme en CSS-laag toe bovenop de standaardstyling van Gradio. Dit geeft de app een verzorgde, GitHub-achtige look met ondersteuning voor zowel light- als dark-mode. Dit is volledig optioneel.

We beginnen met een basisthema met 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"],
)

Dit zet een consistente kleurenpalet en strakke UI-typografie (Inter) met een ontwikkelaarsvriendelijk monospace-lettertype (JetBrains Mono). Vervolgens overschrijven we belangrijke CSS-variabelen om het uiterlijk van de app te sturen:

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;
}

Hier definieert het eerste blok het lichtthema, terwijl het tweede blok waarden overschrijft voor dark-mode. Hoewel deze stap optioneel is, verbeteren custom theming en CSS de algehele gebruikerservaring aanzienlijk.

Nu de UI volledig is gestructureerd en gestyled, zijn we klaar voor de laatste stap, namelijk het starten van de applicatie.

Stap 10: Launch

Tot slot start je de app met:

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,
    )

Hier zorgt server_name="0.0.0.0" ervoor dat de app toegankelijk is op je lokale netwerk en met share=False blijft alles lokaal.  De check ollama_ok() doet een snelle GET naar localhost:11434 en waarschuwt bij het opstarten als Ollama niet draait, in plaats van de fout pas te tonen wanneer de gebruiker voor het eerst een bericht stuurt.

Navigeer tenslotte naar http://localhost:7860 zodra de app draait.

Hieronder zie je de volledige uitvoerstroom vanuit het perspectief van de gebruiker:

Demo with code generation

  • De gebruiker typt een bericht in het chatpaneel, eventueel met het uploaden van een afbeelding of codebestand en het toggelen van agentische/denkmodus

  • Bij verzenden bouwt de functie respond() het volledige Ollama-verzoek op, inclusief de huidige code uit de editor en eventuele bijlagen als context

  • Het verzoek streamt token voor token en deelresponses worden doorgegeven aan de Gradio-chatbot voor live renderen

  • Als agentische modus aanstaat en het model besluit een tool aan te roepen, draait de tool lokaal, worden de resultaten aan de conversatie toegevoegd en streamt een vervolgaanvraag het definitieve antwoord

  • Na afronding van het streamen wordt het laatste fenced codeblok in de respons geëxtraheerd en automatisch naar de editor gepusht

Conclusie

In deze tutorial bouwden we een volledig lokale AI coding assistant met Gemma 4, Ollama en Gradio. De app ondersteunt multimodale input, echte tool-use, streamingantwoorden en een live code-editor, allemaal op je eigen machine zonder externe API.

De whole-context aanpak waarbij je de code uit de editor direct in elke prompt injecteert, is eenvoudiger dan RAG voor een single-file workflow en werkt bijzonder goed voor taken als uitleggen, refactoren of uitbreiden van code waar je actief aan werkt.

Vanaf hier kun je het project in verschillende richtingen uitbreiden:

  • Voeg een bestandsboom-paneel toe om multi-file projecten te ondersteunen en injecteer geselecteerde bestanden als context
  • Voeg ondersteuning toe voor extra Ollama-modellen via een dropdown, zodat gebruikers kunnen wisselen tussen Gemma 4, Llama 3 en andere
  • Bewaar conversatiegeschiedenis op schijf zodat sessies een app-herstart overleven

Aashi Dutt's photo
Author
Aashi Dutt
LinkedIn
Twitter

Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.

Gemma 4 FAQ’s

Heb je hier een GPU voor nodig?

Niet per se. Gemma 4 e4b is een gequantiseerd model dat op de CPU draait, al zal een GPU de inferentiesnelheid aanzienlijk verbeteren.

Wat is het verschil tussen agentische modus en gewone chat?

In de normale modus streamt het model alleen tekst. In de agentische modus kan het model run_code of calculate aanroepen, de resultaten controleren en die verwerken voordat het zijn antwoord afrondt.

Hoe werkt het dat de editor automatisch wordt bijgewerkt als de agent code schrijft?

De helperfunctie extract_last_code_block() scant het antwoord van de assistant op het laatste fenced codeblok en pusht dit na het streamen naar de editor. Daarom instrueert de systeemprompt het model om code altijd in fenced blokken met een taaltag te plaatsen.

Wat gebeurt er als Ollama niet draait?

De check ollama_ok() onderschept dit bij het opstarten en bij elke chatsubmissie. Als Ollama niet bereikbaar is, retourneert de chat een opgemaakte foutmelding in plaats van te crashen.

Onderwerpen
Kunstmatige intelligentie

Leer met DataCamp

Cursus

Praktische AI met Google Gemini en NotebookLM

2 uur
9.8K
Leer Gemini en NotebookLM kennen om dingen automatisch te doen, productiever te werken en slimmer te werken in het AI-ecosysteem van Google.
Bekijk detailsRight Arrow
Start Cursus
Meer bekijkenRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer BekijkenMeer Bekijken