DEV Community

Geoffrey Logovi
Geoffrey Logovi

Posted on

Atelier : construire une app d'IA avec Gemma 4, pas à pas

En 45 minutes, on construit une petite API qui envoie la photo d'une feuille de maïs à Gemma 4 et renvoie un diagnostic en JSON. On applique au passage le guide « Coder avec l'IA : le guide complet, du premier prompt à la production » : fichiers de contexte, prompts rangés, sortie structurée, tests.

Ce qu'on construit

AgriScan : une API qui reçoit la photo d'une feuille de maïs et répond avec un diagnostic simple, en français, au format JSON.

{"maladie": "rouille commune", "confiance": "moyenne", "conseil": "Retire les feuilles touchées et surveille les plants voisins."}
Enter fullscreen mode Exit fullscreen mode

Le trajet d'une requête : l'utilisateur envoie une photo → FastAPI la vérifie → gemma.py l'envoie à Gemma 4 avec le prompt de prompts.py → on lit le JSON → on le renvoie.

Gemma 4 est un modèle open-weight de Google qui comprend le texte et les images. On l'appelle ici via l'API Gemini, ce qui évite de le faire tourner sur ton ordinateur.

Ce projet sert d'exemple. Pendant le Hack Day, remplace le maïs par ton propre problème : la structure reste la même.

Prérequis

  • Python 3.11 ou plus récent (python --version)
  • Git et un compte GitHub
  • Un éditeur, idéalement VS Code avec GitHub Copilot
  • Une clé d'API Gemini, gratuite, créée sur Google AI Studio (bouton Get API key)
  • Une ou deux photos de feuilles pour tester (n'importe quelle plante convient)

Étape 1 : créer le dépôt et ses fichiers de contexte

Sur GitHub, crée un dépôt public agriscan avec un README, une licence MIT et le .gitignore Python. Puis :

git clone https://github.com/TON-PSEUDO/agriscan.git
cd agriscan
mkdir app tests
touch app/__init__.py app/main.py app/gemma.py app/prompts.py tests/test_main.py
Enter fullscreen mode Exit fullscreen mode

Ajoute AGENTS.md à la racine, avant d'écrire le moindre code. C'est lui qui guidera Copilot pendant tout l'atelier (modèle complet dans le guide) :

# Instructions pour l'assistant IA

## Le projet
AgriScan : une API qui diagnostique une maladie du maïs à partir d'une photo.

## Stack
- Python 3.11, FastAPI
- Gemma 4 via l'API Gemini, SDK google-genai, modèle gemma-4-26b-a4b-it

## Commandes
- Lancer : fastapi dev app/main.py
- Tester : pytest

## Règles
- La clé d'API vient de GEMINI_API_KEY, jamais du code.
- Tous les appels au modèle passent par app/gemma.py.
- Tous les prompts sont dans app/prompts.py.
- Chaque route a un test pytest qui n'appelle pas le vrai modèle.
Enter fullscreen mode Exit fullscreen mode

Crée aussi requirements.txt :

fastapi[standard]
google-genai
pytest
Enter fullscreen mode Exit fullscreen mode

Étape 2 : installer et configurer la clé d'API

python -m venv .venv
source .venv/bin/activate        # Windows : .venv\Scripts\activate
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Le SDK lit la clé dans la variable d'environnement GEMINI_API_KEY :

export GEMINI_API_KEY="ta-cle"          # macOS / Linux
$env:GEMINI_API_KEY="ta-cle"            # Windows PowerShell
Enter fullscreen mode Exit fullscreen mode

Ne l'écris jamais dans un fichier du dépôt. Si tu la mets dans un fichier .env, vérifie que .env figure bien dans .gitignore.

Étape 3 : premier appel à Gemma 4

Crée un fichier essai.py à la racine :

from google import genai

client = genai.Client()  # lit GEMINI_API_KEY

response = client.models.generate_content(
    model="gemma-4-26b-a4b-it",
    contents="Explique en deux phrases ce qu'est un modèle open-weight.",
)
print(response.text)
Enter fullscreen mode Exit fullscreen mode
python essai.py
Enter fullscreen mode Exit fullscreen mode

Si une réponse s'affiche, tout est en place. Deux modèles Gemma 4 sont disponibles via l'API Gemini : gemma-4-26b-a4b-it (celui qu'on utilise) et gemma-4-31b-it. Commit maintenant :

git add . && git commit -m "feat: premier appel a Gemma 4"
Enter fullscreen mode Exit fullscreen mode

Étape 4 : ranger le prompt dans prompts.py

Le prompt est du code : on le met à un seul endroit, on le versionne, on le teste. Dans app/prompts.py :

DIAGNOSTIC_PROMPT = """Tu es un agronome qui aide des petits producteurs au Togo.
On te montre la photo d'une feuille de maïs.
Réponds uniquement avec un objet JSON, sans texte autour, avec les clés :
- "maladie" : le nom probable de la maladie, ou "inconnue"
- "confiance" : "faible", "moyenne" ou "forte"
- "conseil" : une action simple, en une phrase, en français
Si la photo ne montre pas une feuille de maïs, réponds
{"maladie": "photo non valide", "confiance": "forte", "conseil": "Envoie une photo nette d'une feuille de maïs."}"""
Enter fullscreen mode Exit fullscreen mode

On applique ici trois techniques du guide : un rôle et un public, un format de sortie imposé, et un cas limite prévu à l'avance.

Étape 5 : envoyer une image à Gemma 4

Dans app/gemma.py, un seul endroit appelle le modèle. Le prompt passe en system instruction ; l'image est envoyée comme une partie du contenu.

import json

from google import genai
from google.genai import types

from app.prompts import DIAGNOSTIC_PROMPT

MODEL = "gemma-4-26b-a4b-it"


def analyser_image(image_bytes: bytes, mime_type: str) -> dict:
    """Envoie la photo à Gemma 4 et renvoie le diagnostic sous forme de dict."""
    client = genai.Client()  # créé ici pour que les tests n'aient pas besoin de clé
    response = client.models.generate_content(
        model=MODEL,
        config=types.GenerateContentConfig(system_instruction=DIAGNOSTIC_PROMPT),
        contents=[
            types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
            "Analyse cette photo.",
        ],
    )
    return extraire_json(response.text)
Enter fullscreen mode Exit fullscreen mode

Étape 6 : lire une sortie JSON fiable

Même quand on demande « uniquement du JSON », un modèle ajoute parfois des balises jsonou une phrase autour. On extrait donc l'objet entre la première accolade et la dernière, et on refuse proprement le reste. Ajoute dansapp/gemma.py` :

python
def extraire_json(texte: str) -> dict:
"""Extrait le premier objet JSON d'une réponse du modèle."""
debut, fin = texte.find("{"), texte.rfind("}")
if debut == -1 or fin == -1:
raise ValueError(f"Pas de JSON dans la réponse : {texte[:200]}")
return json.loads(texte[debut : fin + 1])

Teste-le vite dans un terminal Python avec une de tes photos :

python
from app.gemma import analyser_image
print(analyser_image(open("feuille.jpg", "rb").read(), "image/jpeg"))

Essaie aussi une photo qui n'a rien à voir (ton chat, un ordinateur) pour vérifier que le cas « photo non valide » fonctionne. Si la réponse ne te plaît pas, c'est le prompt qu'on corrige, pas le code.

Étape 7 : exposer une API avec FastAPI

On transforme la fonction en route web. app/main.py vérifie l'entrée (type et taille de la photo), appelle Gemma et renvoie une erreur claire si quelque chose casse :

`python
from fastapi import FastAPI, File, HTTPException, UploadFile

from app import gemma

app = FastAPI(title="AgriScan")
TAILLE_MAX = 5 * 1024 * 1024 # 5 Mo

@app.post("/diagnostic")
async def diagnostic(photo: UploadFile = File(...)):
if photo.content_type not in {"image/jpeg", "image/png"}:
raise HTTPException(415, "Envoie une photo JPEG ou PNG.")
contenu = await photo.read()
if len(contenu) > TAILLE_MAX:
raise HTTPException(413, "Photo trop lourde : 5 Mo maximum.")
try:
return gemma.analyser_image(contenu, photo.content_type)
except ValueError:
raise HTTPException(502, "Le modèle n'a pas renvoyé un résultat lisible. Réessaie.")
`

Note : on écrit gemma.analyser_image(...) et pas from app.gemma import analyser_image. Ce détail permet au test de l'étape 8 de remplacer la fonction.

Lance le serveur :

bash
fastapi dev app/main.py

Ouvre http://127.0.0.1:8000/docs, clique sur POST /diagnostic, puis Try it out, choisis une photo et clique Execute. Tu dois voir le JSON du diagnostic. FastAPI génère cette page tout seul : c'est aussi ta démo pour la vidéo.

Étape 8 : écrire un test qui ne dépend pas du modèle

Un test qui appelle vraiment Gemma est lent, coûte du quota et échoue sans réseau. On remplace donc l'appel au modèle par une fausse réponse avec monkeypatch, et on teste notre code à nous. tests/test_main.py :

`

from fastapi.testclient import TestClient

from app import gemma
from app.main import app

client = TestClient(app)


def test_diagnostic(monkeypatch):
    faux = {"maladie": "rouille", "confiance": "moyenne", "conseil": "..."}
    monkeypatch.setattr(gemma, "analyser_image", lambda data, mime: faux)
    r = client.post("/diagnostic", files={"photo": ("feuille.jpg", b"fake", "image/jpeg")})
    assert r.status_code == 200
    assert r.json()["maladie"] == "rouille"


def test_refuse_un_pdf():
    r = client.post("/diagnostic", files={"photo": ("doc.pdf", b"%PDF", "application/pdf")})
    assert r.status_code == 415


def test_extraire_json_avec_texte_autour():
    texte = 'Voici le résultat :\n```

json\n{"maladie": "inconnue"}\n

```'
    assert gemma.extraire_json(texte) == {"maladie": "inconnue"}
Enter fullscreen mode Exit fullscreen mode


`

Lance les tests, puis commite :

bash
pytest
git add .
git commit -m "Ajoute la route /diagnostic et ses tests"
git push

Trois tests verts, aucune clé d'API nécessaire. C'est exactement ce que le guide appelait « vérifier avant de faire confiance ».

Étape 9 : avancer plus vite avec Copilot (et ton AGENTS.md)

Ton AGENTS.md de l'étape 2 travaille maintenant pour toi : Copilot le lit et connaît déjà le modèle, la structure et les commandes. Tes prompts peuvent donc être courts et précis. Quelques exemples à coller dans Copilot Chat (mode agent) :

text
Ajoute une page HTML simple dans app/static/index.html avec un champ pour choisir
une photo et un bouton « Analyser ». Elle appelle POST /diagnostic et affiche
maladie, confiance et conseil. Pas de framework JavaScript. Sers-la avec FastAPI.

text
Dans app/main.py, si le modèle renvoie "photo non valide", réponds avec un code 422
et le conseil du modèle. Ajoute le test correspondant dans tests/test_main.py.

text
Relis app/gemma.py et liste les cas d'erreur non gérés. Ne modifie rien,
explique seulement.

Après chaque modification : relis le diff, lance pytest, essaie dans /docs, puis commite. Si Copilot part dans une mauvaise direction, annule (git restore .) et reformule plutôt que de corriger à la main un code que tu ne comprends pas.

Étape 10 : README, vidéo et soumission

Le jury regarde ton repo avant ton pitch. Ton README.md doit répondre à ces questions :

  • Le problème : qui en souffre, en une ou deux phrases.
  • La solution : ce que fait l'app et le rôle exact de Gemma 4.
  • Lancer le projet : les commandes de l'étape 3 et de l'étape 7, copiables.
  • Le modèle : gemma-4-26b-a4b-it via l'API Gemini, et sa licence.
  • Outils IA utilisés : par exemple « GitHub Copilot pour la page HTML et les tests ; Gemma 4 pour le diagnostic ».
  • L'équipe : noms et profils GitHub.
  • La vidéo démo : le lien.

Pour la vidéo (2 minutes maximum) : enregistre ton écran pendant que tu envoies une vraie photo et que le diagnostic s'affiche. Dépose-la sur Google Drive, règle le partage sur « Tous les utilisateurs disposant du lien » et colle le lien dans le README. Ouvre ce lien dans une fenêtre privée pour vérifier qu'il marche.

Enfin, sur OrganizerHQ : Challenges, puis Add Submission, coche les défis visés (Best Open-Source AI Project, Best Use of Gemma, Best Use of GitHub Copilot), colle l'URL du repo, puis Submit Project. Le Hack Day ferme les soumissions à 12:35 : vise 12:20 pour garder une marge.

Dernière vérification avant de soumettre :

bash
git status # rien à commiter
git log --oneline | head # tes commits sont là

Puis ouvre ton repo sur GitHub depuis un autre navigateur : il doit être public, avec un fichier LICENSE.

Dépannage

Symptôme Cause probable Que faire
401, API key not valid Clé absente ou mal copiée Relance export GEMINI_API_KEY=... dans le terminal qui lance le serveur, sans guillemets en trop.
429, RESOURCE_EXHAUSTED Quota gratuit dépassé Attends une minute, évite les appels en boucle, et utilise les tests avec monkeypatch pendant le développement.
404, modèle introuvable Mauvais identifiant Vérifie le nom exact du modèle dans Google AI Studio et dans MODEL.
ValueError: Pas de JSON Le modèle a répondu en texte libre Renforce le prompt (« uniquement un objet JSON »), affiche response.text pour voir ce qu'il a dit.
ModuleNotFoundError: app Mauvais dossier Lance les commandes depuis la racine du repo, là où se trouve app/.
415 sur une vraie photo Format HEIC (iPhone) ou WebP Convertis en JPEG, ou ajoute le type dans la liste autorisée.

Ressources

Une question pendant le hack ? Lève la main, un mentor passe. Après le Hack Day, on continue sur le Discord de Python Togo : https://pytogo.org/discord

Top comments (0)