La mémoire entre sessions avec SQLite
Quel service de session choisir ?
Un service de session est l'objet qui crée, relit et sauvegarde les sessions et leur état. ADK 2.10 en propose plusieurs :
| Service | Où sont les données | Dépendance | Quand l'utiliser |
|---|---|---|---|
InMemorySessionService | mémoire du processus | aucune | tests, démonstrations |
SqliteSessionService | un fichier .db local | aiosqlite (installé avec ADK) | un poste, un petit service |
DatabaseSessionService | PostgreSQL, MySQL, SQLite… | sqlalchemy + un pilote asynchrone | serveur partagé, Cloud SQL |
VertexAiSessionService | service managé Google Cloud | un projet GCP | déploiement sur Vertex AI |
Vous déployez le tuteur sur trois instances Cloud Run derrière un équilibreur de charge. Quel service de session choisir ?
Avec trois instances, un élève peut tomber sur une instance différente à chaque requête : il faut une base commune. Un fichier SQLite local n'est vu que par une instance (et disparaît au redémarrage du conteneur).
Brancher SQLite dans le Runner
DB = os.environ.get("TUTEUR_DB", "tuteur.db")
service = SqliteSessionService(DB)
runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=service)
sessions = (await service.list_sessions(app_name=APP_NAME, user_id=args.user)).sessions
if args.reprendre and sessions:
session = sessions[-1] # triées de la plus ancienne à la plus récente
print(f"Reprise de la session {session.id}")
else:
session = await service.create_session(app_name=APP_NAME, user_id=args.user)Deux façons de « se souvenir » :
flowchart LR
subgraph N["Nouvelle session (par défaut)"]
N1["historique vide"] --- N2["état user: relu depuis la base"]
end
subgraph P["--reprendre"]
P1["historique complet de la dernière session"] --- P2["état user: + état de la session"]
end
DB[("tuteur.db")] --> N
DB --> P
- Nouvelle session : la conversation repart de zéro (moins de jetons envoyés au modèle) mais
user:progressionest là. C'est le mode normal d'un tuteur : chaque séance est une session. --reprendre: la session précédente est rechargée avec tous ses événements ; le modèle relit toute la conversation. Pratique après une coupure, coûteux si la session est longue.
list_sessions renvoie les sessions sans leurs événements ni leur état (c'est un index) ; le Runner recharge la session complète avec get_session à chaque message.
Ce qu'il y a dans le fichier
SqliteSessionService crée quatre tables : app_states, user_states, sessions et events. Les clés user: sont rangées sans leur préfixe dans user_states, une ligne par couple (application, utilisateur) :
import sqlite3
c = sqlite3.connect("tuteur.db")
print(c.execute("SELECT app_name, user_id, state FROM user_states").fetchall())
print(c.execute("SELECT session_id, COUNT(*) FROM events GROUP BY session_id").fetchall())Dans quelle table trouvez-vous la progression d'Alice, et sous quelle clé ?
Le service sépare l'état par portée : les clés user: vont dans user_states, sans le préfixe, et sont fusionnées dans session.state à la lecture. Les événements gardent aussi chaque state_delta, mais la table user_states donne directement la valeur courante.
Exercice : effacer la mémoire d'un élève
Un élève demande l'effacement de ses données. Écrivez une fonction asynchrone oublier(user_id) qui supprime toutes ses sessions avec l'API du service, puis vérifiez avec inspecter.py ce qui reste. Que constatez-vous pour user_states ?
Voir la solution
async def oublier(service, user_id: str) -> None:
for s in (await service.list_sessions(app_name=APP_NAME, user_id=user_id)).sessions:
await service.delete_session(app_name=APP_NAME, user_id=user_id, session_id=s.id)Les sessions et leurs événements disparaissent (suppression en cascade), mais la ligne de user_states subsiste (vérifié avec ADK 2.10.0 : 0 session, 0 événement, 1 ligne user_states) : l'état utilisateur n'est rattaché à aucune session précise. Pour un effacement complet, supprimez aussi cette ligne (DELETE FROM user_states WHERE app_name=? AND user_id=?) et vérifiez avec inspecter.py. Documentez cette procédure : c'est elle que vous appliquerez pour une demande d'effacement.
Résultat obtenu
Contenu réel de tuteur.db après les deux sessions de démonstration du 2026-10-15 :
[('tuteur', 'alice', '{"progression":{"python":{"niveau":4,"serie":[false],"bonnes":2,"total":3,
"a_revoir":["d\\u00e9corateurs de fonction"]}}}')]
[(2, '33944140-c04d-42eb-a750-4c1e465229b3'), (20, 'cb275b42-e4fe-4b0e-9bdb-e0ea0eda016c')]La première session compte 20 événements (messages, appels et réponses d'outils), la seconde 2. Une seule ligne user_states porte la progression : c'est elle que la deuxième session a relue pour saluer l'élève au niveau 4.
Je sais faire
- Choisir entre
InMemorySessionService,SqliteSessionServiceetDatabaseSessionService - Créer une nouvelle session ou reprendre la dernière avec
list_sessions - Retrouver l'état
user:dans le fichier SQLite - Prévoir la suppression des données d'un élève