Cours
Les LLM locaux ont franchi un cap. Avec la fenêtre de contexte étendue de Gemma 4, son support multimodal natif et l’accessibilité d’Ollama, il devient réaliste de faire tourner un assistant de codage agentique performant entièrement sur votre machine.
Dans ce tutoriel, je vous montre comment créer un assistant de codage avec Gemma 4 via Ollama, avec une interface Gradio. L’application propose une vue en deux volets : à gauche un éditeur de code en direct, à droite un panneau de chat agentique. Vous pouvez téléverser des images ou des fichiers de code en contexte, activer l’usage d’outils pour que le modèle exécute et valide du code, et basculer en mode réflexion pour les problèmes complexes.
À la fin, vous disposerez d’une application locale capable de :
- Rédiger, expliquer et déboguer du code dans plus de 15 langages
- Exécuter du code Python dans un sous-processus isolé et renvoyer les résultats
- Accepter des images et des fichiers texte comme contexte multimodal
- Diffuser les réponses en temps réel depuis un modèle Gemma 4 exécuté en local
- Fonctionner en boucle agentique, en appelant des outils et en enchaînant selon les résultats
Le code complet de ce tutoriel est disponible ici.
Qu’est-ce que Gemma 4 ?
Gemma 4 est une famille de modèles à poids ouverts de Google DeepMind, conçue pour un déploiement local et la recherche. Elle prolonge la lignée Gemma avec une meilleure conformité aux instructions, des fenêtres de contexte plus longues et une gestion multimodale native des entrées, et s’appuie sur la même infrastructure de recherche que Gemini 3.

Figure : performance du modèle selon la taille (source : blog Gemma 4)
Des modèles comme Gemma-4-26B (MOE) et Gemma-4-31B atteignent des scores Elo comparables à des modèles bien plus grands, signe d’un excellent ratio performance/parapètre. Le 31B se classe actuellement 3e modèle ouvert au monde sur le classement Arena AI (texte), et le 26B occupe la 6e place. Gemma 4 est ainsi particulièrement adaptée aux déploiements locaux et contraints en ressources, sans sacrifier les capacités.
La famille de modèles Gemma 4
Gemma 4 est publiée en quatre tailles polyvalentes : Effective 2B (E2B), Effective 4B (E4B), 26B Mixture of Experts (MoE) et 31B Dense. La famille se divise en deux niveaux distincts selon la cible de déploiement :
|
Modèle |
Architecture |
Paramètres totaux |
Paramètres actifs/effectifs |
Longueur de contexte |
Modalités |
|
Gemma-4-31B |
Transformer dense |
31B |
31B |
256 k tokens |
Texte, vision, vidéo |
|
Gemma-4-26B-A4B |
MoE (128 experts) |
26B |
3,8B actifs |
256 k tokens |
Texte, vision, vidéo |
|
Gemma-4-E4B |
Transformer dense |
7,9B (avec embeddings) |
4,5B effectifs |
128 k tokens |
Texte, audio, vision, vidéo |
|
Gemma-4-E2B |
Transformer dense |
5,1B (avec embeddings) |
2,3B effectifs |
128 k tokens |
Texte, audio, vision, vidéo |
Approfondissons chaque variante :

Figure : guide visuel de Gemma 4 (source)
- 31B Dense est le modèle phare optimisé pour les datacenters et les charges à raisonnement complexe. Il prend en charge une fenêtre de contexte de 256 k tokens avec une fenêtre glissante de 1 024 tokens pour un traitement long contexte efficace.
- 26B-A4B (MoE) est le premier modèle MoE de Gemma, qui achemine les tokens via 128 experts tout en n’activant que 3,8B de paramètres par passe avant. Il offre une qualité proche du 31B pour une fraction du coût de calcul par token, idéal pour le service à haut débit.
- E4B et E2B constituent le niveau sur appareil et mobile. À la différence des variantes plus grandes, ils intègrent un entrée audio native pour la reconnaissance vocale, en plus de la vision et de la vidéo, ce qui en fait les modèles les plus multimodaux de la famille pour l’edge.
Les quatre modèles sont disponibles sous licence Apache 2.0 et peuvent être déployés en local via Ollama, vLLM, llama.cpp ou Unsloth.
Pour les tâches de code, Gemma 4 excelle à :
- Produire du code complet et structuré avec des explications
- Raisonner sur des bases de code existantes fournies en contexte
- Utiliser des outils et des workflows agentiques avec une couche d’orchestration
- Gérer des images avec du code (p. ex. lire une capture d’interface et générer l’HTML correspondant)
Dans ce tutoriel, nous utilisons la variante gemma4:e4b (9,6 Go) via Ollama, une version quantifiée adaptée à l’inférence locale sur du matériel grand public.
Exécuter Gemma 4 via Ollama
Ollama gère le téléchargement, la quantification, le service du modèle et expose une API HTTP compatible OpenAI. Ici, Ollama sert de backend d’inférence, et notre application communique avec lui via localhost:11434.
Pour récupérer et lancer le modèle :
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma4:e4b
ollama serve
Ces commandes installent Ollama en local via le script officiel. Nous téléchargeons ensuite la variante gemma 4:e4b pour l’inférence sur appareil. Enfin, ollama serve démarre le serveur Ollama, ce qui permet à notre application d’envoyer des requêtes au modèle.
Une fois le serveur actif, l’application Gradio s’y connecte automatiquement.
Démo Gemma 4 : créer un assistant de codage avec Ollama
Dans cette section, nous allons construire l’assistant pas à pas. En résumé, l’application :
-
Accepte un message en langage naturel dans le panneau de chat, avec pièces jointes (image ou fichier) en option
-
Injecte le code actuellement présent dans l’éditeur comme contexte pour le modèle
-
Envoie une requête en streaming à Gemma 4 via l’endpoint
/api/chatd’Ollama -
Appelle éventuellement des outils (exécution de code, calcul) dans une boucle agentique
-
Pousse les blocs de code extraits de la réponse dans l’éditeur en direct
Construisons-le pas à pas.

Étape 1 : installer les dépendances
L’application nécessite un petit ensemble de bibliothèques pour l’UI, la gestion d’images et la communication HTTP. Comme le modèle tourne en local via Ollama, aucune dépendance SDK cloud n’est requise.
pip install gradio requests pillow
Dans ce projet, nous utiliserons :
-
gradiopour l’éditeur et l’interface de chat en deux volets -
requestspour communiquer avec l’API HTTP d’Ollama -
pillowpour la gestion des images dans l’UI
Cela reste léger et fonctionne sur toute machine exécutant Ollama, y compris macOS (Apple Silicon), Linux et Windows via WSL.
Étape 2 : configuration et constantes
Avant d’écrire la logique, nous définissons les imports, le nom du modèle, l’URL de base Ollama, les langages pris en charge et un prompt système par défaut. Ces constantes pilotent le comportement de toute l’application.
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.\
"""
Les ensembles TEXT_EXTS et IMAGE_EXTS pilotent deux comportements de pièces jointes. Les fichiers texte/code sont lus et injectés comme contexte dans le prompt, tandis que les images sont encodées en base64 et passées dans le champ images pour les capacités de vision de Gemma 4.
Le prompt DEFAULT_SYSTEM façonne le style de génération de code du modèle : il demande explicitement des blocs de code complets avec balise de langage, car l’application les analyse pour injecter le code dans l’éditeur.
Étape 3 : définir les outils agentiques
L’assistant peut fonctionner en mode agentique et appeler des outils pendant l’inférence. Nous définissons deux outils selon le schéma standard d’appel de fonctions pris en charge par 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"],
},
},
},
]
Le bloc ci-dessus définit l’outil run_code, qui exécute du Python dans un fichier temporaire via un sous-processus (délai 5 s) et capture stdout et stderr. Le modèle peut ainsi valider la logique, exécuter des exemples ou produire une sortie en direct, et pas seulement du texte statique.
L’outil calculate évalue des expressions mathématiques dans un espace de noms restreint, avec uniquement le module math et quelques fonctions sûres. Il couvre les cas où le modèle a besoin d’un résultat numérique précis sans lancer un processus Python complet.
Étape 4 : exécution des outils
La couche d’exécution associe les noms d’outils à leurs implémentations. Elle est appelée durant la boucle agentique quand le modèle retourne un bloc 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}"
Quelques choix de sécurité notables dans cette pile d’exécution :
-
_run_python()écrit dans un fichier temporaire puis le supprime, évitant les fichiers orphelins même en cas d’erreur -
La sortie est plafonnée à 3 000 caractères pour ne pas saturer le contexte avec de longs dumps
-
La fonction
_calculate()utiliseeval()avec__builtins__vidé, empêchant l’accès aux built-ins non autorisés, et n’autorise explicitement que les fonctionsmath -
Le délai est strictement de 5 secondes : suffisant pour la validation, assez court pour éviter les processus incontrôlés
Étape 5 : utilitaires
Avant de coder la logique centrale du chat, nous avons besoin de quelques fonctions utilitaires pour gérer les chemins de fichiers, l’encodage d’images et l’extraction de code. Elles sont appelées tout au long du pipeline de 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
Ces utilitaires facilitent la gestion des entrées multimodales et des sorties structurées :
-
encode_image(): Convertit une image en chaînebase64pour la passer au champimagesd’Ollama. Gemma peut ainsi traiter l’image avec le texte pour un raisonnement multimodal. -
file_as_context(): Lit les fichiers texte/code et les injecte directement dans le prompt sous forme de bloc Markdown formaté. Inutile d’avoir un système de retrieval séparé : tout passe en contexte inline. -
resolve_gradio_path(): Normalise les différents formats de chemins retournés par Gradio en un chemin exploitable. Évite des bugs de bord en gestion de fichiers. -
is_image_path(): Vérifie rapidement si un fichier est une image, pour router correctement les entrées. -
extract_last_code_block(): Extrait le dernier bloc de code de la réponse du modèle. -
ollama_ok(): Vérifie que le serveur Ollama local est actif avant d’émettre des requêtes. -
_gradio_content_to_text()et_append_chat_turn(): Aplatissent le format de message structuré de Gradio en texte brut pour un traitement homogène et maintiennent l’historique du chat. -
run_code_btn(): Exécute le code généré quand l’utilisateur clique sur le bouton Run code de l’UI.
Avec ces utilitaires en place, nous pouvons implémenter la fonction centrale chat().
Étape 6 : générateur de streaming du chat
Cette fonction pilote toute l’interaction : préparation des entrées, streaming des réponses et exécution des outils.
En résumé, chat() :
- construit l’historique de conversation
- enrichit l’entrée courante avec du contexte (code, fichiers, images)
- envoie une requête en streaming à Ollama
- exécute éventuellement des outils (mode agentique)
- émet des réponses partielles pour une mise à jour en temps réel de l’UI
def chat(
message, history, image_path, file_path,
editor_code, language, system_prompt,
agentic, thinking, temperature,
):
La fonction suit la séquence étape par étape suivante :
Étape 1 : construire l’historique
Nous convertissons d’abord les tours précédents en format de message Ollama. Le prompt système est préfixé pour guider le modèle et garantir qu’il dispose de tout le contexte avant de répondre.
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
Étape 2 : composer le tour utilisateur
Nous construisons ensuite le message courant en injectant du contexte additionnel. Si l’utilisateur travaille sur du code, nous ajoutons le contenu de l’éditeur :
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```"
)
De même, les fichiers téléversés sont ajoutés inline et les images encodées en base64 et passées via le champ images. Le modèle peut ainsi raisonner sur texte, code et images ensemble.
Étape 3 : envoyer la requête Ollama
La charge utile inclut le nom du modèle, l’historique, le flag de streaming et les options :
payload = {
"model": MODEL,
"messages": messages,
"stream": True,
"options": options,
}
if agentic:
payload["tools"] = TOOLS
Si le mode réflexion est activé, options["think"] = True est défini, ce qui active un raisonnement en chaîne étendu sur les modèles compatibles.
Étape 4 : gérer les appels d’outils (mode agentique)
Si le mode agentique est activé, le modèle peut retourner des appels d’outils au lieu de texte. Chaque outil est alors exécuté, les résultats sont ajoutés à la conversation, puis une requête de suivi est envoyée pour poursuivre la génération.
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", ...)
Cela crée une boucle agentique en un tour : le modèle appelle un outil, consulte le résultat, puis poursuit la réponse finale. Les journaux d’appels d’outils sont ajoutés au chat pour l’utilisateur.
Étape 5 : extraire le code et mettre à jour l’éditeur
À la fin du streaming, nous extrayons le dernier bloc de code de la réponse :
extracted, _ = extract_last_code_block(full_response)
if extracted:
new_code = extracted
Au final, le modèle génère le code, l’éditeur se met à jour automatiquement, et vous évitez tout copier-coller.
Étape 7 : mise en page Gradio
Avec la logique en place, concevons l’interface dans Gradio. L’objectif : un agencement qui combine flux de codage et interaction avec l’IA côte à côte.
Nous utilisons gr.Blocks pour définir deux colonnes :
- le panneau de gauche pour l’édition et l’exécution du code
- le panneau de droite pour le chat, les pièces jointes et les contrôles du modèle
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)
Voyons comment l’UI fonctionne avec les composants décrits ci-dessus :
- Espace de code : Le panneau gauche propose un sélecteur de langage, un éditeur de code, et des contrôles exécuter/effacer. La sortie s’affiche dans une zone dédiée.
- Chat et interaction : Le panneau droit contient le chatbot, une zone de saisie et la prise en charge des pièces jointes (images pour le raisonnement multimodal, fichiers pour l’injection de contexte). Des options permettent d’activer l’usage d’outils agentiques et des modes de raisonnement.
- Ergonomie : L’interface adopte une mise en page deux colonnes (55/45), priorisant l’éditeur tout en gardant le chat à portée. Ce juste équilibre fluidifie le passage entre codage, assistance IA et exécution.
Étape 8 : câblage des événements
Les événements d’envoi et de soumission partagent les mêmes entrées/sorties : cliquer sur le bouton ou appuyer sur Entrée déclenche le même comportement :
_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)
Le wrapper respond() résout d’abord les objets chemins de Gradio en chaînes de chemins de fichiers, puis passe ces chemins au générateur de streaming. Il renvoie ensuite (history, code, "") à chaque chunk pour effacer la zone de saisie dès la première émission.
Étape 9 : thème et CSS
Pour améliorer l’ergonomie et l’homogénéité visuelle, nous ajoutons un thème et une couche CSS personnalisés par-dessus le style par défaut de Gradio. L’application adopte un look soigné inspiré de GitHub, en clair comme en sombre. Cette étape est facultative.
Nous définissons d’abord un thème de base via 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"],
)
Cela impose une palette cohérente et une typo soignée (Inter) avec une monospace adaptée aux développeurs (JetBrains Mono). Nous surchargeons ensuite des variables CSS clés pour contrôler l’apparence :
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;
}
Le premier bloc définit le thème clair, le second surcharge pour le mode sombre. Optionnel mais très utile pour une expérience plus aboutie.
L’UI étant structurée et stylée, passons au lancement de l’application.
Étape 10 : lancement
Enfin, lancez l’application :
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,
)
Ici, server_name="0.0.0.0" rend l’app accessible sur votre réseau local et share=False garde tout en local. Le contrôle ollama_ok() envoie un GET rapide à localhost:11434 et avertit au démarrage si Ollama n’est pas lancé, au lieu d’échouer à la première requête utilisateur.
Ensuite, ouvrez http://localhost:7860 une fois l’app démarrée.
Voici le flux d’exécution côté utilisateur :

-
L’utilisateur saisit un message dans le chat, peut joindre une image ou un fichier de code, et activer les modes agentique/réflexion
-
À l’envoi, la fonction
respond()construit la requête complète à Ollama, incluant le code de l’éditeur et les pièces jointes en contexte -
La réponse est diffusée token par token, et les parties sont rendues en direct dans le chatbot Gradio
-
Si le mode agentique est activé et que le modèle appelle un outil, celui-ci s’exécute en local, les résultats sont ajoutés à la conversation, puis une requête de suivi diffuse la réponse finale
-
Une fois le streaming terminé, le dernier bloc de code de la réponse est extrait et injecté automatiquement dans l’éditeur
Conclusion
Dans ce tutoriel, nous avons construit un assistant de codage IA entièrement local avec Gemma 4, Ollama et Gradio. L’application gère l’entrée multimodale, l’usage réel d’outils, le streaming des réponses et un éditeur en direct — le tout sur votre machine, sans API externe.
L’approche « plein contexte » qui consiste à injecter le code de l’éditeur dans chaque prompt est plus simple que le RAG pour un flux mono‑fichier et fonctionne très bien pour expliquer, refactorer ou étendre le code sur lequel vous travaillez.
Vous pouvez prolonger le projet dans plusieurs directions :
- Ajouter un panneau d’arborescence pour gérer des projets multi‑fichiers et injecter des fichiers sélectionnés en contexte
- Ajouter un sélecteur de modèles Ollama pour basculer entre Gemma 4, Llama 3 et d’autres
- Persister l’historique des conversations sur disque pour conserver les sessions au redémarrage
Je suis experte Google Developers en ML (Gen AI), triple experte Kaggle et ambassadrice Women Techmakers, avec plus de trois ans d’expérience dans la tech. J’ai cofondé une startup dans le domaine de la santé en 2020 et je poursuis actuellement un master en informatique à Georgia Tech, avec une spécialisation en apprentissage automatique.
FAQ Gemma 4
Faut-il un GPU ?
Pas forcément. Gemma 4 e4b est un modèle quantifié qui peut tourner sur CPU, même si un GPU accélérera nettement l’inférence.
Quelle différence entre le mode agentique et le chat classique ?
En mode classique, le modèle ne diffuse que du texte. En mode agentique, il peut appeler run_code ou calculate, vérifier les résultats et les intégrer avant de conclure sa réponse.
Comment l’éditeur se met-il à jour automatiquement quand l’agent écrit du code ?
La fonction utilitaire extract_last_code_block() repère le dernier bloc de code délimité dans la réponse de l’assistant et l’envoie à l’éditeur une fois le streaming terminé. C’est la raison pour laquelle le prompt système impose d’encapsuler le code dans des blocs avec balise de langage.
Que se passe-t-il si Ollama n’est pas lancé ?
Le contrôle ollama_ok() intercepte ce cas au démarrage et à chaque envoi. Si Ollama est injoignable, le chat renvoie un message d’erreur formaté au lieu de planter.


