大模型
dev.to ·
@Geoffrey Logovi
工作坊:用 Gemma 4 一步步构建 AI 应用
教程演示在 45 分钟内构建 AgriScan:用户上传玉米叶片照片,FastAPI 校验后经 gemma.py 调用 Gemma 4(通过 Gemini API,无需本地部署),按 prompts.py 中的提示词返回法语 JSON 诊断(病害、置信度、建议),并涉及上下文文件、结构化输出与测试等工程实践。
AI推荐理由 提供用 Gemma 4 构建玉米叶片病害诊断 API 的分步教程,包含 FastAPI 调用、结构化 JSON 输出和提示词组织方式,并说明通过 Gemini API 调用而非本地运行。属于可复现的教学示例,但未给出准确率或实测效果。
62
正文
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
Gemma sur l'API Gemini, documentation officielle : https://ai.google.dev/gemma/docs/core/gemma_on_gemini_api
Google AI Studio, pour créer ta clé et tester tes prompts : https://aistudio.google.com
Démarrer avec Gemma (MLH) : https://mlh.link/gemma-quickstart
Guide débutant Gemma (MLH) : https://mlh.link/gemma-beginnerguide
Documentation Gemma (MLH) : https://mlh.link/gemma-docs
Gemma 4 avec l'API Gemini, tutoriel de Philipp Schmid : https://www.philschmid.de/gemma-4-gemini-api
GitHub Copilot gratuit pour les étudiants : https://mlh.link/GitHub
Documentation FastAPI : https://fastapi.tiangolo.com
Le guide compagnon de cet atelier : Coder avec l'IA : le guide complet, du premier prompt à la production
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