Un tuteur adaptatif : besoin et architecture
Le besoin
Une équipe de formation interne veut un assistant qui fait réviser les nouvelles recrues (Python, SQL, outils maison). Trois exigences reviennent dans tous les entretiens :
- Évaluer : ne pas assommer un expert de questions faciles, ni décourager un débutant.
- Adapter : monter ou descendre la difficulté selon les réponses, pas au hasard.
- Se souvenir : l'élève revient trois jours plus tard ; le tuteur doit connaître son niveau et ses points faibles.
Un LLM seul sait poser et corriger des questions. Il ne sait pas se souvenir d'une conversation fermée, et il n'applique pas une règle de progression de façon parfaitement régulière. C'est là qu'interviennent les outils et l'état de session.
Qui fait quoi ?
| Tâche | Qui s'en charge | Pourquoi |
|---|---|---|
| Rédiger une question adaptée au niveau | le modèle (Gemini) | génération de contenu : c'est son point fort |
| Juger si la réponse est juste | le modèle | compréhension du langage, réponses ouvertes |
| Calculer le nouveau niveau | une fonction Python (ajuster_niveau) | règle métier : elle doit être identique pour tous |
| Mémoriser la progression | l'état de session ADK + SQLite | survit à la fermeture du programme |
| Choisir quand enregistrer | le modèle, via un outil | il sait quand l'élève vient de répondre |
Pourquoi la règle « deux bonnes réponses d'affilée : niveau +1 » est-elle codée en Python plutôt que décrite dans le prompt ?
Un modèle suit généralement bien une consigne, mais pas avec la régularité d'une fonction. En Python, la règle est identique pour tous et vérifiable par un test unitaire (test_ajuster_niveau).
L'architecture
sequenceDiagram participant E as Élève participant R as Runner ADK participant M as Gemini participant O as Outils Python participant DB as SQLite E->>R: « Je veux progresser en Python » R->>DB: charge la session et l'état user: R->>M: instruction + progression + message M-->>E: question de positionnement E->>R: réponse R->>M: réponse de l'élève M->>O: enregistrer_reponse(theme, notion, correcte) O->>DB: user:progression mis à jour M-->>E: retour + question suivante
Le Runner est le chef d'orchestre d'ADK : il charge la session, appelle le modèle, exécute les outils demandés et enregistre chaque événement via un service de session.
Les trois portées de l'état dans ADK
L'état (session.state) est un dictionnaire attaché à une session. Un préfixe dans la clé change sa portée :
| Clé | Portée | Exemple dans ce lab |
|---|---|---|
question_en_cours (sans préfixe) | cette session seulement | non utilisé |
user:progression | toutes les sessions de cet utilisateur, pour cette application | niveau, score, notions à revoir |
app:... | tous les utilisateurs de l'application | une banque de questions partagée |
temp:... | l'invocation en cours, jamais sauvegardée | calculs intermédiaires |
C'est le préfixe user: qui permet au tuteur de « se souvenir » quand l'élève ouvre une nouvelle session. Encore faut-il que le service de session écrive sur disque : InMemorySessionService oublie tout à l'arrêt du programme, SqliteSessionService écrit dans un fichier.
Un élève ferme le terminal puis relance « python main.py --user alice » (nouvelle session). Quelles clés retrouve-t-il, avec SqliteSessionService ?
Les clés user: sont partagées par toutes les sessions d'un même utilisateur ; les clés sans préfixe appartiennent à l'ancienne session (récupérable avec --reprendre) ; temp: n'est jamais persisté.
Exercice : découper un autre tuteur
Une école de langues veut un tuteur de vocabulaire anglais. Pour chacune des tâches suivantes, dites si elle revient au modèle, à une fonction Python ou à l'état, et avec quel préfixe :
- proposer une phrase à traduire adaptée au niveau ;
- retenir les 20 derniers mots ratés de l'élève ;
- appliquer « 5 réussites sur un mot : il sort de la liste à revoir » ;
- connaître la liste des mots du programme officiel, la même pour tous.
Voir la solution
- Le modèle : génération adaptée, à partir du niveau injecté dans l'instruction.
- L'état
user:mots_rates, mis à jour par un outil : propre à l'élève, conservé entre sessions. - Une fonction Python appelée par l'outil : règle reproductible et testable.
- L'état
app:programme(ou un simple fichier lu par un outil) : identique pour tous les élèves.
Résultat obtenu
Extrait réel de la démonstration du 2026-10-15 (python main.py --user alice --demo 1 1 1 1 0, puis une nouvelle session) :
ÉLÈVE (juste) > La réponse exacte est la C) `[4, 16]`. [...]
[outil] definir_niveau {'niveau': 3, 'theme': 'python'}
[...]
[outil] enregistrer_reponse {'theme': 'python', 'notion': 'méthode dictionnaire get', 'correcte': True}
TUTEUR > Parfait ! [...] 🎉 **Félicitations ! Tu passes au niveau 4 (avancé) !**
[...]
== session 2
Nouvelle session 33944140-c04d-42eb-a750-4c1e465229b3 (1 session(s) précédente(s))
TUTEUR > Salut ! Content de te retrouver. Tu es actuellement au **niveau 4 (avancé)** en Python,
et nous avons la notion des **décorateurs de fonction** à revoir.Je sais faire
- Expliquer les trois besoins d'un tuteur : évaluer, adapter, se souvenir
- Répartir les tâches entre modèle, fonctions Python et état
- Choisir le bon préfixe d'état (
user:,app:,temp:ou aucun) - Expliquer pourquoi
InMemorySessionServicene suffit pas ici