Oxyb commited on
Commit
975ab8d
·
verified ·
1 Parent(s): d0500bb

Upload 251 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. tp_ia_chatbot-main/tp_ia_chatbot-main/.gitignore +12 -0
  3. tp_ia_chatbot-main/tp_ia_chatbot-main/EVALUATION_TP.md +73 -0
  4. tp_ia_chatbot-main/tp_ia_chatbot-main/PRESENTATION_ETAPES.md +610 -0
  5. tp_ia_chatbot-main/tp_ia_chatbot-main/README.md +146 -0
  6. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/.env.example +28 -0
  7. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/EVALUATION_TP_ETAPE_00.md +66 -0
  8. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/README.md +125 -0
  9. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/config.py +154 -0
  10. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/demo_connexion.py +119 -0
  11. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/requirements.txt +2 -0
  12. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/.env.example +9 -0
  13. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/01_chatbot_naif_no_message_stack.py +57 -0
  14. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/02_chatbot_naif.py +57 -0
  15. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/EVALUATION_TP_ETAPE_01.md +79 -0
  16. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/README.md +91 -0
  17. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/logs/prompts_demo.txt +82 -0
  18. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/requirements.txt +2 -0
  19. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/.env.example +9 -0
  20. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/01_mesurer_kpis.py +128 -0
  21. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/02_kpis_comparatif.py +142 -0
  22. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/EVALUATION_TP_ETAPE_02.md +79 -0
  23. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/README.md +46 -0
  24. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/logs/benchmark_results.json +92 -0
  25. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/prompts_benchmark.txt +8 -0
  26. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/requirements.txt +3 -0
  27. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/.env.example +3 -0
  28. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/01_memory_window.py +103 -0
  29. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/02_memory_summary.py +103 -0
  30. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/EVALUATION_TP_ETAPE_03.md +81 -0
  31. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/README.md +44 -0
  32. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/01_memory_window.log +81 -0
  33. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/02_memory_summary.log +237 -0
  34. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/limite_memort_summary.txt +17 -0
  35. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/requirements.txt +2 -0
  36. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/.env.example +4 -0
  37. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/EVALUATION_TP_ETAPE_04.md +79 -0
  38. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/README.md +57 -0
  39. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/explorer_db.py +135 -0
  40. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/historique.json +78 -0
  41. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_json_chatgpt.log +88 -0
  42. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_sqlite_mistral_localnetwork.log +137 -0
  43. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_json.py +83 -0
  44. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_sqlite.py +149 -0
  45. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/requirements.txt +2 -0
  46. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/.env.example +4 -0
  47. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/EVALUATION_TP_ETAPE_05.md +82 -0
  48. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/README.md +53 -0
  49. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/data/entreprise.txt +101 -0
  50. tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/data/politique.txt +82 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tp_ia_chatbot-main/tp_ia_chatbot-main/etape_08_monitoring/image-1.png filter=lfs diff=lfs merge=lfs -text
37
+ tp_ia_chatbot-main/tp_ia_chatbot-main/etape_08_monitoring/image.png filter=lfs diff=lfs merge=lfs -text
tp_ia_chatbot-main/tp_ia_chatbot-main/.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .env
3
+ etape_05_rag/chroma_db/
4
+ **/avis/
5
+ **/__pycache__/
6
+ **/*.pyc
7
+ **/chat.db
8
+ **/*.bin
9
+ .claude
10
+ .coverage
11
+ **/chroma.sqlite3
12
+ evaluation/
tp_ia_chatbot-main/tp_ia_chatbot-main/EVALUATION_TP.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Évaluation TP — Chatbot IA : du prototype au déploiement
2
+
3
+ > **Bloc CCP4 — Chef de Projet IA**
4
+
5
+ Ce document est le point d'entrée des évaluations formatives du TP.
6
+ Chaque étape a son propre fichier `EVALUATION_TP_ETAPE_XX.md` dans son dossier.
7
+
8
+ **Règle d'or :** Lis le fichier d'évaluation **après** avoir exécuté l'étape, pas avant.
9
+ Les questions de niveau 1 sont impossibles à répondre sans avoir lancé le code — c'est voulu.
10
+
11
+ ---
12
+
13
+ ## Philosophie du TP
14
+
15
+ Une app IA, c'est avant tout **un logiciel à déployer, mesurer et sécuriser**.
16
+ L'objectif n'est pas de produire du code parfait, mais de comprendre ce qui se passe,
17
+ pourquoi ça marche (ou pas), et ce que cela implique en production.
18
+
19
+ Les évaluations suivent 3 niveaux de questionnement :
20
+ - **Niveau 1 — Observation** : tu as vu, tu peux répondre (impossible sans exécuter)
21
+ - **Niveau 2 — Analyse** : tu comprends pourquoi le mécanisme fonctionne
22
+ - **Niveau 3 — Décision** : tu ferais quoi en production, en tant que chef de projet ?
23
+
24
+ ---
25
+
26
+ ## Progression et fichiers d'évaluation
27
+
28
+ | Étape | Thème | Niveau | Durée | Fichier |
29
+ |-------|-------|--------|-------|---------|
30
+ | 00 | Le Moteur — connexion LLM, variables d'env | Débutant | 30 min | [etape_00_moteur/EVALUATION_TP_ETAPE_00.md](etape_00_moteur/EVALUATION_TP_ETAPE_00.md) |
31
+ | 01 | Le Chatbot Naïf — stateless, mémoire côté client | Débutant | 30 min | [etape_01_banane/EVALUATION_TP_ETAPE_01.md](etape_01_banane/EVALUATION_TP_ETAPE_01.md) |
32
+ | 02 | KPIs — latence, TPS, coût, P95 | Débutant | 30 min | [etape_02_kpis/EVALUATION_TP_ETAPE_02.md](etape_02_kpis/EVALUATION_TP_ETAPE_02.md) |
33
+ | 03 | Mémoire Tampon — fenêtre glissante, résumé | Intermédiaire | 45 min | [etape_03_memoire/EVALUATION_TP_ETAPE_03.md](etape_03_memoire/EVALUATION_TP_ETAPE_03.md) |
34
+ | 04 | Persistance — SQLite vs JSON, sessions | Intermédiaire | 45 min | [etape_04_persistance/EVALUATION_TP_ETAPE_04.md](etape_04_persistance/EVALUATION_TP_ETAPE_04.md) |
35
+ | 05 | RAG — ChromaDB, embeddings, hit rate | Intermédiaire | 60 min | [etape_05_rag/EVALUATION_TP_ETAPE_05.md](etape_05_rag/EVALUATION_TP_ETAPE_05.md) |
36
+ | 06 | LangChain — abstraction, swap cloud/local | Intermédiaire | 45 min | [etape_06_langchain/EVALUATION_TP_ETAPE_06.md](etape_06_langchain/EVALUATION_TP_ETAPE_06.md) |
37
+ | 07 | Docker & FastAPI — API REST, containerisation | Intermédiaire | 60 min | [etape_07_docker/EVALUATION_TP_ETAPE_07.md](etape_07_docker/EVALUATION_TP_ETAPE_07.md) |
38
+ | 08 | Monitoring — Prometheus, Grafana, PromQL | Intermédiaire | 60 min | [etape_08_monitoring/EVALUATION_TP_ETAPE_08.md](etape_08_monitoring/EVALUATION_TP_ETAPE_08.md) |
39
+ | 09 | Sécurité — JWT, rate limiting, injection prompt | Avancé | 60 min | [etape_09_securite/EVALUATION_TP_ETAPE_09.md](etape_09_securite/EVALUATION_TP_ETAPE_09.md) |
40
+ | 10 | Test de charge — Locust, SLA, goulots | Avancé | 60 min | [etape_10_locust/EVALUATION_TP_ETAPE_10.md](etape_10_locust/EVALUATION_TP_ETAPE_10.md) |
41
+ | 11 | Tests automatisés — pytest, mock, coverage | Avancé | 60 min | [etape_11_tests/EVALUATION_TP_ETAPE_11.md](etape_11_tests/EVALUATION_TP_ETAPE_11.md) |
42
+ | 12 | Benchmark — LLM-as-Judge, qualité/prix | Avancé | 60 min | [etape_12_benchmark/EVALUATION_TP_ETAPE_12.md](etape_12_benchmark/EVALUATION_TP_ETAPE_12.md) |
43
+ | 13 | Production-Ready — Dockerfile multi-stage, stack complète | Avancé | 90 min | [etape_13_deployable/EVALUATION_TP_ETAPE_13.md](etape_13_deployable/EVALUATION_TP_ETAPE_13.md) |
44
+ | 14 | Deployed — Kubernetes, CI/CD, GitOps | Avancé | 90-120 min | [etape_14_deployed/EVALUATION_TP_ETAPE_14.md](etape_14_deployed/EVALUATION_TP_ETAPE_14.md) |
45
+
46
+ ---
47
+
48
+ ## Arc narratif du TP
49
+
50
+ ```
51
+ 00 → "Le LLM ne se souvient de rien — comment construire une mémoire ?"
52
+ 03 → "La mémoire a un coût — comment l'optimiser ?"
53
+ 05 → "Le LLM ne sait que ce qu'on lui donne — RAG comme mémoire externe"
54
+ 07 → "Le chatbot devient un service — comment l'exposer ?"
55
+ 08 → "On ne peut pas corriger ce qu'on ne voit pas — métriques"
56
+ 09 → "Un service exposé est une cible — sécurité en profondeur"
57
+ 10 → "Est-ce que ça tient la charge ? — test de limite"
58
+ 11 → "Est-ce qu'on peut livrer en confiance ? — tests automatisés"
59
+ 13 → "Toutes les briques ensemble — prêt pour la prod"
60
+ 14 → "La prod, c'est quoi concrètement ? — CI/CD + K8S + GitOps"
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Lien avec le dossier E8
66
+
67
+ Chaque fichier d'évaluation contient une section **"Lien avec le dossier E8"**.
68
+ Ces questions sont directement réutilisables dans les parties :
69
+ - Développement et choix d'architecture
70
+ - Tests et validation
71
+ - Déploiement et mise en production
72
+ - Prévention des risques
73
+ - Aspects réglementaires (RGPD, sécurité)
tp_ia_chatbot-main/tp_ia_chatbot-main/PRESENTATION_ETAPES.md ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Présentation des étapes — TP Chatbot IA : du prototype au déploiement
2
+
3
+ > **Usage** : Ce document est le prompt de référence pour présenter l'ensemble du projet.
4
+ > Il donne le contexte, la progression pédagogique et les points saillants de chaque étape.
5
+ > Les étapes 13 et 14 sont documentées en détail car elles constituent l'aboutissement technique du TP.
6
+
7
+ ## Contexte général
8
+
9
+ Ce TP construit un chatbot d'entreprise de bout en bout — du premier appel API jusqu'au
10
+ déploiement Kubernetes avec pipeline CI/CD. Chaque étape ajoute une brique, et chaque brique
11
+ répond à un problème concret rencontré en production.
12
+
13
+ **Stack finale :** Python · FastAPI · LangChain · ChromaDB · SQLite · Prometheus · Grafana ·
14
+ Docker multi-stage · Nginx · JWT · pytest · Kubernetes (kind/GKE) · GitHub Actions · ArgoCD
15
+
16
+ ## Étapes 00 à 12 — Construction progressive
17
+
18
+ ### Étape 00 — Le Moteur
19
+ **Ce qu'on fait :** Premier appel à un LLM (OpenAI ou modèle local via LM Studio).
20
+ **Ce qu'on apprend :** Variables d'environnement, API REST d'OpenAI, concept cloud vs local.
21
+ **Concept clé :** Le LLM est une boîte noire sans état — il ne retient rien entre les appels.
22
+
23
+ ### Étape 01 — Le Chatbot Naïf (La Banane)
24
+ **Ce qu'on fait :** Un chatbot fonctionnel en ~15 lignes. On simule la mémoire en accumulant
25
+ les messages dans une liste `msgs` envoyée à chaque appel.
26
+ **Ce qu'on apprend :** Format `messages` (role: system/user/assistant), nature stateless du LLM,
27
+ mémoire côté client = illusion.
28
+ **Concept clé :** La "mémoire" n'est pas dans le modèle — elle est dans notre code. Si on
29
+ redémarre le script, tout est perdu.
30
+
31
+ ### Étape 02 — Mesurer pour Comprendre (KPIs)
32
+ **Ce qu'on fait :** Ajout de métriques en temps réel : latence, tokens/seconde, coût/requête, P95.
33
+ **Ce qu'on apprend :** Mesurer avant d'optimiser. Différence cloud (~80 TPS) vs local (~15 TPS).
34
+ Formule de coût : `(prompt_tokens × $0.00015 + completion_tokens × $0.00060) / 1000`.
35
+ **Concept clé :** Sans mesure, on optimise au hasard. Le P95 capture les cas lents que la moyenne cache.
36
+
37
+ ### Étape 03 — La Mémoire Tampon
38
+ **Ce qu'on fait :** Implémentation d'une fenêtre glissante (`MAX_HISTORY=8`) et d'un mode résumé
39
+ automatique pour les longues conversations.
40
+ **Ce qu'on apprend :** Le context window est une ressource limitée et payante. Stratégies :
41
+ fenêtre fixe (simple) vs résumé (coûteux mais préserve l'essentiel).
42
+ **Concept clé :** `coût ∝ nombre de tokens` — garder toute l'histoire est impraticable en prod.
43
+
44
+ ### Étape 04 — La Persistance
45
+ **Ce qu'on fait :** Sauvegarde des conversations dans SQLite (et comparaison avec JSON).
46
+ Sessions multi-utilisateurs, restauration au redémarrage.
47
+ **Ce qu'on apprend :** SQLite vs JSON : race conditions, transactions ACID, scalabilité.
48
+ `explorer_db.py` pour inspecter les données.
49
+ **Concept clé :** JSON avec 1000 users simultanés = corruption garantie. SQLite gère les
50
+ écritures concurrentes par verrous transactionnels.
51
+
52
+ ### Étape 05 — RAG (Retrieval-Augmented Generation)
53
+ **Ce qu'on fait :** Connexion du chatbot à une base documentaire (ChromaDB). Indexation de
54
+ fichiers `.txt` en chunks, recherche sémantique par embeddings, injection du contexte dans le prompt.
55
+ **Ce qu'on apprend :** Pipeline RAG complet : embed → store → retrieve → inject → generate.
56
+ KPI : Hit Rate > 80%.
57
+ **Concept clé :** Le LLM ne "sait" que ce qu'on lui donne. RAG = mémoire longue terme
58
+ externe, sans coût de contexte permanent.
59
+
60
+ ### Étape 06 — LangChain
61
+ **Ce qu'on fait :** Refactorisation du code avec LangChain. Swap cloud/local en 1 ligne.
62
+ `ConversationBufferWindowMemory`, `ConversationalRetrievalChain`.
63
+ **Ce qu'on apprend :** Abstraction LLM — même code, backend différent. Gain : moins de
64
+ boilerplate, plus de fonctionnalités.
65
+ **Concept clé :** `ChatOpenAI` → `ChatOllama` en changeant une seule ligne. L'interopérabilité
66
+ est la vraie valeur ajoutée de LangChain.
67
+
68
+ ### Étape 07 — Docker & FastAPI
69
+ **Ce qu'on fait :** Exposition du chatbot via une API REST FastAPI. Containerisation Docker.
70
+ Endpoints : `/health`, `/chat`, `/history/{id}`, `/sessions`, `/docs` (Swagger auto).
71
+ **Ce qu'on apprend :** FastAPI, Pydantic, Docker build + run, docker-compose.
72
+ **Concept clé :** L'API REST transforme le chatbot script en service interrogeable par
73
+ n'importe quel client (web, mobile, autre service).
74
+
75
+ ### Étape 08 — Prometheus & Grafana
76
+ **Ce qu'on fait :** Instrumentation avec `prometheus_client`. Dashboard Grafana avec
77
+ latence P95, RPS, tokens, erreurs, mémoire processus.
78
+ **Ce qu'on apprend :** Types de métriques (Counter, Histogram, Gauge), scraping Prometheus,
79
+ PromQL de base, provisioning automatique de datasource.
80
+ **Concept clé :** On ne peut pas corriger ce qu'on ne voit pas. Les métriques sont la
81
+ fenêtre sur le comportement réel du service en production.
82
+
83
+ ### Étape 09 — Sécurité
84
+ **Ce qu'on fait :** Authentification JWT, rate limiting (slowapi), filtrage prompt injection
85
+ (12 patterns regex), sanitization XSS, CORS restreint, hachage bcrypt.
86
+ **Ce qu'on apprend :** Chaque couche protège contre un vecteur d'attaque différent.
87
+ **Concept clé :** Sécurité en profondeur — une seule couche ne suffit jamais.
88
+ ```
89
+ Requête → CORS → Rate Limit → JWT → Sanitize → Prompt Guard → LLM
90
+ ```
91
+
92
+ ### Étape 10 — Test de Charge (Locust)
93
+ **Ce qu'on fait :** Simulation de 5 / 20 / 50 / 100 utilisateurs simultanés. Mesure du
94
+ taux d'échec, P50, P95, RPS.
95
+ **Ce qu'on apprend :** Identifier le goulot d'étranglement (API ? LLM ? DB ?). SLA réalistes :
96
+ taux d'échec < 1%, P95 < 5s, RPS > 20.
97
+ **Concept clé :** Le test de charge révèle les limites avant que les vrais utilisateurs les
98
+ atteignent. Mieux vaut crasher en test qu'en prod.
99
+
100
+ ### Étape 11 — Tests automatisés (pytest)
101
+ **Ce qu'on fait :** 3 niveaux de tests : unitaires (fonctions isolées, LLM mocké),
102
+ intégration (API + DB, LLM mocké), E2E (API réelle). Coverage ≥ 70% requis.
103
+ **Ce qu'on apprend :** Pourquoi mocker le LLM (rapidité, coût, déterminisme).
104
+ `conftest.py` et fixtures partagées. `pytest --cov`.
105
+ **Concept clé :** Un test qui appelle l'API OpenAI coûte $0.001 et prend 2s. Avec 100 tests
106
+ lancés 50 fois par jour, c'est $5/jour juste pour les tests.
107
+
108
+ ### Étape 12 — Benchmark LLM
109
+ **Ce qu'on fait :** Comparaison objective de plusieurs modèles (GPT-4o, GPT-4o-mini, Mistral 7B)
110
+ sur un jeu de 20 questions. Notation automatique par LLM-as-Judge (score 1-10).
111
+ **Ce qu'on apprend :** Évaluation reproductible. Rapport qualité/prix. `eval_set.jsonl` comme
112
+ source de vérité.
113
+ **Concept clé :** GPT-4o-mini : 8.2/10 pour $0.004 vs GPT-4o : 9.1/10 pour $0.024.
114
+ Pour 90% des cas d'usage, le mini gagne. Le benchmark le prouve objectivement.
115
+
116
+ ## Étape 13 — Production-Ready (`etape_13_deployable`)
117
+
118
+ ### Vue d'ensemble
119
+
120
+ L'étape 13 est la **synthèse du projet** : toutes les briques précédentes sont
121
+ réunies, durcies pour la production, et livrées comme une application déployable.
122
+ C'est la première étape où le code passe de "ça marche sur ma machine" à
123
+ "ça peut tourner en production".
124
+
125
+ ```
126
+ Étudiants arrivent avec → chatbot fonctionnel (étapes 00-12, séparées)
127
+ Étape 13 produit → application unifiée, testée, containerisée, monitorée
128
+ ```
129
+
130
+ ### Ce qui est assemblé
131
+
132
+ | Fonctionnalité | Source | Ajout étape 13 |
133
+ |---|---|---|
134
+ | LangChain + cloud/local swap | Étape 06 | — |
135
+ | RAG ChromaDB (graceful fallback) | Étapes 04/05 | Fallback si collection absente |
136
+ | Prometheus + Grafana | Étape 08 | Métriques étendues (voir ci-dessous) |
137
+ | JWT + Rate limiting + Prompt guard | Étape 09 | — |
138
+ | Suite de tests pytest | Étape 11 | Intégrée au build Docker |
139
+ | **Dockerfile multi-stage** | **Nouveau** | Tests bloquants au build |
140
+ | **Nginx reverse proxy** | **Nouveau** | TLS, proxy pass, headers de sécurité |
141
+ | **Script d'évaluation RAG** | **Nouveau** | `eval_set.jsonl` + score keywords |
142
+ | **Alertes Prometheus** | **Nouveau** | 5 règles d'alerte |
143
+ | **Alertes Grafana** | **Nouveau** | Mémoire système > 95% |
144
+
145
+ ### Architecture
146
+
147
+ ```
148
+ Client
149
+
150
+ ├── dev → FastAPI :8000 (direct)
151
+ └── prod → Nginx :443 → FastAPI :8000
152
+
153
+ ┌───────────────┼──────────────────┐
154
+ │ │ │
155
+ JWT + Rate LangChain LLM Prometheus
156
+ Limit + │ /metrics
157
+ Prompt Guard ┌──┴──────────┐
158
+ │ ChromaDB │ ← RAG optionnel
159
+ │ (optionnel)│ (fallback silencieux)
160
+ └─────────────┘
161
+
162
+ ┌────────────┴────────────┐
163
+ │ │
164
+ SQLite Named volume
165
+ (historique) /app/data/
166
+ ```
167
+
168
+ ### Le Dockerfile multi-stage — point pédagogique clé
169
+
170
+ ```dockerfile
171
+ Stage 1 : builder → apt + pip (dépendances)
172
+ Stage 2 : test → pytest (exit 1 si échec → build bloqué)
173
+ Stage 3 : production → image finale, user non-root, sans outils de build
174
+ ```
175
+
176
+ **Pourquoi c'est important :**
177
+ - Les tests font partie du pipeline de build — on ne peut pas livrer du code cassé
178
+ - L'image finale ne contient pas `pytest`, `gcc`, ni les sources intermédiaires
179
+ - L'image de prod est plus petite et moins exposée
180
+
181
+ **Effet en pratique :**
182
+ ```bash
183
+ make dev-bg # échoue si un test est rouge → le développeur ne peut pas ignorer
184
+ ```
185
+
186
+ ### Métriques Prometheus étendues
187
+
188
+ L'étape 13 va bien au-delà du monitoring basique de l'étape 08 :
189
+
190
+ #### Métriques métier
191
+ | Métrique | Type | Nouveauté |
192
+ |---|---|---|
193
+ | `chat_requests_total` | Counter | Labels : modèle, status (`success`/`error`), rag (`true`/`false`) |
194
+ | `chat_tokens_total` | Counter | Labels : `prompt`/`completion` |
195
+ | `chat_latency_seconds` | Histogram | 8 buckets 0.1s → 30s |
196
+ | `rag_retrieval_seconds` | Histogram | Latence ChromaDB séparément |
197
+ | `chat_context_messages` | Gauge | **Nouveau** — taille de l'historique à chaque requête |
198
+ | `chat_errors_total` | Counter | Par type d'exception |
199
+ | `auth_attempts_total` | Counter | Par status (`success`/`failure`) |
200
+ | `prompt_injection_blocked_total` | Counter | Tentatives d'injection bloquées |
201
+
202
+ #### Métriques système (nouvelles)
203
+ | Métrique | Type | Description |
204
+ |---|---|---|
205
+ | `process_memory_bytes` | Gauge | RAM RSS du processus Python |
206
+ | `system_memory_total_bytes` | Gauge | RAM totale de la machine hôte |
207
+ | `system_memory_used_bytes` | Gauge | RAM utilisée sur la machine hôte |
208
+ | `process_cpu_percent` | Gauge | CPU % du processus chatbot |
209
+ | `system_cpu_percent` | Gauge | CPU % global du système |
210
+ | `gpu_utilization_percent` | Gauge | GPU % par carte NVIDIA (si disponible) |
211
+ | `gpu_memory_used_bytes` | Gauge | VRAM utilisée par carte |
212
+ | `gpu_memory_total_bytes` | Gauge | VRAM totale par carte |
213
+
214
+ > Les métriques GPU utilisent `pynvml`. Détection automatique au démarrage :
215
+ > si aucun GPU NVIDIA n'est présent, les jauges existent mais ne sont jamais renseignées
216
+ > — les panels Grafana affichent "No data" sans erreur.
217
+
218
+ ### Dashboard Grafana
219
+
220
+ Le dashboard `grafana/provisioning/dashboards/chatbot.json` contient ~550 lignes.
221
+ Il est **provisionné automatiquement** (aucune action manuelle à l'import).
222
+
223
+ **Panels :**
224
+ - Requêtes totales + taux d'erreur (PromQL avec `or vector(0)` pour éviter "No data")
225
+ - Latence P50 / P95 / P99
226
+ - Tokens consommés (prompt vs completion)
227
+ - Taux RAG vs direct
228
+ - Mémoire : RSS processus + utilisée système + totale système (3 courbes)
229
+ - CPU : processus + système (2 courbes)
230
+ - GPU : utilisation % et VRAM (panels conditionnels)
231
+ - Injections de prompt bloquées
232
+
233
+ **Correctif clé :** La datasource doit avoir `uid: prometheus` (fixe) dans le YAML de
234
+ provisioning. Sans ça, Grafana génère un uid aléatoire à chaque redémarrage — tous
235
+ les panels perdent leur source et affichent "No data".
236
+
237
+ ### Alertes
238
+
239
+ #### Prometheus (`prometheus-alerts.yml`)
240
+ | Alerte | Condition | Sévérité |
241
+ |---|---|---|
242
+ | `ChatbotDown` | API indisponible > 1 min | critical |
243
+ | `HighErrorRate` | Taux erreur > 10% sur 5 min | warning |
244
+ | `HighLatency` | P95 > 5s sur 5 min | warning |
245
+ | `HighMemoryUsage` | RAM processus > 500 MB | warning |
246
+ | `PromptInjectionAttempts` | > 10 injections / 5 min | warning |
247
+
248
+ #### Grafana (`grafana/provisioning/alerting/alerts.yml`)
249
+ | Alerte | Condition | Sévérité |
250
+ |---|---|---|
251
+ | Mémoire système > 95% | `system_memory_used / system_memory_total > 95%` pendant 1 min | critical |
252
+
253
+ ### Évaluation RAG intégrée
254
+
255
+ ```bash
256
+ make eval # 15 questions → vérification par mots-clés attendus
257
+ make smoke # 5 questions rapides → alimente Grafana + vérifie l'API
258
+ ```
259
+
260
+ Format `data/eval_set.jsonl` :
261
+ ```json
262
+ {"id": "q001", "question": "Quel est le prix de CloudSync Pro ?",
263
+ "expected_keywords": ["29", "mois"], "category": "pricing", "difficulty": "facile"}
264
+ ```
265
+
266
+ Sortie :
267
+ ```
268
+ [PASS] q001 Quel est le prix de CloudSync Pro ? 2.9s [RAG 3src]
269
+ [FAIL] q003 Qui est le CEO de TechCorp ? 1.9s [RAG 3src]
270
+ → Je n'ai pas cette information.
271
+ ✗ manquants : Marie Dupont
272
+
273
+ Résultat : 7/15 PASS (47%) 1 PARTIEL 7 FAIL
274
+ ```
275
+
276
+ ### Commandes de référence
277
+
278
+ ```bash
279
+ make dev-bg # démarre la stack (rebuild si code changé)
280
+ make stop # arrête tout
281
+ make status # health check + statut des conteneurs
282
+ make test # 73 tests, coverage ≥ 70% requis
283
+ make test-security # bandit : zéro vulnérabilité critique
284
+ make smoke # 5 questions → vérifie l'API + alimente Grafana
285
+ make eval # évaluation complète RAG
286
+ make index-rag-docker # indexation des documents dans le conteneur
287
+ make prod # stack production avec Nginx + TLS
288
+ ```
289
+
290
+ ### Problèmes connus documentés
291
+
292
+ **Bug iptables Docker 28.4 + kernel 6.17+**
293
+ Docker 28.4.0 crée de nouvelles chaînes nft au démarrage mais continue d'utiliser les
294
+ anciennes chaînes `DOCKER-ISOLATION-STAGE-1/2` pour les bridges user-defined.
295
+ Fix documenté dans `docker-compose.yml` (commentaire en-tête) :
296
+ ```bash
297
+ # Fix temporaire
298
+ sudo iptables -t filter -N DOCKER-ISOLATION-STAGE-1 2>/dev/null || true
299
+ sudo iptables -t filter -N DOCKER-ISOLATION-STAGE-2 2>/dev/null || true
300
+ sudo iptables -t filter -A DOCKER-ISOLATION-STAGE-2 -j RETURN
301
+ # Vérification
302
+ docker network create test-net && echo "OK" && docker network rm test-net
303
+ ```
304
+
305
+ **GPU Docker passthrough**
306
+ Nécessite NVIDIA Container Toolkit (pas dans les dépôts Ubuntu standard).
307
+ Section GPU dans `docker-compose.yml` commentée par défaut avec instructions complètes.
308
+
309
+ ## Étape 14 — Déployé (`etape_14_deployed`)
310
+
311
+ ### Vue d'ensemble
312
+
313
+ L'étape 14 répond à la question : **"L'application est prête — où et comment on la déploie ?"**
314
+ Elle propose plusieurs stratégies selon le contexte (budget, équipe, infrastructure),
315
+ toutes partageant la même image Docker produite à l'étape 13.
316
+
317
+ ```
318
+ Étape 13 → image Docker → Étape 14 → déploiement(s)
319
+ ```
320
+
321
+ ### Quatre stratégies de déploiement
322
+
323
+ ```
324
+ Besoin Solution
325
+ ────────────────────────────────────────────────────────
326
+ Pipeline CI/CD visuel local → act + Gitea + ArgoCD + kind ← nouveau
327
+ Tester K8S sans compte cloud → Docker Compose VPS
328
+ 1 serveur, budget limité → Docker Compose VPS
329
+ Serverless, scale to zero → Google Cloud Run
330
+ Déjà sur AWS → ECS Fargate
331
+ Forte charge, équipe DevOps → GKE ou EKS
332
+ ```
333
+
334
+ ### Option 0 — Pipeline CI/CD local complet
335
+
336
+ **Pourquoi c'est la première option :** Avant de déployer sur un vrai cloud, on valide
337
+ le pipeline complet en local. L'architecture est **identique** à la production —
338
+ seules les URLs changent.
339
+
340
+ ```
341
+ git push → Gitea (:3001) → act (CI) → registry local (:5001)
342
+
343
+ ArgoCD (dans kind) ← surveille Gitea
344
+
345
+ kind cluster (:8080) ← pull depuis registry local
346
+ ```
347
+
348
+ **Composants :**
349
+
350
+ | Composant | Local | Équivalent cloud |
351
+ |---|---|---|
352
+ | Git server | Gitea (Docker, port 3001) | GitHub |
353
+ | CI runner | `act` (GitHub Actions local) | GitHub Actions runners |
354
+ | Registry images | localhost:5001 | GHCR / ECR / Artifact Registry |
355
+ | Kubernetes | kind (Docker local) | GKE / EKS / AKS |
356
+ | GitOps | ArgoCD (dans kind) | ArgoCD (dans le cluster cloud) |
357
+
358
+ **Installation en une commande :**
359
+ ```bash
360
+ ./scripts/setup-local-pipeline.sh setup
361
+ ```
362
+
363
+ **Workflow de développement :**
364
+ ```bash
365
+ # 1. Modifier le code
366
+ # 2. CI local (lint + tests + docker build)
367
+ ./scripts/setup-local-pipeline.sh ci
368
+
369
+ # 3. Build et push vers le registry local
370
+ ./scripts/setup-local-pipeline.sh push-image
371
+
372
+ # 4. Pousser sur Gitea
373
+ git push local main
374
+
375
+ # 5. ArgoCD détecte et déploie automatiquement
376
+ # Voir : https://localhost:8081 (port-forward ArgoCD)
377
+ ```
378
+
379
+ ### Option 1 — Docker Compose sur VPS
380
+
381
+ La solution la plus simple pour un vrai déploiement.
382
+ ```bash
383
+ ./scripts/deploy-docker.sh --host user@votre-serveur.com
384
+ ```
385
+ Le script se connecte en SSH, clone le repo, configure le `.env`, lance
386
+ `docker compose -f docker/docker-compose.prod.yml up -d`.
387
+
388
+ L'image est tirée depuis GHCR — pas de build sur le serveur.
389
+
390
+ **Monitoring :** `docker-compose.prod.yml` référence directement les configs de l'étape 13 :
391
+ ```
392
+ ../etape_13_deployable/prometheus.yml → config Prometheus
393
+ ../etape_13_deployable/grafana/provisioning/ → datasource + dashboard automatiques
394
+ ```
395
+ Aucune configuration supplémentaire — Grafana démarre avec le dashboard préconfiguré.
396
+
397
+ ### Options 2 & 3 — Cloud (GCP / AWS)
398
+
399
+ Documentées dans `docs/DEPLOY_GCP.md` et `docs/DEPLOY_AWS.md`.
400
+
401
+ **GCP Cloud Run (serverless recommandé pour démarrer) :**
402
+ ```bash
403
+ gcloud builds submit --tag europe-west1-docker.pkg.dev/MON_PROJET/chatbot/api
404
+ gcloud run deploy chatbot-api \
405
+ --image europe-west1-docker.pkg.dev/MON_PROJET/chatbot/api \
406
+ --set-secrets OPENAI_API_KEY=openai-key:latest \
407
+ --min-instances 0 --max-instances 10
408
+ ```
409
+
410
+ **AWS ECS Fargate :**
411
+ ```bash
412
+ aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
413
+ docker push $ECR_URI/chatbot-api:latest
414
+ aws ecs create-service --cli-input-json file://ecs-service.json
415
+ ```
416
+
417
+ ### Kubernetes — Manifests détaillés
418
+
419
+ Structure `k8s/` :
420
+
421
+ ```
422
+ k8s/
423
+ ├── namespace.yaml → namespace "chatbot"
424
+ ├── configmap.yaml → variables non-sensibles
425
+ ├── secret.yaml → ⚠ placeholders uniquement
426
+ ├── pvc.yaml → 3 volumes : data, chroma, grafana
427
+ ├── chatbot-deployment.yaml → Deployment + init container RAG
428
+ ├── chatbot-service.yaml → ClusterIP
429
+ ├── chatbot-hpa.yaml → HPA : 1 → 10 replicas (CPU 70% / RAM 80%)
430
+ ├── ingress.yaml → Nginx Ingress + TLS cert-manager
431
+ ├── kustomization.yaml → orchestration des manifests
432
+ ├── monitoring/
433
+ │ ├── prometheus-deployment.yaml → Prometheus + RBAC + ServiceAccount
434
+ │ ├── grafana-deployment.yaml → Grafana + volumeMounts subPath
435
+ │ └── grafana-provisioning-configmap.yaml → datasource uid:prometheus + dashboard provider
436
+ └── argocd/
437
+ └── application.yaml → Application ArgoCD (GitOps)
438
+ ```
439
+
440
+ **Points notables :**
441
+
442
+ `chatbot-deployment.yaml` — init container RAG :
443
+ L'image de l'application s'initialise avec un init container qui peut pré-indexer
444
+ les documents RAG avant que le pod principal démarre.
445
+
446
+ `chatbot-hpa.yaml` — HPA (Horizontal Pod Autoscaler) :
447
+ ```yaml
448
+ minReplicas: 1
449
+ maxReplicas: 10
450
+ # Scale up si CPU > 70% ou mémoire > 80%
451
+ # Scale down après 5 min de charge réduite
452
+ ```
453
+
454
+ `grafana-deployment.yaml` — provisioning via subPath :
455
+ ```yaml
456
+ volumeMounts:
457
+ - name: provisioning
458
+ mountPath: /etc/grafana/provisioning/datasources/datasources.yaml
459
+ subPath: datasources.yaml # montage d'un seul fichier du ConfigMap
460
+ - name: provisioning
461
+ mountPath: /etc/grafana/provisioning/dashboards/dashboards.yaml
462
+ subPath: dashboards.yaml
463
+ ```
464
+
465
+ `grafana-provisioning-configmap.yaml` — datasource avec uid fixe :
466
+ ```yaml
467
+ datasources:
468
+ - name: Prometheus
469
+ uid: prometheus # UID FIXE — requis pour que les panels trouvent la datasource
470
+ url: http://prometheus:9090
471
+ ```
472
+
473
+ ### Pipeline CI/CD GitHub Actions
474
+
475
+ Trois workflows dans `.github/workflows/` :
476
+
477
+ #### `ci.yml` — Déclenché sur chaque push/PR
478
+ ```
479
+ lint → ruff (style + erreurs statiques)
480
+ tests → pytest (working-directory: etape_13_deployable, coverage ≥ 70%)
481
+ security → bandit (vulnérabilités) + safety (dépendances)
482
+ docker → build stage test + stage production (validation uniquement, pas de push)
483
+ ```
484
+
485
+ #### `cd.yml` — Déclenché sur merge main ou tag `v*.*.*`
486
+ ```
487
+ test → reprend ci.yml (obligatoire)
488
+ build-and-push → GHCR : tags sha-{short} + semver + latest
489
+ SBOM généré pour traçabilité
490
+ deploy-staging → SSH + Docker Compose (auto sur merge main)
491
+ deploy-prod → SSH + Docker Compose (déclenché par tag v*.*.*)
492
+ ```
493
+
494
+ #### `security.yml` — Hebdomadaire
495
+ ```
496
+ safety → audit des dépendances Python
497
+ trivy → scan de l'image Docker → résultats dans GitHub Security tab
498
+ ```
499
+
500
+ **Déclencher manuellement :**
501
+ ```bash
502
+ # Déployer en staging
503
+ gh workflow run cd.yml --field environment=staging
504
+
505
+ # Créer un tag de release (déclenche le déploiement prod)
506
+ git tag -a v1.2.3 -m "Release v1.2.3"
507
+ git push origin v1.2.3
508
+ ```
509
+
510
+ ### Kubernetes local avec kind — Déploiement pas à pas
511
+
512
+ Pour tester les manifests sans compte cloud :
513
+
514
+ ```bash
515
+ # 1. Créer le cluster (1 control-plane + 2 workers + Nginx Ingress + cert-manager)
516
+ ./scripts/local-cloud.sh setup
517
+
518
+ # 2. Build et chargement de l'image dans kind (évite le pull depuis GHCR)
519
+ docker build --target production -t chatbot-api:local etape_13_deployable/
520
+ kind load docker-image chatbot-api:local --name chatbot-local
521
+
522
+ # 3. Créer les secrets
523
+ kubectl create namespace chatbot
524
+ kubectl create secret generic chatbot-secrets \
525
+ --from-literal=openai-api-key="sk-votre-cle" \
526
+ --from-literal=secret-key="$(openssl rand -hex 32)" \
527
+ --from-literal=grafana-password="admin123" \
528
+ -n chatbot
529
+
530
+ # 4. Déployer
531
+ kubectl apply -k etape_14_deployed/k8s/
532
+ kubectl rollout status deployment/chatbot-api -n chatbot --timeout=3m
533
+
534
+ # 5. Déployer le monitoring
535
+ kubectl apply -f etape_14_deployed/k8s/monitoring/grafana-provisioning-configmap.yaml
536
+ kubectl apply -f etape_14_deployed/k8s/monitoring/prometheus-deployment.yaml
537
+ kubectl apply -f etape_14_deployed/k8s/monitoring/grafana-deployment.yaml
538
+
539
+ # Tester
540
+ curl http://localhost:8080/health
541
+ ```
542
+
543
+ ### ArgoCD — GitOps visuel
544
+
545
+ ArgoCD surveille le repo Git et synchronise automatiquement le cluster quand le code change.
546
+
547
+ ```bash
548
+ # Installer ArgoCD dans le cluster
549
+ kubectl create namespace argocd
550
+ kubectl apply -n argocd \
551
+ -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
552
+
553
+ # Accéder à l'UI
554
+ kubectl port-forward svc/argocd-server -n argocd 8081:443 &
555
+ # → https://localhost:8081
556
+
557
+ # Appliquer l'Application (surveille Gitea, déploie dans kind)
558
+ kubectl apply -f etape_14_deployed/k8s/argocd/application.yaml
559
+ ```
560
+
561
+ **Ce que l'UI ArgoCD montre :**
562
+ - Arbre de toutes les ressources K8S (Deployment, Service, Ingress, PVC, HPA…)
563
+ - Statut de synchronisation : `Synced` / `OutOfSync` / `Degraded`
564
+ - Historique des déploiements avec possibilité de rollback en 1 clic
565
+ - Diff entre l'état Git et l'état du cluster
566
+
567
+ ### Scaling et rollback K8S
568
+
569
+ ```bash
570
+ # Scaling manuel
571
+ kubectl scale deployment chatbot-api --replicas=5 -n chatbot
572
+
573
+ # Rolling update (zero-downtime)
574
+ kubectl set image deployment/chatbot-api chatbot=chatbot-api:v2 -n chatbot
575
+ kubectl rollout status deployment/chatbot-api -n chatbot
576
+
577
+ # Rollback
578
+ kubectl rollout undo deployment/chatbot-api -n chatbot
579
+ kubectl rollout undo deployment/chatbot-api --to-revision=2 -n chatbot
580
+
581
+ # Historique
582
+ kubectl rollout history deployment/chatbot-api -n chatbot
583
+ ```
584
+
585
+ ### Synthèse pédagogique étapes 13 & 14
586
+
587
+ | Question | Étape 13 | Étape 14 |
588
+ |---|---|---|
589
+ | **Le code est-il correct ?** | Tests bloquants au build Docker | CI GitHub Actions obligatoire avant merge |
590
+ | **Est-il sécurisé ?** | bandit + safety dans `make test-security` | `security.yml` hebdomadaire + Trivy |
591
+ | **Comment le surveiller ?** | Prometheus + Grafana + alertes | Même stack, déployée dans K8S |
592
+ | **Comment le livrer ?** | `make prod` (Docker Compose local) | GitHub Actions → GHCR → Staging → Prod |
593
+ | **Comment le scaler ?** | — | HPA K8S (2 → 10 replicas) |
594
+ | **Comment rollback ?** | `docker compose up` version précédente | `kubectl rollout undo` / ArgoCD 1 clic |
595
+ | **Comment simuler tout ça ?** | — | act + Gitea + kind + ArgoCD (100% local) |
596
+
597
+ ## Arc narratif pour la présentation
598
+
599
+ ```
600
+ 00 → "Le LLM ne se souvient de rien — comment construire une mémoire ?"
601
+ 03 → "La mémoire a un coût — comment l'optimiser ?"
602
+ 05 → "Le LLM ne sait que ce qu'on lui donne — RAG comme mémoire externe"
603
+ 07 → "Le chatbot devient un service — comment l'exposer ?"
604
+ 08 → "On ne peut pas corriger ce qu'on ne voit pas — métriques"
605
+ 09 → "Un service exposé est une cible — sécurité en profondeur"
606
+ 10 → "Est-ce que ça tient la charge ? — test de limite"
607
+ 11 → "Est-ce qu'on peut livrer en confiance ? — tests automatisés"
608
+ 13 → "Toutes les briques ensemble — prêt pour la prod"
609
+ 14 → "La prod, c'est quoi concrètement ? — CI/CD + K8S + GitOps"
610
+ ```
tp_ia_chatbot-main/tp_ia_chatbot-main/README.md ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TP IA — Construction d'un Chatbot de A à Z
2
+
3
+ Bienvenue dans ce TP pratique (Travaux Pratiques) sur la construction d'un chatbot IA complet.
4
+ Vous allez partir d'un simple script Python de 10 lignes et arriver à une API sécurisée,
5
+ monitorée, testée et benchmarkée.
6
+
7
+ ---
8
+
9
+ ## Vue d'ensemble du projet
10
+
11
+ ```
12
+ tp_ia_chatbot/
13
+ ├── etape_00_moteur/ # Connexion au LLM (cloud ou local)
14
+ ├── etape_01_banane/ # Chatbot naïf — le problème d'amnésie
15
+ ├── etape_02_kpis/ # Mesurer latence, TPS, coût
16
+ ├── etape_03_memoire/ # Fenêtre glissante + résumé automatique
17
+ ├── etape_04_persistance/ # SQLite et JSON — sauvegarder les sessions
18
+ ├── etape_05_rag/ # RAG avec ChromaDB — grounding dans vos docs
19
+ ├── etape_06_langchain/ # LangChain — framework d'orchestration
20
+ ├── etape_07_docker/ # API FastAPI dockerisée
21
+ ├── etape_08_monitoring/ # Prometheus + Grafana
22
+ ├── etape_09_securite/ # JWT, rate limiting, sanitisation
23
+ ├── etape_10_locust/ # Tests de charge avec Locust
24
+ ├── etape_11_tests/ # Tests unitaires, intégration, E2E
25
+ └── etape_12_benchmark/ # Benchmark LLM-as-Judge
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Prérequis
31
+
32
+ | Outil | Version minimale | Utilisation |
33
+ |-------|-----------------|-------------|
34
+ | Python | 3.11+ | Langage principal |
35
+ | pip | 23+ | Gestion des dépendances |
36
+ | Docker | 24+ | Étapes 07, 08, 09 |
37
+ | Docker Compose | 2.0+ | Orchestration des services |
38
+ | LM Studio | optionnel | Modèle local (étape 00) |
39
+
40
+ ### Clé API OpenAI (recommandée)
41
+
42
+ Les étapes utilisent par défaut l'API OpenAI. Créez un compte sur [platform.openai.com](https://platform.openai.com), générez une clé API, et gardez-la sous la main.
43
+
44
+ ### Alternative locale avec LM Studio
45
+
46
+ Si vous n'avez pas de clé OpenAI, installez [LM Studio](https://lmstudio.ai/), téléchargez un modèle (ex: `mistral-7b-instruct`), démarrez le serveur local sur `http://localhost:1234`, et configurez `MODE=local` dans votre `.env`.
47
+
48
+ ---
49
+
50
+ ## Installation rapide
51
+
52
+ ```bash
53
+ # Cloner / naviguer dans le projet
54
+ cd tp_ia_chatbot
55
+
56
+ # Chaque étape est autonome — exemple pour l'étape 01 :
57
+ cd etape_01_banane
58
+ cp .env.example .env
59
+ # Éditez .env et ajoutez votre clé OPENAI_API_KEY
60
+ pip install -r requirements.txt
61
+ python chatbot_naif.py
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Progression des étapes
67
+
68
+ ### Étape 00 — Le Moteur
69
+ **Objectif :** Établir la connexion avec le LLM (cloud OpenAI ou local LM Studio).
70
+ **Ce que vous apprenez :** API OpenAI, variables d'environnement, dotenv.
71
+
72
+ ### Étape 01 — Le Chatbot Naïf (La Banane)
73
+ **Objectif :** Créer un chatbot fonctionnel en ~10 lignes. Constater l'amnésie.
74
+ **Ce que vous apprenez :** La nature stateless des LLMs, le rôle de l'historique côté client.
75
+
76
+ ### Étape 02 — Les KPIs
77
+ **Objectif :** Mesurer latence, tokens par seconde, coût estimé.
78
+ **Ce que vous apprenez :** Métriques de performance LLM, analyse comparative.
79
+
80
+ ### Étape 03 — La Mémoire
81
+ **Objectif :** Implémenter une fenêtre glissante et un résumé automatique.
82
+ **Ce que vous apprenez :** Gestion du contexte, trade-off mémoire/coût.
83
+
84
+ ### Étape 04 — La Persistance
85
+ **Objectif :** Sauvegarder les conversations dans SQLite ou JSON.
86
+ **Ce que vous apprenez :** Bases de données légères, gestion de sessions.
87
+
88
+ ### Étape 05 — RAG (Retrieval-Augmented Generation)
89
+ **Objectif :** Ancrer le chatbot dans vos propres documents.
90
+ **Ce que vous apprenez :** ChromaDB, embeddings, chunking, évaluation RAG.
91
+
92
+ ### Étape 06 — LangChain
93
+ **Objectif :** Utiliser le framework LangChain pour orchestrer le chatbot.
94
+ **Ce que vous apprenez :** Chaînes LangChain, mémoire intégrée, LCEL.
95
+
96
+ ### Étape 07 — Docker
97
+ **Objectif :** Exposer le chatbot comme une API REST dockerisée.
98
+ **Ce que vous apprenez :** FastAPI, Docker, endpoints REST.
99
+
100
+ ### Étape 08 — Monitoring
101
+ **Objectif :** Instrumenter l'API avec Prometheus et Grafana.
102
+ **Ce que vous apprenez :** Métriques custom, dashboards, observabilité.
103
+
104
+ ### Étape 09 — Sécurité
105
+ **Objectif :** Sécuriser l'API avec JWT, rate limiting, sanitisation.
106
+ **Ce que vous apprenez :** Authentification, protection contre l'injection de prompt.
107
+
108
+ ### Étape 10 — Tests de Charge (Locust)
109
+ **Objectif :** Tester la tenue en charge du chatbot.
110
+ **Ce que vous apprenez :** Tests de performance, Locust, analyse des résultats.
111
+
112
+ ### Étape 11 — Tests Automatisés
113
+ **Objectif :** Couvrir l'application avec des tests unitaires, d'intégration et E2E.
114
+ **Ce que vous apprenez :** pytest, mocking, TestClient FastAPI, couverture de code.
115
+
116
+ ### Étape 12 — Benchmark LLM-as-Judge
117
+ **Objectif :** Comparer plusieurs modèles sur un jeu d'évaluation standardisé.
118
+ **Ce que vous apprenez :** Évaluation automatique, LLM-as-Judge, analyse comparative.
119
+
120
+ ---
121
+
122
+ ## Conseils pédagogiques
123
+
124
+ 1. **Ne sautez pas les étapes** — chaque étape introduit un concept qui sera réutilisé ensuite.
125
+ 2. **Lisez le README de chaque étape** avant de lancer le code.
126
+ 3. **Expérimentez** — modifiez les paramètres (MAX_HISTORY, modèles, etc.) pour comprendre l'impact.
127
+ 4. **Comparez** — l'étape 12 vous permettra de voir concrètement les différences entre modèles.
128
+
129
+ ---
130
+
131
+ ## Structure des coûts indicatifs (OpenAI gpt-4o-mini)
132
+
133
+ | Étape | Appels estimés | Coût estimé |
134
+ |-------|---------------|-------------|
135
+ | 00 à 03 | ~50 | < 0,01 € |
136
+ | 04 à 06 | ~200 | < 0,05 € |
137
+ | 07 à 09 | ~500 | < 0,10 € |
138
+ | 10 à 12 | ~2000 | < 0,50 € |
139
+
140
+ *Tarifs indicatifs basés sur gpt-4o-mini à $0.15/1M tokens input, $0.60/1M tokens output.*
141
+
142
+ ---
143
+
144
+ ## Licence
145
+
146
+ Projet éducatif — libre d'utilisation et de modification.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/.env.example ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 00 — Variables d'environnement (partagées par toutes les étapes)
2
+ # Copiez ce fichier en .env et remplissez vos valeurs
3
+
4
+ # Clé API OpenAI (obligatoire pour le mode cloud)
5
+ OPENAI_API_KEY=sk-your-key-here
6
+
7
+ # Mode de connexion par défaut : "cloud" (OpenAI) ou "local" (LM Studio)
8
+ MODE=local
9
+
10
+ # --- Config CLOUD ---
11
+ #CLOUD_MODEL=gpt-4o-mini
12
+
13
+ # --- Config LOCAL (LM Studio) ---
14
+ # 2. local — gpt-oss-20b (localhost)
15
+ #LOCAL_BASE_URL=http://localhost:1234/v1
16
+ #LOCAL_MODEL=openai/gpt-oss-20b
17
+
18
+ # 3. local_mistral3 — ministral-3-3b (localhost)
19
+ #LOCAL_BASE_URL=http://localhost:1234/v1
20
+ #LOCAL_MODEL=mistralai/ministral-3-3b
21
+
22
+ # 4. reseau_ministral14b — ministral-3-14b-reasoning (réseau 192.168.1.141) ← actif
23
+ LOCAL_BASE_URL=http://192.168.1.141:1234/v1
24
+ LOCAL_MODEL=mistralai/ministral-3-14b-reasoning
25
+
26
+ # 5. reseau_gpt_oss_20b — gpt-oss-20b (réseau 192.168.1.141)
27
+ #LOCAL_BASE_URL=http://192.168.1.141:1234/v1
28
+ #LOCAL_MODEL=openai/gpt-oss-20b
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/EVALUATION_TP_ETAPE_00.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 00 : Le Moteur
2
+
3
+ > ⏱ Durée estimée : 30 min | Niveau : Débutant
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ Avant d'écrire une seule ligne de logique, il faut que la connexion au LLM fonctionne.
8
+ Cette étape pose la fondation : un LLM cloud ou local expose **la même interface REST**,
9
+ les clés API ne doivent **jamais** être dans le code, et la latence de départ est déjà mesurable.
10
+
11
+ ---
12
+
13
+ ## ✅ Checklist de validation
14
+
15
+ - [ ] J'ai créé mon `.env` à partir de `.env.example` et ajouté ma clé API
16
+ - [ ] J'ai lancé `python demo_connexion.py` et obtenu une réponse du LLM (LM Studio en local)
17
+ - [ ] J'ai lancé `python demo_connexion.py` et obtenu une réponse du LLM (LM Studio sur un serveur local)
18
+ - [ ] J'ai noté la latence affichée (mode local LM Studio)
19
+ - [ ] J'ai vérifié que le fichier `.env` **n'apparaît pas** dans `git status`
20
+
21
+ ---
22
+
23
+ ## 🔍 Questions de compréhension
24
+
25
+ ### Niveau 1 — Observation
26
+
27
+ 1. Quelle latence as-tu obtenue lors de ton premier appel (`demo_connexion.py`) ? En combien de millisecondes ?
28
+ 2. Qu'affiche `python config.py` sur ton poste ? Quel mode est actif (cloud / local) et quel modèle est configuré ?
29
+ 3. Si tu ouvres ton `.env`, quelles variables y sont définies ? Laquelle serait catastrophique à exposer sur GitHub ?
30
+
31
+ ### Niveau 2 — Analyse
32
+
33
+ 1. Pourquoi utilise-t-on `python-dotenv` plutôt que d'écrire `api_key = "sk-..."` directement dans le code ? Que se passerait-il avec un `git push` avec la clé en dur ?
34
+ 2. Cloud et local utilisent la **même interface** (`client.chat.completions.create`). Qu'est-ce que ça implique pour le code des étapes suivantes ?
35
+
36
+ ### Niveau 3 — Décision
37
+
38
+ 1. Tu dois choisir entre cloud (OpenAI) et local (LM Studio) pour un chatbot interne à une banque. Quels critères guident ta décision ? Cite au moins 3 dimensions (coût, conformité, latence, confidentialité...).
39
+
40
+ ---
41
+
42
+ ## 🧪 Mini-expérience guidée
43
+
44
+ Dans `demo_connexion.py`, change `max_tokens` à `5`. Que se passe-t-il ? La réponse est-elle complète ?
45
+
46
+ **Observation :**
47
+
48
+ ```
49
+ _____________________________________________________________
50
+ _____________________________________________________________
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 📋 Lien avec le dossier E8
56
+
57
+ - **Architecture** : Décris en 3-5 phrases comment la gestion des clés API via variables d'environnement s'inscrit dans les bonnes pratiques de sécurité (secrets managers, AWS Secrets Manager, HashiCorp Vault).
58
+ - **Réglementaire** : Si le chatbot utilise l'API OpenAI, les messages transitent par des serveurs américains. Quelles implications pour le RGPD et la souveraineté des données ?
59
+
60
+ ---
61
+
62
+ ## 💡 Pour aller plus loin
63
+
64
+ - Teste LM Studio : compare la latence cloud vs local.
65
+ - Calcule le coût de 10 000 messages de 500 tokens avec `gpt-4o-mini` (page pricing OpenAI).
66
+ - Explore la différence entre `max_tokens` et `temperature`.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/README.md ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 00 — Le Moteur
2
+
3
+ ## Objectif
4
+
5
+ Établir la connexion avec un LLM, que ce soit via l'API cloud d'OpenAI ou via un modèle local avec LM Studio.
6
+
7
+ ## Concepts clés
8
+
9
+ - **API OpenAI** : Interface REST standardisée pour les LLMs
10
+ - **Variables d'environnement** : Ne jamais écrire les clés API en dur dans le code
11
+ - **Mode cloud vs local** : Même interface, backends différents
12
+
13
+ ## Installation
14
+
15
+ ```
16
+ # Créer un environemnet virtuel
17
+ python -m venv venv
18
+ source venv/bin/active
19
+ ```
20
+
21
+ ```
22
+ # Installer LM Studio
23
+ ./LM-Studio-0.4.4-1-x64.AppImage
24
+ # /!\ Attention parfois il faut modifier une option
25
+ [50827:0417/124432.581745:FATAL:sandbox/linux/suid/client/setuid_sandbox_host.cc:166] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that /tmp/.mount_LM-Stu4qQ6d5/chrome-sandbox is owned by root and has mode 4755.
26
+ Trappe pour point d'arrêt et de trace (core dumped)
27
+ fabien@fabien-B760M-DS3H-AX-DDR4:~/Téléchargements$
28
+ fabien@fabien-B760M-DS3H-AX-DDR4:~/Téléchargements$ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
29
+ kernel.apparmor_restrict_unprivileged_userns = 0
30
+ fabien@fabien-B760M-DS3H-AX-DDR4:~/Téléchargements$ ./LM-Studio-0.4.4-1-x64.AppImage
31
+
32
+ # Ou lancer LM Studio sans sandbox
33
+ ./LM-Studio-0.4.4-1-x64.AppImage --no-sandbox
34
+
35
+ # Ou gérer via un "bouble click" sous Ubuntu
36
+ sudo apt install libfuse2
37
+
38
+ # Installer un modèle simple
39
+ mistralai/ministral-3-3b
40
+ ```
41
+
42
+
43
+ ```bash
44
+ cd etape_00_moteur
45
+ pip install -r requirements.txt
46
+ cp .env.example .env
47
+ ```
48
+
49
+ Éditez `.env` et ajoutez votre clé API :
50
+
51
+ ```
52
+ OPENAI_API_KEY=sk-votre-vraie-cle-ici
53
+ MODE=cloud
54
+ ```
55
+
56
+ ## Utilisation
57
+
58
+ ### Tester les connexions
59
+
60
+ ```bash
61
+ python demo_connexion.py
62
+ ```
63
+
64
+ Vous verrez quelles connexions fonctionnent et leur latence.
65
+
66
+ ### Tester la configuration
67
+
68
+ ```bash
69
+ python config.py
70
+ ```
71
+
72
+ ## Changer de mode
73
+
74
+ Dans votre `.env` :
75
+
76
+ ```bash
77
+ # Pour utiliser OpenAI (cloud)
78
+ MODE=cloud
79
+
80
+ # Pour utiliser LM Studio (local)
81
+ MODE=local
82
+ ```
83
+
84
+ ## Configuration LM Studio (optionnel)
85
+
86
+ 1. Téléchargez [LM Studio](https://lmstudio.ai/)
87
+ 2. Téléchargez un modèle (ex: `mistral-7b-instruct-v0.3`)
88
+ 3. Allez dans l'onglet "Local Server"
89
+ 4. Cliquez sur "Start Server" (port 1234 par défaut)
90
+ 5. Notez le nom exact du modèle chargé et mettez-le dans `LOCAL_MODEL`
91
+
92
+ ## Structure des fichiers
93
+
94
+ ```
95
+ etape_00_moteur/
96
+ ├── config.py ← Configuration centralisée (cloud/local)
97
+ ├── demo_connexion.py ← Test des connexions avec timing
98
+ ├── .env.example ← Template pour vos variables d'environnement
99
+ ├── requirements.txt ← Dépendances Python
100
+ └── README.md ← Ce fichier
101
+ ```
102
+
103
+ ## Points importants
104
+
105
+ - Ne commitez JAMAIS votre fichier `.env` (il contient votre clé API)
106
+ - Le fichier `.env.example` sert de documentation — il peut être commité
107
+ - `config.py` est réutilisé par les étapes suivantes via `sys.path`
108
+
109
+ # Info réseau
110
+
111
+ Pour récupérer l'IP local sous Ubuntu: `ip a` ou `hostname -I`.
112
+ Si problème de firewall `sudo ufw allow 1234`
113
+
114
+ Vérifier que LM Studio écoute bien partout:
115
+
116
+ ss -tulnp | grep 1234 (on doit voir 0.0.0.0:1234, si on voit 127.0.0.1:1234 ça veut dire que c'est encore bloqué en local)
117
+
118
+
119
+ Exemple de sortie:
120
+
121
+ $ hostname -I
122
+ 192.168.1.141 172.19.0.1 172.17.0.1 172.20.0.1 172.18.0.1 2001:861:5441:a6d0:8242:xx:yyy:ccc 2001:861:5441:a6d0:16fc:zz:aa:vvv
123
+
124
+ ss -tulnp | grep 1234
125
+ tcp LISTEN 0 511 0.0.0.0:1234 0.0.0.0:* users:(("lm-studio",pid=17384,fd=127))
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/config.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 00 — Le Moteur
3
+ Configuration centralisée pour cloud (OpenAI) et local (LM Studio).
4
+
5
+ Importez ce module depuis n'importe quelle étape pour éviter la duplication :
6
+ import sys, os
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'etape_00_moteur'))
8
+ from config import CONFIG, list_configs, make_client, get_client, get_model
9
+ """
10
+ import os
11
+ from dotenv import load_dotenv
12
+ import openai
13
+
14
+ # Charge toujours depuis ce répertoire, indépendamment du cwd de l'appelant
15
+ load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
16
+
17
+ # Serveurs LM Studio — modifiez ici si les IPs changent
18
+ LM_STUDIO_LOCAL = "http://192.168.1.66:1235/v1" # machine locale (exemple WSL2 sur Windows)
19
+ LM_STUDIO_RESEAU = "http://192.168.1.141:1234/v1" # serveur IA réseau loca
20
+
21
+ CONFIG = {
22
+ "cloud": {
23
+ "base_url": None,
24
+ "api_key": os.environ.get("OPENAI_API_KEY", "sk-changeme"),
25
+ "model": os.environ.get("CLOUD_MODEL", "gpt-4o-mini"),
26
+ "price_input": 0.00015, # $/1K tokens
27
+ "price_output": 0.00060,
28
+ },
29
+ "local_mistral7b": {
30
+ "base_url": os.environ.get("LOCAL_BASE_URL", "http://localhost:1234/v1"),
31
+ "api_key": "lm-studio",
32
+ "model": os.environ.get("LOCAL_MODEL", "mistral-7b-instruct"),
33
+ "price_input": 0.0, # gratuit (local)
34
+ "price_output": 0.0,
35
+ },
36
+ "force_localhost_gpt_oss_20b": {
37
+ "base_url": LM_STUDIO_LOCAL, # Windows host depuis WSL2 (LM Studio port 1235)
38
+ "api_key": "lm-studio",
39
+ "model": "openai/gpt-oss-20b",
40
+ "price_input": 0.0,
41
+ "price_output": 0.0,
42
+ "timeout": 120, # MSI sans GPU dédié = lent, timeout généreux
43
+ },
44
+ "force_localhost_mistral7b": {
45
+ "base_url": LM_STUDIO_LOCAL, # Windows host depuis WSL2 (LM Studio port 1235)
46
+ "api_key": "lm-studio",
47
+ "model": "mistralai/ministral-3-3b",
48
+ "price_input": 0.0, # gratuit (local)
49
+ "price_output": 0.0,
50
+ },
51
+ "reseau_ministral3b": {
52
+ "base_url": LM_STUDIO_RESEAU,
53
+ "api_key": "lm-studio",
54
+ "model": "mistralai/ministral-3-3b",
55
+ "price_input": 0.0, # gratuit (réseau local)
56
+ "price_output": 0.0,
57
+ "timeout": 120, # modèle lourd sur réseau — timeout généreux
58
+ },
59
+ "reseau_ministral14b": {
60
+ "base_url": LM_STUDIO_RESEAU,
61
+ "api_key": "lm-studio",
62
+ "model": "mistralai/ministral-3-14b-reasoning",
63
+ "price_input": 0.0, # gratuit (réseau local)
64
+ "price_output": 0.0,
65
+ "timeout": 120, # modèle lourd sur réseau — timeout généreux
66
+ },
67
+ "reseau_gpt_oss_20b": {
68
+ "base_url": LM_STUDIO_RESEAU,
69
+ "api_key": "lm-studio",
70
+ "model": "openai/gpt-oss-20b",
71
+ "price_input": 0.0, # gratuit (réseau local)
72
+ "price_output": 0.0,
73
+ "timeout": 120, # modèle lourd sur réseau — timeout généreux
74
+ },
75
+ }
76
+
77
+
78
+ def clean_reply(text: str) -> str:
79
+ """Supprime les tokens de contrôle internes qui fuient dans certains modèles locaux.
80
+ Ex: <|channel|>commentary...<|message|>réponse → réponse"""
81
+ import re
82
+ # Retire tout bloc <|...|> et son contenu jusqu'au dernier <|...|>
83
+ cleaned = re.sub(r"<\|[^|]*\|>[^<]*", "", text)
84
+ # Si le nettoyage a retiré tout le texte, retourner l'original
85
+ return cleaned.strip() or text.strip()
86
+
87
+
88
+ def list_configs() -> list[str]:
89
+ """Retourne la liste des noms de configs disponibles."""
90
+ return list(CONFIG.keys())
91
+
92
+
93
+ def choose_mode() -> str:
94
+ """Sélection interactive du mode. Le MODE du .env est proposé par défaut."""
95
+ configs = list_configs()
96
+ default = os.environ.get("MODE", configs[0])
97
+ if default not in configs:
98
+ default = configs[0]
99
+
100
+ print("Configs disponibles :")
101
+ for i, name in enumerate(configs, 1):
102
+ marker = " ← défaut" if name == default else ""
103
+ print(f" {i}. {name} — modèle : {CONFIG[name]['model']}{marker}")
104
+ choice = input(f"Choisissez (numéro ou nom, Entrée = {default}) : ").strip()
105
+
106
+ if not choice:
107
+ return default
108
+ if choice.isdigit() and 1 <= int(choice) <= len(configs):
109
+ return configs[int(choice) - 1]
110
+ if choice in configs:
111
+ return choice
112
+ print(f" Choix invalide, utilisation de '{default}'")
113
+ return default
114
+
115
+
116
+ def make_client(mode: str) -> openai.OpenAI:
117
+ """Crée un client OpenAI pour la config `mode`."""
118
+ if mode not in CONFIG:
119
+ raise ValueError(f"Mode inconnu : '{mode}'. Disponibles : {list_configs()}")
120
+ cfg = CONFIG[mode]
121
+ timeout = cfg.get("timeout", 30)
122
+ if cfg["base_url"]:
123
+ return openai.OpenAI(base_url=cfg["base_url"], api_key=cfg["api_key"], timeout=timeout)
124
+ return openai.OpenAI(api_key=cfg["api_key"], timeout=timeout)
125
+
126
+
127
+ def get_client(mode: str = None) -> openai.OpenAI:
128
+ """Retourne un client OpenAI (mode actif par défaut via MODE dans .env)."""
129
+ return make_client(mode or os.environ.get("MODE", "cloud"))
130
+
131
+
132
+ def get_model(mode: str = None) -> str:
133
+ """Retourne le modèle actif (mode actif par défaut via MODE dans .env)."""
134
+ m = mode or os.environ.get("MODE", "cloud")
135
+ if m not in CONFIG:
136
+ m = "cloud"
137
+ return CONFIG[m]["model"]
138
+
139
+
140
+ # Accès direct au mode/config actifs (compat étape 00)
141
+ MODE = os.environ.get("MODE", "cloud")
142
+ if MODE not in CONFIG:
143
+ print(f"[ATTENTION] MODE='{MODE}' inconnu, utilisation de 'cloud' par défaut.")
144
+ MODE = "cloud"
145
+ ACTIVE = CONFIG[MODE]
146
+
147
+
148
+ if __name__ == "__main__":
149
+ print(f"Mode actif : {MODE}")
150
+ print(f"Modèle : {get_model()}")
151
+ if ACTIVE["base_url"]:
152
+ print(f"Base URL : {ACTIVE['base_url']}")
153
+ else:
154
+ print(f"Base URL : (OpenAI par défaut)")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/demo_connexion.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 00 — Démo de connexion
3
+ Teste les connexions cloud (OpenAI) et local (LM Studio) et affiche les résultats.
4
+ """
5
+ import os
6
+ import time
7
+ from dotenv import load_dotenv
8
+ import openai
9
+
10
+ load_dotenv()
11
+
12
+ TEST_PROMPT = "Réponds juste 'OK' en un mot."
13
+
14
+ def test_connection(name: str, base_url, api_key: str, model: str) -> dict:
15
+ """Teste une connexion LLM et retourne les résultats."""
16
+ result = {
17
+ "name": name,
18
+ "model": model,
19
+ "success": False,
20
+ "reply": None,
21
+ "latency": None,
22
+ "error": None,
23
+ }
24
+
25
+ try:
26
+ if base_url:
27
+ client = openai.OpenAI(base_url=base_url, api_key=api_key)
28
+ else:
29
+ client = openai.OpenAI(api_key=api_key)
30
+
31
+ start = time.time()
32
+ response = client.chat.completions.create(
33
+ model=model,
34
+ messages=[{"role": "user", "content": TEST_PROMPT}],
35
+ max_tokens=10,
36
+ timeout=15,
37
+ )
38
+ result["latency"] = round(time.time() - start, 3)
39
+ result["reply"] = response.choices[0].message.content.strip()
40
+ result["success"] = True
41
+
42
+ except openai.AuthenticationError:
43
+ result["error"] = "Clé API invalide ou manquante"
44
+ except openai.APIConnectionError:
45
+ result["error"] = "Impossible de se connecter au serveur"
46
+ except openai.APITimeoutError:
47
+ result["error"] = "Timeout — le serveur ne répond pas"
48
+ except Exception as e:
49
+ result["error"] = str(e)
50
+
51
+ return result
52
+
53
+
54
+ def print_result(res: dict):
55
+ status = "OK" if res["success"] else "ECHEC"
56
+ print(f"\n [{status}] {res['name']} — modèle: {res['model']}")
57
+ if res["success"]:
58
+ print(f" Réponse : {res['reply']}")
59
+ print(f" Latence : {res['latency']}s")
60
+ else:
61
+ print(f" Erreur : {res['error']}")
62
+
63
+
64
+ def main():
65
+ print("=" * 55)
66
+ print(" Étape 00 — Test de Connexion au Moteur LLM")
67
+ print("=" * 55)
68
+
69
+ results = []
70
+
71
+ # Test 1 : OpenAI Cloud
72
+ print("\n[1/2] Test connexion CLOUD (OpenAI)...")
73
+ api_key = os.environ.get("OPENAI_API_KEY", "sk-changeme")
74
+ r1 = test_connection(
75
+ name="Cloud OpenAI",
76
+ base_url=None,
77
+ api_key=api_key,
78
+ model="gpt-4o-mini",
79
+ )
80
+ print_result(r1)
81
+ results.append(r1)
82
+
83
+ # Test 2 : LM Studio Local
84
+ print("\n[2/2] Test connexion LOCAL (LM Studio)...")
85
+
86
+ # On récupère l'URL du moteur ou on utilise l'IP Windows par défaut
87
+ local_url = os.environ.get("LOCAL_BASE_URL", "http://192.168.1.66:1235/v1")
88
+ local_model = os.environ.get("LOCAL_MODEL", "openai/gpt-oss-20b")
89
+
90
+ r2 = test_connection(
91
+ name="Local LM Studio",
92
+ base_url=local_url,
93
+ api_key="lm-studio",
94
+ model=local_model,
95
+ )
96
+ print_result(r2)
97
+ results.append(r2)
98
+
99
+ # Résumé
100
+ print("\n" + "=" * 55)
101
+ print(" RÉSUMÉ")
102
+ print("=" * 55)
103
+ working = [r for r in results if r["success"]]
104
+ if not working:
105
+ print("\n ATTENTION : Aucune connexion ne fonctionne !")
106
+ print(" → Vérifiez votre OPENAI_API_KEY dans .env")
107
+ print(" → Ou démarrez LM Studio avec le serveur local")
108
+ else:
109
+ print(f"\n {len(working)}/{len(results)} connexion(s) opérationnelle(s)")
110
+ print(f"\n Recommandation MODE:")
111
+ if r1["success"]:
112
+ print(" MODE=cloud ← OpenAI fonctionne")
113
+ if r2["success"]:
114
+ print(" MODE=local ← LM Studio fonctionne")
115
+ print()
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai>=1.0.0
2
+ python-dotenv>=1.0.0
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 01 — La config est mutualisée dans etape_00_moteur/.env
2
+ # Copiez et remplissez etape_00_moteur/.env.example si ce n'est pas déjà fait.
3
+ #
4
+ # Variables lues depuis etape_00_moteur/.env :
5
+ # OPENAI_API_KEY — clé API OpenAI (mode cloud)
6
+ # MODE — "cloud" ou "local" (défaut si non saisi interactivement)
7
+ # CLOUD_MODEL — modèle OpenAI (défaut : gpt-4o-mini)
8
+ # LOCAL_BASE_URL — URL LM Studio (défaut : http://localhost:1234/v1)
9
+ # LOCAL_MODEL — modèle LM Studio chargé
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/01_chatbot_naif_no_message_stack.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 01 — Le Chatbot SANS historique (version naïve)
3
+ Démontre le problème d'amnésie : chaque appel est indépendant.
4
+ Le LLM ne reçoit jamais les échanges précédents → il ne peut pas se souvenir.
5
+
6
+ OBSERVATION CLÉS :
7
+ - Le message utilisateur N'EST PAS ajouté à msgs avant l'appel
8
+ - Chaque appel reçoit uniquement le system prompt → amnésie totale
9
+ - Comparez avec 02_chatbot_naif.py qui accumule l'historique
10
+ """
11
+ import os, sys
12
+ import openai
13
+
14
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
15
+ from config import CONFIG, choose_mode, make_client
16
+
17
+ mode = choose_mode()
18
+ client = make_client(mode)
19
+ MODEL = CONFIG[mode]["model"]
20
+
21
+ # msgs contient SEULEMENT le system prompt — jamais mis à jour
22
+ msgs = [{"role": "system", "content": "Tu es un assistant utile et concis. Réponds en français."}]
23
+
24
+ print("=== Chatbot SANS Historique — Étape 01 (version 1/2) ===")
25
+ print("Ce chatbot ne se souvient de RIEN, même dans la même session !")
26
+ print("Tapez 'quit' pour quitter.\n")
27
+ print("ASTUCE : Dites votre prénom, puis demandez-le → il ne saura pas répondre !\n")
28
+
29
+ try:
30
+ while True:
31
+ q = input("Vous: ").strip()
32
+ if q.lower() in ("quit", "exit", "q"):
33
+ print("\nAu revoir !")
34
+ break
35
+ if not q:
36
+ continue
37
+
38
+ # On envoie la question en one-shot, sans l'ajouter à msgs
39
+ messages_one_shot = msgs + [{"role": "user", "content": q}]
40
+
41
+ try:
42
+ response = client.chat.completions.create(model=MODEL, messages=messages_one_shot)
43
+ reply = response.choices[0].message.content
44
+ print(f"IA: {reply}\n")
45
+ # NOTE : on ne fait RIEN avec reply → prochain appel = même contexte vide
46
+
47
+ except openai.AuthenticationError:
48
+ print("ERREUR: Clé API invalide. Vérifiez OPENAI_API_KEY dans etape_00_moteur/.env\n")
49
+ except openai.APIConnectionError:
50
+ print("ERREUR: Impossible de contacter l'API. Vérifiez votre connexion internet.\n")
51
+ except openai.RateLimitError:
52
+ print("ERREUR: Limite de débit atteinte. Attendez quelques secondes.\n")
53
+ except Exception as e:
54
+ print(f"ERREUR inattendue: {e}\n")
55
+
56
+ except KeyboardInterrupt:
57
+ print("\n\nAu revoir !")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/02_chatbot_naif.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 01 — Le Chatbot Naïf
3
+ Un chatbot fonctionnel en ~10 lignes. Constat : l'IA est amnésique.
4
+
5
+ OBSERVATION CLÉS :
6
+ - Lors de cette session, le chatbot "se souvient" de tout (l'historique est en mémoire)
7
+ - Si vous redémarrez le script, toute la mémoire disparaît
8
+ - Le LLM lui-même n'a aucune mémoire — c'est NOUS qui gérons l'historique
9
+ """
10
+ import os, sys
11
+ import openai
12
+
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
14
+ from config import CONFIG, choose_mode, make_client
15
+
16
+ mode = choose_mode()
17
+ client = make_client(mode)
18
+ MODEL = CONFIG[mode]["model"]
19
+
20
+ msgs = [{"role": "system", "content": "Tu es un assistant utile et concis. Réponds en français."}]
21
+
22
+ print("=== Chatbot Naïf — Étape 01 ===")
23
+ print("Tapez 'quit' pour quitter.\n")
24
+ print("ASTUCE : Dites votre prénom, puis demandez-le à nouveau après redémarrage !\n")
25
+
26
+ try:
27
+ while True:
28
+ q = input("Vous: ").strip()
29
+ if q.lower() in ("quit", "exit", "q"):
30
+ print("\nAu revoir ! (Relancez le script : je ne me souviendrai de rien...)")
31
+ break
32
+ if not q:
33
+ continue
34
+
35
+ msgs.append({"role": "user", "content": q})
36
+
37
+ try:
38
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
39
+ reply = response.choices[0].message.content
40
+ print(f"IA: {reply}\n")
41
+ msgs.append({"role": "assistant", "content": reply})
42
+
43
+ except openai.AuthenticationError:
44
+ print("ERREUR: Clé API invalide. Vérifiez OPENAI_API_KEY dans etape_00_moteur/.env\n")
45
+ msgs.pop()
46
+ except openai.APIConnectionError:
47
+ print("ERREUR: Impossible de contacter l'API. Vérifiez votre connexion internet.\n")
48
+ msgs.pop()
49
+ except openai.RateLimitError:
50
+ print("ERREUR: Limite de débit atteinte. Attendez quelques secondes.\n")
51
+ msgs.pop()
52
+ except Exception as e:
53
+ print(f"ERREUR inattendue: {e}\n")
54
+ msgs.pop()
55
+
56
+ except KeyboardInterrupt:
57
+ print("\n\nAu revoir ! (Relancez le script : je ne me souviendrai de rien...)")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/EVALUATION_TP_ETAPE_01.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 01 : Le Chatbot Naïf (La Banane)
2
+
3
+ > ⏱ Durée estimée : 30 min | Niveau : Débutant
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ Un LLM est **stateless** : il ne se souvient de rien entre deux appels. La "mémoire" du chatbot
8
+ est une illusion que tu crées en renvoyant tout l'historique à chaque requête.
9
+ Ça marche — mais ça ne survit pas au redémarrage, et ça grossit sans limite.
10
+
11
+ ---
12
+
13
+ ## ✅ Checklist de validation
14
+
15
+ - [ ] J'ai lancé `02_chatbot_naif.py` et eu une vraie conversation
16
+ - [ ] J'ai dit mon prénom, puis demandé "Quel est mon prénom ?" → réponse correcte
17
+ - [ ] J'ai quitté (`quit`) et relancé → vérifié que le bot ne se souvient plus
18
+ - [ ] J'ai observé comment la liste `msgs` grandit à chaque tour dans le code
19
+ - [ ] J'ai comparé `01_chatbot_naif_no_message_stack.py` et `02_chatbot_naif.py`
20
+
21
+ ---
22
+
23
+ ## 🔍 Questions de compréhension
24
+
25
+ ### Niveau 1 — Observation
26
+ 1. Après 5 échanges, combien d'éléments contient `msgs` ? Décris le contenu du 3e élément.
27
+
28
+ > _________________________________________________________
29
+
30
+ 2. Que se passe-t-il exactement quand tu tapes `quit` et relances le script ? Quelle donnée est perdue ?
31
+
32
+ > _________________________________________________________
33
+
34
+ 3. Dans `01_chatbot_naif_no_message_stack.py` (sans historique), que répond le bot si tu lui donnes ton prénom puis demandes "Comment je m'appelle ?" à l'échange suivant ?
35
+
36
+ > _________________________________________________________
37
+
38
+ ### Niveau 2 — Analyse
39
+ 1. Pourquoi dit-on que la mémoire est une "illusion côté client" ? Qui fait vraiment le travail de mémorisation ?
40
+
41
+ > _________________________________________________________
42
+
43
+ 2. Si une conversation dure 200 échanges, combien de messages sont envoyés à l'API au 200e échange ? Quel impact sur le coût et la latence ?
44
+
45
+ > _________________________________________________________
46
+
47
+ ### Niveau 3 — Décision
48
+ 1. Tu déploies ce chatbot naïf pour 500 utilisateurs simultanés. Quels sont les 2 problèmes critiques du premier jour de prod ? Comment les hiérarchiser ?
49
+
50
+ > _________________________________________________________
51
+ > _________________________________________________________
52
+
53
+ ---
54
+
55
+ ## 🧪 Mini-expérience guidée
56
+
57
+ Modifie le `system prompt` pour donner au bot une personnalité différente
58
+ (ex : "Tu réponds uniquement en 10 mots maximum"). Lance une conversation et observe.
59
+
60
+ **Comportement observé :**
61
+ ```
62
+ _____________________________________________________________
63
+ _____________________________________________________________
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 📋 Lien avec le dossier E8
69
+
70
+ - **Architecture** : Pourquoi l'architecture "stateless + historique côté client" est-elle à la fois une contrainte et un avantage pour un déploiement multi-utilisateurs ?
71
+ - **Risques** : Si l'historique n'est pas plafonné, quel risque technique et financier émerge après quelques jours d'utilisation intensive ? Proposez une métrique à surveiller.
72
+
73
+ ---
74
+
75
+ ## 💡 Pour aller plus loin
76
+
77
+ - Envoie la même question avec 0 et avec 10 échanges d'historique : compare la qualité des réponses.
78
+ - Mesure la taille de `msgs` après 20 échanges et estime son coût en tokens (~4 chars = 1 token).
79
+ - Lis la doc OpenAI sur le format `messages` : quels rôles existent au total ?
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/README.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 01 — Le Chatbot Naïf (La Banane)
2
+
3
+ ## Objectif
4
+
5
+ Créer un chatbot fonctionnel en une dizaine de lignes de Python.
6
+ Constater et comprendre le problème fondamental : **l'amnésie du LLM**.
7
+
8
+ ## Le concept de la Banane
9
+
10
+ Le "problème de la banane" illustre la nature stateless des LLMs :
11
+ - Pendant une session : le chatbot semble avoir de la mémoire (grâce à `msgs`)
12
+ - Après redémarrage : toute la "mémoire" disparaît
13
+
14
+ Ce n'est pas un bug — c'est la nature fondamentale des LLMs.
15
+ La mémoire est une **illusion** que nous créons côté client.
16
+
17
+ ## Ce que vous apprenez
18
+
19
+ - L'API `client.chat.completions.create()` et ses paramètres
20
+ - Le format `messages` : liste de dicts `{"role": ..., "content": ...}`
21
+ - Les rôles : `system`, `user`, `assistant`
22
+ - La nature **stateless** du LLM
23
+ - Pourquoi l'historique doit être géré côté client
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ cd etape_01_banane
29
+ pip install -r requirements.txt
30
+ cp .env.example .env
31
+ # Éditez .env avec votre clé API
32
+ ```
33
+
34
+ ## Utilisation
35
+
36
+ ### Version 1 — Sans historique (amnésie totale, même en session)
37
+ ```bash
38
+ python 01_chatbot_naif_no_message_stack.py
39
+ ```
40
+
41
+ ### Version 2 — Avec historique de session (amnésie au redémarrage)
42
+ ```bash
43
+ python 02_chatbot_naif.py
44
+ ```
45
+
46
+ ## Expériences à faire
47
+
48
+ ### Expérience 1 — Amnésie en session (version 1)
49
+ 1. Lancez `01_chatbot_naif_no_message_stack.py`
50
+ 2. Dites : `Je m'appelle [votre prénom]`
51
+ 3. Demandez : `Quel est mon prénom ?` → Il ne sait **déjà** plus ✗
52
+ 4. CONSTAT : chaque appel est indépendant, aucun historique côté client
53
+
54
+ ### Expérience 2 — Mémoire en session (version 2)
55
+ 1. Lancez `02_chatbot_naif.py`
56
+ 2. Dites : `Je m'appelle [votre prénom] et j'adore [un truc]`
57
+ 3. Demandez : `Quel est mon prénom ?` → Il répond correctement ✓
58
+ 4. Quittez avec `quit`
59
+
60
+ ### Expérience 3 — L'amnésie après redémarrage (version 2)
61
+ 1. Relancez `02_chatbot_naif.py`
62
+ 2. Demandez : `Quel est mon prénom ?` → Il ne sait plus ✗
63
+ 3. CONSTAT : la "mémoire" ne persiste pas entre les sessions
64
+
65
+ ### Expérience 4 — Inspecter le code
66
+ Comparez les deux fichiers. Dans `02_chatbot_naif.py` :
67
+ ```python
68
+ msgs = [{"role": "system", "content": "..."}]
69
+ # On ajoute user + assistant à chaque tour
70
+ msgs.append({"role": "user", "content": q})
71
+ msgs.append({"role": "assistant", "content": reply})
72
+ ```
73
+ C'est NOUS qui envoyons tout l'historique à chaque appel.
74
+
75
+ ## Fichiers
76
+
77
+ ```
78
+ etape_01_banane/
79
+ ├── 01_chatbot_naif_no_message_stack.py ← Amnésie totale (sans historique)
80
+ ├── 02_chatbot_naif.py ← Avec historique de session
81
+ ├── .env.example ← Template d'environnement
82
+ ├── requirements.txt ← Dépendances
83
+ └── README.md ← Ce fichier
84
+ ```
85
+
86
+ ## Question à méditer
87
+
88
+ > Si le LLM est stateless et qu'on lui envoie l'historique complet à chaque appel,
89
+ > que se passe-t-il quand la conversation devient très longue ?
90
+
91
+ Réponse dans l'**Étape 03 — La Mémoire** !
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/logs/prompts_demo.txt ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === DÉMONSTRATION DU PROBLÈME D'AMNÉSIE ===
2
+
3
+ Ce fichier montre deux sessions de conversation pour illustrer pourquoi
4
+ on appelle ce problème "le problème de la banane" :
5
+ si vous demandez à un chatbot naïf "qu'est-ce que je t'ai dit au début ?",
6
+ il peut répondre "une banane" ... ou n'importe quoi d'autre lors du prochain lancement.
7
+
8
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9
+
10
+ --- Session 1 (normale — historique en mémoire pendant l'exécution) ---
11
+
12
+ Vous: Je m'appelle Jean et j'adore programmer en Python.
13
+ IA: Bonjour Jean ! C'est super que vous aimiez Python, c'est un excellent langage
14
+ de programmation. Que puis-je faire pour vous aujourd'hui ?
15
+
16
+ Vous: Quel est mon prénom ?
17
+ IA: Vous m'avez dit que vous vous appelez Jean !
18
+
19
+ Vous: Quel est mon langage de programmation préféré ?
20
+ IA: Vous m'avez dit que vous adorez Python, Jean !
21
+
22
+ Vous: Donne-moi un projet sympa à faire en Python ce week-end.
23
+ IA: Voici quelques idées de projets Python pour le week-end, Jean :
24
+ 1. Un scraper web pour collecter des données qui vous intéressent
25
+ 2. Un bot Discord ou Telegram
26
+ 3. Un jeu de texte en console (pendu, quiz, etc.)
27
+ 4. Un analyseur de vos dépenses personnelles en CSV
28
+ 5. Un générateur de mots de passe sécurisé
29
+
30
+ Vous: Merci, je vais faire le bot Telegram !
31
+ IA: Excellent choix Jean ! Les bots Telegram sont très amusants à créer.
32
+ Vous aurez besoin de la bibliothèque python-telegram-bot...
33
+
34
+ CONSTAT SESSION 1 : Tout fonctionne, le chatbot "se souvient" de votre prénom
35
+ et de vos préférences — MAIS seulement parce que l'historique est en mémoire RAM.
36
+
37
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
38
+
39
+ --- Session 2 (après redémarrage du script — PROBLÈME D'AMNÉSIE) ---
40
+
41
+ [Le script a été arrêté et relancé. L'historique en mémoire est PERDU.]
42
+
43
+ Vous: Quel est mon prénom ?
44
+ IA: Je suis désolé, je ne connais pas votre prénom. Vous ne me l'avez pas encore dit !
45
+ Comment puis-je vous appeler ?
46
+
47
+ Vous: Quel est mon langage de programmation préféré ?
48
+ IA: Je n'ai aucune information sur vos préférences en matière de programmation.
49
+ Vous ne m'avez pas encore dit quel langage vous aimez. Lequel est-ce ?
50
+
51
+ Vous: Pourtant je te l'ai dit il y a 5 minutes !
52
+ IA: Je comprends votre frustration, mais je dois vous informer que je n'ai pas
53
+ accès aux conversations précédentes. Chaque nouvelle session repart de zéro.
54
+ Je n'ai aucune mémoire persistante entre les conversations.
55
+
56
+ CONSTAT SESSION 2 : Le LLM est STATELESS. Il ne se souvient de rien entre
57
+ les sessions. La "mémoire" de la session 1 était une ILLUSION gérée par NOUS
58
+ (la liste `msgs` en Python). Dès qu'on redémarre, tout disparaît.
59
+
60
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61
+
62
+ --- Session 3 (illustration de la banane) ---
63
+
64
+ Vous: Au début de notre conversation, je t'ai dit que j'aimais les bananes.
65
+ IA: Je suis désolé, je n'ai aucune trace d'une conversation précédente entre nous.
66
+ Cette session vient de commencer ! Aimez-vous vraiment les bananes ?
67
+
68
+ C'est le "problème de la banane" : vous pouvez dire n'importe quoi
69
+ en début de session, mais si vous redémarrez, c'est comme si ça n'avait
70
+ jamais existé.
71
+
72
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73
+
74
+ SOLUTIONS (abordées dans les étapes suivantes) :
75
+
76
+ 1. Étape 03 — Fenêtre glissante : garder les N derniers messages en RAM
77
+ 2. Étape 04 — Persistance SQLite : sauvegarder l'historique sur disque
78
+ 3. Étape 05 — RAG : accéder à une base de connaissances externe
79
+
80
+ LA LEÇON FONDAMENTALE :
81
+ Le LLM est un ORACLE stateless. Lui envoyer l'historique à chaque appel,
82
+ c'est comme lui donner la mémoire. C'est NOTRE responsabilité de la gérer.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai>=1.0.0
2
+ python-dotenv>=1.0.0
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 02 — La config est mutualisée dans etape_00_moteur/.env
2
+ # Copiez et remplissez etape_00_moteur/.env.example si ce n'est pas déjà fait.
3
+ #
4
+ # Variables lues depuis etape_00_moteur/.env :
5
+ # OPENAI_API_KEY — clé API OpenAI (mode cloud)
6
+ # MODE — "cloud" ou "local" (défaut si non saisi interactivement)
7
+ # CLOUD_MODEL — modèle OpenAI (défaut : gpt-4o-mini)
8
+ # LOCAL_BASE_URL — URL LM Studio (défaut : http://localhost:1234/v1)
9
+ # LOCAL_MODEL — modèle LM Studio chargé
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/01_mesurer_kpis.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 02 — Mesurer pour Comprendre
3
+ Intégration des KPIs de performance : latence, TPS, coût.
4
+ """
5
+ import os, sys, time
6
+ import openai
7
+
8
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
9
+ from config import CONFIG, choose_mode, make_client
10
+
11
+ mode = choose_mode()
12
+ client = make_client(mode)
13
+ MODEL = CONFIG[mode]["model"]
14
+ price_input = CONFIG[mode]["price_input"]
15
+ price_output = CONFIG[mode]["price_output"]
16
+
17
+ msgs = [{"role": "system", "content": "Tu es un assistant utile et concis. Réponds en français."}]
18
+
19
+ session_stats = {
20
+ "total_requests": 0,
21
+ "total_latency": 0.0,
22
+ "total_prompt_tokens": 0,
23
+ "total_completion_tokens": 0,
24
+ "total_cost": 0.0,
25
+ "latencies": [],
26
+ }
27
+
28
+
29
+ def calc_cost(prompt_tokens: int, completion_tokens: int) -> float:
30
+ return (prompt_tokens * price_input + completion_tokens * price_output) / 1000
31
+
32
+
33
+ def print_kpis(latency: float, prompt_tokens: int, completion_tokens: int, cost: float):
34
+ tps = completion_tokens / latency if latency > 0 else 0
35
+ cost_str = f"${cost:.6f}" if price_output > 0 else "gratuit (local)"
36
+ total_str = f"${session_stats['total_cost']:.6f}" if price_output > 0 else "gratuit (local)"
37
+ print(f"\n ┌─ KPIs ─────────────────────────────────────────┐")
38
+ print(f" │ Latence : {latency:.3f}s", end="")
39
+ status = "✓" if latency < 2 else ("⚠" if latency < 5 else "✗")
40
+ print(f" {status} (cible < 2s)")
41
+ print(f" │ TPS : {tps:.1f} tokens/sec", end="")
42
+ status = "✓" if tps > 30 else "⚠"
43
+ print(f" {status}")
44
+ print(f" │ Tokens : {prompt_tokens} in → {completion_tokens} out")
45
+ print(f" │ Coût : {cost_str}")
46
+ print(f" │ Coût total : {total_str}")
47
+ print(f" └────────────────────────────────────────────────┘\n")
48
+
49
+
50
+ def print_session_summary():
51
+ n = session_stats["total_requests"]
52
+ if n == 0:
53
+ return
54
+ avg_latency = session_stats["total_latency"] / n
55
+ latencies = sorted(session_stats["latencies"])
56
+ p50_idx = int(0.50 * len(latencies))
57
+ p50 = latencies[min(p50_idx, len(latencies) - 1)]
58
+ p95_idx = int(0.95 * len(latencies))
59
+ p95 = latencies[min(p95_idx, len(latencies) - 1)]
60
+ cost_str = f"${session_stats['total_cost']:.6f}" if price_output > 0 else "gratuit (local)"
61
+ print(f"\n ╔═ RÉSUMÉ DE SESSION ({'='*30})╗")
62
+ print(f" ║ Requêtes : {n}")
63
+ print(f" ║ Latence moyenne : {avg_latency:.3f}s")
64
+ print(f" ║ Latence P50 : {p50:.3f}s")
65
+ print(f" ║ Latence P95 : {p95:.3f}s")
66
+ print(f" ║ Tokens totaux : {session_stats['total_prompt_tokens']} in / {session_stats['total_completion_tokens']} out")
67
+ print(f" ║ Coût total : {cost_str}")
68
+ print(f" ╚{'═'*52}╝\n")
69
+
70
+
71
+ print(f"=== Chatbot avec KPIs — Étape 02 ===")
72
+ print(f"Modèle : {MODEL} | Mode : {mode}")
73
+ print("Tapez 'stats' pour le résumé, 'quit' pour quitter.\n")
74
+
75
+ try:
76
+ while True:
77
+ q = input("Vous: ").strip()
78
+ if q.lower() in ("quit", "exit", "q"):
79
+ break
80
+ if q.lower() == "stats":
81
+ print_session_summary()
82
+ continue
83
+ if not q:
84
+ continue
85
+
86
+ msgs.append({"role": "user", "content": q})
87
+
88
+ start = time.time()
89
+ try:
90
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
91
+ except openai.AuthenticationError:
92
+ print(" ✗ Erreur : clé API invalide. Vérifiez OPENAI_API_KEY dans etape_00_moteur/.env\n")
93
+ msgs.pop()
94
+ continue
95
+ except openai.APIConnectionError:
96
+ print(" ✗ Erreur : impossible de se connecter. Vérifiez que LM Studio est démarré.\n")
97
+ msgs.pop()
98
+ continue
99
+ except Exception as e:
100
+ print(f" ✗ Erreur inattendue : {e}\n")
101
+ msgs.pop()
102
+ continue
103
+
104
+ latency = time.time() - start
105
+ reply = response.choices[0].message.content
106
+ prompt_tokens = response.usage.prompt_tokens
107
+ completion_tokens = response.usage.completion_tokens
108
+ cost = calc_cost(prompt_tokens, completion_tokens)
109
+
110
+ msgs.append({"role": "assistant", "content": reply})
111
+
112
+ session_stats["total_requests"] += 1
113
+ session_stats["total_latency"] += latency
114
+ session_stats["total_prompt_tokens"] += prompt_tokens
115
+ session_stats["total_completion_tokens"] += completion_tokens
116
+ session_stats["total_cost"] += cost
117
+ session_stats["latencies"].append(latency)
118
+
119
+ print(f"IA: {reply}")
120
+ print_kpis(latency, prompt_tokens, completion_tokens, cost)
121
+
122
+ if session_stats["total_requests"] % 5 == 0:
123
+ print_session_summary()
124
+
125
+ except KeyboardInterrupt:
126
+ print("\n\nRésumé final :")
127
+ print_session_summary()
128
+ print("Au revoir !")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/02_kpis_comparatif.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 02 — Comparaison des KPIs
3
+ Lance les mêmes prompts sur les configs sélectionnées et compare les résultats.
4
+ """
5
+ import os, sys, time, json
6
+
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
8
+ from config import CONFIG, list_configs, make_client, choose_mode
9
+
10
+ try:
11
+ from tabulate import tabulate
12
+ HAS_TABULATE = True
13
+ except ImportError:
14
+ HAS_TABULATE = False
15
+
16
+ PROMPTS = [
17
+ "Explique le concept de mémoire dans les chatbots en 2 phrases.",
18
+ "Quelle est la différence entre GPT-4o et GPT-4o-mini ?",
19
+ "Donne-moi 3 avantages du RAG par rapport au fine-tuning.",
20
+ "Qu'est-ce que le context window d'un LLM ?",
21
+ "Explique TPS (Tokens Per Second) et pourquoi c'est important.",
22
+ ]
23
+
24
+
25
+ def choose_configs() -> list[dict]:
26
+ """Sélection interactive des configs à comparer."""
27
+ configs = list_configs()
28
+ print("Configs disponibles :")
29
+ for i, name in enumerate(configs, 1):
30
+ cfg = CONFIG[name]
31
+ print(f" {i}. {name} — modèle : {cfg['model']}")
32
+ print("Sélectionnez les configs à comparer (ex: '1 2', '1,2', 'cloud local').")
33
+ print("Appuyez sur Entrée pour toutes les sélectionner.")
34
+ choice = input("Votre choix : ").strip()
35
+
36
+ if not choice:
37
+ selected_names = configs
38
+ else:
39
+ parts = choice.replace(",", " ").split()
40
+ selected_names = []
41
+ for p in parts:
42
+ if p.isdigit() and 1 <= int(p) <= len(configs):
43
+ selected_names.append(configs[int(p) - 1])
44
+ elif p in configs:
45
+ selected_names.append(p)
46
+ if not selected_names:
47
+ print(" Sélection invalide, toutes les configs seront utilisées.")
48
+ selected_names = configs
49
+
50
+ return [
51
+ {
52
+ "name": name,
53
+ "label": f"{name} ({CONFIG[name]['model']})",
54
+ "model": CONFIG[name]["model"],
55
+ "client": make_client(name),
56
+ "price_input": CONFIG[name]["price_input"],
57
+ "price_output": CONFIG[name]["price_output"],
58
+ }
59
+ for name in selected_names
60
+ ]
61
+
62
+
63
+ def run_benchmark(cfg: dict, prompts: list) -> list:
64
+ results = []
65
+ for i, prompt in enumerate(prompts):
66
+ print(f" [{i+1}/{len(prompts)}] {prompt[:50]}...")
67
+ start = time.time()
68
+ try:
69
+ response = cfg["client"].chat.completions.create(
70
+ model=cfg["model"],
71
+ messages=[
72
+ {"role": "system", "content": "Réponds en français, de façon concise."},
73
+ {"role": "user", "content": prompt},
74
+ ],
75
+ )
76
+ latency = time.time() - start
77
+ pt = response.usage.prompt_tokens
78
+ ct = response.usage.completion_tokens
79
+ cost = (pt * cfg["price_input"] + ct * cfg["price_output"]) / 1000
80
+ tps = ct / latency if latency > 0 else 0
81
+ results.append({
82
+ "prompt": prompt[:40] + "...",
83
+ "latency": round(latency, 3),
84
+ "tps": round(tps, 1),
85
+ "prompt_tokens": pt,
86
+ "completion_tokens": ct,
87
+ "cost_mc": round(cost * 1000, 4), # millicents
88
+ })
89
+ except Exception as e:
90
+ print(f" Erreur: {e}")
91
+ results.append({
92
+ "prompt": prompt[:40] + "...",
93
+ "latency": -1, "tps": -1,
94
+ "prompt_tokens": -1, "completion_tokens": -1, "cost_mc": -1,
95
+ })
96
+ return results
97
+
98
+
99
+ def print_results(cfg: dict, results: list):
100
+ label = cfg["label"]
101
+ is_free = cfg["price_output"] == 0
102
+ print(f"\n=== Résultats : {label} ===")
103
+ headers = ["Prompt", "Latence(s)", "TPS", "Tok. In", "Tok. Out", "Coût(m$)"]
104
+ rows = [
105
+ [
106
+ r["prompt"], r["latency"], r["tps"],
107
+ r["prompt_tokens"], r["completion_tokens"],
108
+ "gratuit" if is_free else r["cost_mc"],
109
+ ]
110
+ for r in results
111
+ ]
112
+ if HAS_TABULATE:
113
+ print(tabulate(rows, headers=headers, tablefmt="rounded_grid"))
114
+ else:
115
+ print(f"{'Latence':>10} {'TPS':>8} {'TokIn':>8} {'TokOut':>8} {'Coût':>10}")
116
+ for r in results:
117
+ cost_str = " gratuit" if is_free else f"{r['cost_mc']:>10.4f}"
118
+ print(f"{r['latency']:>10.3f} {r['tps']:>8.1f} {r['prompt_tokens']:>8} {r['completion_tokens']:>8} {cost_str}")
119
+
120
+ valid = [r for r in results if r["latency"] > 0]
121
+ if valid:
122
+ avg_lat = sum(r["latency"] for r in valid) / len(valid)
123
+ avg_tps = sum(r["tps"] for r in valid) / len(valid)
124
+ cost_str = "gratuit" if is_free else f"{sum(r['cost_mc'] for r in valid):.4f} m$"
125
+ print(f"\nMoyennes → Latence: {avg_lat:.3f}s | TPS: {avg_tps:.1f} | Coût total: {cost_str}")
126
+
127
+
128
+ if __name__ == "__main__":
129
+ print("=== Benchmark Comparatif — Étape 02 ===\n")
130
+
131
+ selected = choose_configs()
132
+ all_results = {}
133
+
134
+ for cfg in selected:
135
+ print(f"\n--- Benchmark : {cfg['label']} ---")
136
+ results = run_benchmark(cfg, PROMPTS)
137
+ print_results(cfg, results)
138
+ all_results[cfg["name"]] = {"model": cfg["model"], "results": results}
139
+
140
+ with open("benchmark_results.json", "w") as f:
141
+ json.dump(all_results, f, indent=2)
142
+ print("\nRésultats sauvegardés dans benchmark_results.json")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/EVALUATION_TP_ETAPE_02.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 02 : Mesurer pour Comprendre (KPIs)
2
+
3
+ > ⏱ Durée estimée : 30 min | Niveau : Débutant
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ On ne pilote pas ce qu'on ne mesure pas. Cette étape introduit les 4 KPIs fondamentaux
8
+ d'un LLM en production : latence, TPS, coût par requête, P95.
9
+ L'enjeu : prendre des décisions techniques basées sur des chiffres, pas des intuitions.
10
+
11
+ ---
12
+
13
+ ## ✅ Checklist de validation
14
+
15
+ - [ ] J'ai lancé `01_mesurer_kpis.py` et posé au moins 5 questions
16
+ - [ ] J'ai tapé `stats` et lu le résumé de session
17
+ - [ ] J'ai noté mon P95 personnel
18
+ - [ ] J'ai lancé `02_kpis_comparatif.py` et obtenu des résultats chiffrés
19
+
20
+ ---
21
+
22
+ ## 🔍 Questions de compréhension
23
+
24
+ ### Niveau 1 — Observation
25
+ 1. Quel TPS (tokens/seconde) as-tu observé ? Est-il dans la plage attendue pour le cloud (~80 TPS) ?
26
+
27
+ > _________________________________________________________
28
+
29
+ 2. Quel était ton coût moyen par requête en dollars ? Combien coûterait 10 000 requêtes/jour ?
30
+
31
+ > _________________________________________________________
32
+
33
+ 3. Quelle est la différence entre ta latence **moyenne** et ton **P95** ? Sur combien de requêtes ?
34
+
35
+ > _________________________________________________________
36
+
37
+ ### Niveau 2 — Analyse
38
+ 1. Pourquoi le P95 est-il plus utile que la moyenne ? Donne un exemple où la moyenne serait trompeuse.
39
+
40
+ > _________________________________________________________
41
+
42
+ 2. La formule est `(prompt_tokens × $0.00015 + completion_tokens × $0.00060) / 1000`. Pourquoi les tokens de complétion sont-ils 4× plus chers ? Qu'est-ce que ça implique pour la taille du system prompt ?
43
+
44
+ > _________________________________________________________
45
+
46
+ ### Niveau 3 — Décision
47
+ 1. Définis un SLA réaliste pour ce chatbot en production : seuils pour P50, P95 et coût journalier pour 1 000 utilisateurs actifs. Justifie chaque chiffre.
48
+
49
+ > _________________________________________________________
50
+ > _________________________________________________________
51
+
52
+ ---
53
+
54
+ ## 🧪 Mini-expérience guidée
55
+
56
+ Pose la même question simple ("Quelle est la capitale de la France ?") 5 fois de suite.
57
+ Note les latences individuelles. Sont-elles identiques ? Explique la variabilité.
58
+
59
+ **Mes 5 latences :** `___s ___s ___s ___s ___s`
60
+
61
+ **Explication :**
62
+ ```
63
+ _____________________________________________________________
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 📋 Lien avec le dossier E8
69
+
70
+ - **Métriques** : Quels KPIs intégreriez-vous dans un tableau de bord de supervision ? Pourquoi le P95 est-il l'indicateur de référence pour les SLA ?
71
+ - **Coûts** : Estimez le budget mensuel pour 500 employés faisant chacun 10 questions/jour. Détaillez l'hypothèse de coût par requête.
72
+
73
+ ---
74
+
75
+ ## 💡 Pour aller plus loin
76
+
77
+ - Compare les TPS entre différents modèles (`gpt-4o` vs `gpt-4o-mini`).
78
+ - Mesure le "coût du contexte" : 0 message d'historique vs 8 messages.
79
+ - Cherche la définition de P50, P95, P99 dans le vocabulaire SRE.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/README.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 02 — Mesurer pour Comprendre
2
+
3
+ ## Objectif
4
+ Intégrer des KPIs de performance dans le chatbot dès le début.
5
+
6
+ ## KPIs mesurés
7
+ | KPI | Description | Cible |
8
+ |-----|-------------|-------|
9
+ | Latence | Temps total de réponse | < 2s (P50) |
10
+ | TPS | Tokens Per Second | Cloud ~80, Local ~15 |
11
+ | Coût/requête | Tokens × prix modèle | < $0.001 |
12
+ | Latence P95 | 95e percentile | < 5s |
13
+
14
+ ## Installation
15
+ ```bash
16
+ pip install -r requirements.txt
17
+ cp .env.example .env
18
+ # Éditez .env avec votre clé API
19
+ ```
20
+
21
+ ## Scripts
22
+
23
+ ### `01_mesurer_kpis.py` — Chatbot avec métriques en temps réel
24
+ ```bash
25
+ python 01_mesurer_kpis.py
26
+ ```
27
+ Commandes disponibles :
28
+ - `stats` : affiche le résumé de session
29
+ - `quit` : quitte
30
+
31
+ ### `02_kpis_comparatif.py` — Benchmark comparatif
32
+ ```bash
33
+ python 02_kpis_comparatif.py
34
+ ```
35
+
36
+ ## Exercice
37
+ 1. Lancez `01_mesurer_kpis.py` et posez 10 questions
38
+ 2. Tapez `stats` pour voir vos métriques
39
+ 3. Lancez `02_kpis_comparatif.py` pour voir le benchmark
40
+ 4. Comparez les résultats cloud vs local (si LM Studio installé)
41
+
42
+ ## Formule de coût
43
+ ```
44
+ coût = (prompt_tokens × $0.00015 + completion_tokens × $0.00060) / 1000
45
+ ```
46
+ Pour gpt-4o-mini. Consultez la page de tarification OpenAI pour les autres modèles.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/logs/benchmark_results.json ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "reseau_ministral3b": {
3
+ "model": "mistralai/ministral-3-3b",
4
+ "results": [
5
+ {
6
+ "prompt": "Explique le concept de m\u00e9moire dans les ...",
7
+ "latency": 3.422,
8
+ "tps": 19.6,
9
+ "prompt_tokens": 31,
10
+ "completion_tokens": 67,
11
+ "cost_mc": 0.0
12
+ },
13
+ {
14
+ "prompt": "Quelle est la diff\u00e9rence entre GPT-4o et...",
15
+ "latency": 0.812,
16
+ "tps": 133.0,
17
+ "prompt_tokens": 35,
18
+ "completion_tokens": 108,
19
+ "cost_mc": 0.0
20
+ },
21
+ {
22
+ "prompt": "Donne-moi 3 avantages du RAG par rapport...",
23
+ "latency": 1.01,
24
+ "tps": 138.6,
25
+ "prompt_tokens": 32,
26
+ "completion_tokens": 140,
27
+ "cost_mc": 0.0
28
+ },
29
+ {
30
+ "prompt": "Qu'est-ce que le context window d'un LLM...",
31
+ "latency": 0.863,
32
+ "tps": 135.6,
33
+ "prompt_tokens": 27,
34
+ "completion_tokens": 117,
35
+ "cost_mc": 0.0
36
+ },
37
+ {
38
+ "prompt": "Explique TPS (Tokens Per Second) et pour...",
39
+ "latency": 1.716,
40
+ "tps": 141.6,
41
+ "prompt_tokens": 30,
42
+ "completion_tokens": 243,
43
+ "cost_mc": 0.0
44
+ }
45
+ ]
46
+ },
47
+ "force_localhost_mistral7b": {
48
+ "model": "mistralai/ministral-3-3b",
49
+ "results": [
50
+ {
51
+ "prompt": "Explique le concept de m\u00e9moire dans les ...",
52
+ "latency": 6.518,
53
+ "tps": 11.0,
54
+ "prompt_tokens": 31,
55
+ "completion_tokens": 72,
56
+ "cost_mc": 0.0
57
+ },
58
+ {
59
+ "prompt": "Quelle est la diff\u00e9rence entre GPT-4o et...",
60
+ "latency": 14.959,
61
+ "tps": 11.2,
62
+ "prompt_tokens": 35,
63
+ "completion_tokens": 168,
64
+ "cost_mc": 0.0
65
+ },
66
+ {
67
+ "prompt": "Donne-moi 3 avantages du RAG par rapport...",
68
+ "latency": 13.655,
69
+ "tps": 10.9,
70
+ "prompt_tokens": 32,
71
+ "completion_tokens": 149,
72
+ "cost_mc": 0.0
73
+ },
74
+ {
75
+ "prompt": "Qu'est-ce que le context window d'un LLM...",
76
+ "latency": 12.564,
77
+ "tps": 10.7,
78
+ "prompt_tokens": 27,
79
+ "completion_tokens": 135,
80
+ "cost_mc": 0.0
81
+ },
82
+ {
83
+ "prompt": "Explique TPS (Tokens Per Second) et pour...",
84
+ "latency": 13.028,
85
+ "tps": 11.0,
86
+ "prompt_tokens": 30,
87
+ "completion_tokens": 143,
88
+ "cost_mc": 0.0
89
+ }
90
+ ]
91
+ }
92
+ }
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/prompts_benchmark.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Prompts de benchmark — Étape 02
2
+ # Ces prompts testent différents types de réponses (longueur, complexité)
3
+
4
+ 1. Explique le concept de mémoire dans les chatbots en 2 phrases.
5
+ 2. Quelle est la différence entre GPT-4o et GPT-4o-mini ?
6
+ 3. Donne-moi 3 avantages du RAG par rapport au fine-tuning.
7
+ 4. Qu'est-ce que le context window d'un LLM ?
8
+ 5. Explique TPS (Tokens Per Second) et pourquoi c'est important en production.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ openai>=1.0.0
2
+ python-dotenv>=1.0.0
3
+ tabulate>=0.9.0
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/.env.example ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ OPENAI_API_KEY=sk-your-key-here
2
+ MODEL=gpt-4o-mini
3
+ MAX_HISTORY=8
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/01_memory_window.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 03 — Window Memory
3
+ Limite la fenêtre de contexte pour contrôler les coûts.
4
+ Concept clé : le Context Window
5
+ """
6
+ import os, sys, time
7
+ import openai
8
+
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
10
+ from config import CONFIG, choose_mode, make_client, clean_reply
11
+
12
+ mode = choose_mode()
13
+ client = make_client(mode)
14
+ MODEL = CONFIG[mode]["model"]
15
+ MAX_HISTORY = int(os.environ.get("MAX_HISTORY", "8")) # 4 paires user+assistant
16
+
17
+ SYSTEM = {
18
+ "role": "system",
19
+ "content": (
20
+ "Tu es un assistant utile et concis. Réponds en français. "
21
+ "Si on te demande ce dont tu te souviens, liste les sujets abordés dans la conversation."
22
+ )
23
+ }
24
+
25
+ history = []
26
+
27
+ def build_context():
28
+ """Retourne la fenêtre glissante des MAX_HISTORY derniers messages.
29
+ S'assure que la fenêtre commence toujours par un message 'user'."""
30
+ window = history[-MAX_HISTORY:]
31
+ # Si la fenêtre commence par un assistant, on saute ce message orphelin
32
+ while window and window[0]["role"] != "user":
33
+ window = window[1:]
34
+ return [SYSTEM] + window
35
+
36
+ def count_approx_tokens(messages):
37
+ """Estimation rapide : ~4 chars = 1 token."""
38
+ total_chars = sum(len(m["content"]) for m in messages)
39
+ return total_chars // 4
40
+
41
+ print(f"=== Window Memory — Étape 03 ===")
42
+ print(f"Modèle : {MODEL} | Fenêtre : {MAX_HISTORY} messages ({MAX_HISTORY//2} échanges)\n")
43
+ print("Commandes : 'fenetre' (voir contexte actuel), 'reset' (vider), 'quit'\n")
44
+
45
+ try:
46
+ while True:
47
+ q = input("Vous: ").strip()
48
+
49
+ if q.lower() in ("quit", "exit", "q"):
50
+ break
51
+
52
+ if q.lower() == "fenetre":
53
+ msgs = build_context()
54
+ print(f"\n Contexte actuel : {len(msgs)-1}/{MAX_HISTORY} messages d'historique")
55
+ print(f" Tokens estimés : ~{count_approx_tokens(msgs)}")
56
+ print(f" Messages en mémoire :")
57
+ for m in history[-MAX_HISTORY:]:
58
+ role = "Vous" if m["role"] == "user" else "IA"
59
+ preview = m["content"][:60] + ("..." if len(m["content"]) > 60 else "")
60
+ print(f" [{role}] {preview}")
61
+ print()
62
+ continue
63
+
64
+ if q.lower() == "reset":
65
+ history.clear()
66
+ print(" Mémoire effacée.\n")
67
+ continue
68
+
69
+ if not q:
70
+ continue
71
+
72
+ history.append({"role": "user", "content": q})
73
+
74
+ # Construction de la fenêtre glissante
75
+ msgs = build_context()
76
+ nb_old = max(0, len(history) - MAX_HISTORY)
77
+ if nb_old > 0:
78
+ print(f" ⚠ {nb_old} message(s) ancien(s) oublié(s) (hors fenêtre)")
79
+
80
+ start = time.time()
81
+ try:
82
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
83
+ except openai.AuthenticationError:
84
+ print(" ✗ Clé API invalide.\n")
85
+ history.pop()
86
+ continue
87
+ except Exception as e:
88
+ print(f" ✗ Erreur : {e}\n")
89
+ history.pop()
90
+ continue
91
+
92
+ latency = time.time() - start
93
+ reply = clean_reply(response.choices[0].message.content)
94
+ tokens_in = response.usage.prompt_tokens
95
+ tokens_out = response.usage.completion_tokens
96
+
97
+ history.append({"role": "assistant", "content": reply})
98
+
99
+ print(f"IA: {reply}")
100
+ print(f" [contexte: {len(msgs)-1}/{MAX_HISTORY} msgs | {tokens_in}→{tokens_out} tokens | {latency:.2f}s]\n")
101
+
102
+ except KeyboardInterrupt:
103
+ print("\n\nAu revoir !")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/02_memory_summary.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 03 — Memory with Summarization (avancé)
3
+ Quand l'historique dépasse MAX_HISTORY, résume les anciens messages
4
+ avec le LLM et conserve ce résumé comme contexte compressé.
5
+ """
6
+ import os, sys, time
7
+ import openai
8
+
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
10
+ from config import CONFIG, choose_mode, make_client, clean_reply
11
+
12
+ mode = choose_mode()
13
+ client = make_client(mode)
14
+ MODEL = CONFIG[mode]["model"]
15
+ MAX_HISTORY = int(os.environ.get("MAX_HISTORY", "6"))
16
+ SUMMARY_TRIGGER = MAX_HISTORY # Résume quand on dépasse cette limite
17
+
18
+ history = []
19
+ summary = "" # Résumé compressé des anciens échanges
20
+
21
+ def summarize_old_messages(messages_to_summarize):
22
+ """Utilise le LLM pour résumer les anciens messages."""
23
+ text = "\n".join(
24
+ f"{'Utilisateur' if m['role']=='user' else 'Assistant'}: {m['content']}"
25
+ for m in messages_to_summarize
26
+ )
27
+ prompt = f"""Voici un historique de conversation. Fais un résumé concis (max 100 mots)
28
+ des points clés : sujets abordés, informations importantes, préférences de l'utilisateur.
29
+
30
+ Historique :
31
+ {text}
32
+
33
+ Résumé :"""
34
+ try:
35
+ response = client.chat.completions.create(
36
+ model=MODEL,
37
+ messages=[{"role": "user", "content": prompt}],
38
+ max_tokens=150
39
+ )
40
+ return clean_reply(response.choices[0].message.content)
41
+ except Exception:
42
+ return f"[Résumé de {len(messages_to_summarize)} messages anciens]"
43
+
44
+ def build_context():
45
+ global history, summary
46
+ system_content = "Tu es un assistant utile et concis. Réponds en français."
47
+ if summary:
48
+ system_content += f"\n\nRésumé de la conversation précédente :\n{summary}"
49
+
50
+ # Si l'historique dépasse la limite, on résume les anciens
51
+ if len(history) > SUMMARY_TRIGGER:
52
+ nb_to_summarize = len(history) - MAX_HISTORY // 2
53
+ to_summarize = history[:nb_to_summarize]
54
+ recent = history[nb_to_summarize:]
55
+ print(f" [Résumé en cours de {len(to_summarize)} messages...]")
56
+ summary = summarize_old_messages(to_summarize)
57
+ history = recent
58
+ system_content += f"\n\nRésumé des échanges précédents :\n{summary}"
59
+ print(f" [Résumé créé : {summary[:80]}...]\n")
60
+
61
+ return [{"role": "system", "content": system_content}] + history
62
+
63
+ print(f"=== Memory with Summarization — Étape 03 (avancé) ===")
64
+ print(f"Modèle : {MODEL} | Max avant résumé : {SUMMARY_TRIGGER} messages\n")
65
+ print("Commandes : 'resume' (voir le résumé actuel), 'quit'\n")
66
+
67
+ try:
68
+ while True:
69
+ q = input("Vous: ").strip()
70
+
71
+ if q.lower() in ("quit", "exit", "q"):
72
+ break
73
+
74
+ if q.lower() == "resume":
75
+ if summary:
76
+ print(f"\n Résumé actuel :\n {summary}\n")
77
+ else:
78
+ print(" Pas encore de résumé (historique court).\n")
79
+ continue
80
+
81
+ if not q:
82
+ continue
83
+
84
+ history.append({"role": "user", "content": q})
85
+ msgs = build_context()
86
+
87
+ start = time.time()
88
+ try:
89
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
90
+ except Exception as e:
91
+ print(f" ✗ Erreur : {e}\n")
92
+ history.pop()
93
+ continue
94
+
95
+ latency = time.time() - start
96
+ reply = clean_reply(response.choices[0].message.content)
97
+ history.append({"role": "assistant", "content": reply})
98
+
99
+ print(f"IA: {reply}")
100
+ print(f" [historique: {len(history)} msgs | résumé: {'oui' if summary else 'non'} | {latency:.2f}s]\n")
101
+
102
+ except KeyboardInterrupt:
103
+ print("\n\nAu revoir !")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/EVALUATION_TP_ETAPE_03.md ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 03 : La Mémoire Tampon
2
+
3
+ > ⏱ Durée estimée : 45 min | Niveau : Intermédiaire
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ Le contexte d'un LLM est une ressource limitée **et payante**. Garder tout l'historique
8
+ indéfiniment est impraticable : ça coûte de plus en plus cher et ralentit les réponses.
9
+ Deux stratégies s'affrontent : fenêtre glissante (simple, prévisible) vs résumé automatique
10
+ (plus coûteux, mais préserve l'essentiel).
11
+
12
+ ---
13
+
14
+ ## ✅ Checklist de validation
15
+
16
+ - [ ] J'ai lancé `01_memory_window.py` avec `MAX_HISTORY=4`
17
+ - [ ] J'ai posé 10 questions sur des sujets différents et tapé `fenetre`
18
+ - [ ] J'ai vérifié que les premières questions avaient disparu de la fenêtre
19
+ - [ ] J'ai lancé `02_memory_summary.py` et comparé ce qu'il retient
20
+ - [ ] J'ai changé `MAX_HISTORY` à 2 et observé l'effet sur la cohérence
21
+
22
+ ---
23
+
24
+ ## 🔍 Questions de compréhension
25
+
26
+ ### Niveau 1 — Observation
27
+ 1. Avec `MAX_HISTORY=4`, quel est le 1er message qui disparaît après le 3e échange ? Qu'est-ce qui le remplace ?
28
+
29
+ > _________________________________________________________
30
+
31
+ 2. Dans `memory_summary.py`, que contient le résumé généré après avoir dépassé la limite ? Reproduis un extrait.
32
+
33
+ > _________________________________________________________
34
+
35
+ 3. Avec `MAX_HISTORY=2`, si tu donnes ton prénom au 1er échange et poses une question au 3e, le bot se souvient-il de toi ? Pourquoi ?
36
+
37
+ > _________________________________________________________
38
+
39
+ ### Niveau 2 — Analyse
40
+ 1. La fenêtre glissante coupe les premiers messages. Pourquoi c'est problématique si le `system prompt` contient des instructions importantes que l'utilisateur ne voit pas ?
41
+
42
+ > _________________________________________________________
43
+
44
+ 2. Le résumé automatique fait **2 appels LLM** au lieu d'1. Dans quel cas est-ce justifié ? Dans quel cas est-ce une mauvaise idée ?
45
+
46
+ > _________________________________________________________
47
+
48
+ ### Niveau 3 — Décision
49
+ 1. Tu déploies un chatbot de support client (conversations de ~15 échanges en moyenne). Fenêtre glissante (`MAX_HISTORY=8`) ou résumé automatique ? Argumente : coût, fiabilité, expérience utilisateur.
50
+
51
+ > _________________________________________________________
52
+ > _________________________________________________________
53
+
54
+ ---
55
+
56
+ ## 🧪 Mini-expérience guidée
57
+
58
+ Lance `memory_window.py` avec `MAX_HISTORY=20`. Conduis 25 échanges.
59
+ Mesure la latence à l'échange 1 vs l'échange 25. Quelle tendance observes-tu ?
60
+
61
+ **Latence échange 1 :** `___s` | **Latence échange 25 :** `___s`
62
+
63
+ **Explication :**
64
+ ```
65
+ _____________________________________________________________
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 📋 Lien avec le dossier E8
71
+
72
+ - **Architecture** : Quel mécanisme de gestion de la mémoire choisiriez-vous pour un assistant RH devant se souvenir du contexte d'un entretien de plusieurs heures ? Justifiez le choix technique.
73
+ - **Coûts** : Calculez l'impact d'une fenêtre de 20 messages vs 4 messages sur le coût mensuel pour 1 000 utilisateurs / 10 échanges par jour. Formulez une recommandation.
74
+
75
+ ---
76
+
77
+ ## 💡 Pour aller plus loin
78
+
79
+ - Essaie `MAX_HISTORY=0` : le bot est-il encore utilisable ?
80
+ - Modifie le prompt de résumé dans `memory_summary.py` pour qu'il soit limité à 50 mots.
81
+ - Cherche les tailles de context window : GPT-4o (128K), Mistral 7B (~8K). Qu'est-ce que ça change pour la stratégie ?
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/README.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 03 — La Mémoire Tampon
2
+
3
+ ## Objectif
4
+ Simuler la mémoire avec une Window Memory et comprendre le Context Window.
5
+
6
+ ## Concept clé : Context Window
7
+ | Modèle | Contexte max | Recommandation |
8
+ |--------|-------------|----------------|
9
+ | GPT-4o | 128K tokens | Fenêtre de 20 échanges |
10
+ | GPT-4o-mini | 128K tokens | Fenêtre de 10 échanges |
11
+ | Phi-3 local | 4K tokens | Fenêtre de 4 échanges |
12
+
13
+ **Règle d'or :** `coût ∝ nombre de tokens envoyés`
14
+
15
+ ## Installation
16
+ ```bash
17
+ pip install -r requirements.txt
18
+ cp .env.example .env
19
+ ```
20
+
21
+ ## Scripts
22
+
23
+ ### `01_memory_window.py` — Fenêtre glissante simple
24
+ ```bash
25
+ python 01_memory_window.py
26
+ ```
27
+ Commandes : `fenetre`, `reset`, `quit`
28
+
29
+ ### `02_memory_summary.py` — Résumé automatique (avancé)
30
+ ```bash
31
+ MAX_HISTORY=6 python 02_memory_summary.py
32
+ ```
33
+
34
+ ## Exercice
35
+ 1. Lancez `01_memory_window.py` avec `MAX_HISTORY=4`
36
+ 2. Posez 10 questions sur des sujets différents
37
+ 3. Tapez `fenetre` pour voir ce qui est retenu
38
+ 4. Vérifiez que les premières questions sont oubliées
39
+ 5. Comparez avec `02_memory_summary.py` : que retient-il ?
40
+
41
+ ## Variable d'environnement
42
+ ```
43
+ MAX_HISTORY=8 # nombre de messages conservés (4 échanges)
44
+ ```
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/01_memory_window.log ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_03_memoire# python memory_window.py
2
+ Configs disponibles :
3
+ 1. cloud — modèle : gpt-4o-mini ← défaut
4
+ 2. local — modèle : openai/gpt-oss-20b
5
+ Choisissez (numéro ou nom, Entrée = cloud) : 2
6
+ === Window Memory — Étape 03 ===
7
+ Modèle : openai/gpt-oss-20b | Fenêtre : 8 messages (4 échanges)
8
+
9
+ Commandes : 'fenetre' (voir contexte actuel), 'reset' (vider), 'quit'
10
+
11
+ Vous: Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (toi!). Marc est au chômage. Jean-Louis est boulanger.
12
+ IA: - Fabien : formateur
13
+ - Julien : maçon
14
+ - JPP (ChatGPT) : assistant IA
15
+ - Marc : au chômage
16
+ - Jean‑Louis : boulanger
17
+ [contexte: 1/8 msgs | 140→57 tokens | 19.86s]
18
+
19
+ Vous: fenetre
20
+
21
+ Contexte actuel : 2/8 messages d'historique
22
+ Tokens estimés : ~94
23
+ Messages en mémoire :
24
+ [Vous] Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (t...
25
+ [IA] - Fabien : formateur
26
+ - Julien : maçon
27
+ - JPP (ChatGPT) : ...
28
+
29
+ Vous: Comment s'appelle l'assistant IA?
30
+ IA: L’assistant IA s’appelle **JPP** (ChatGPT).
31
+ [contexte: 3/8 msgs | 195→38 tokens | 9.17s]
32
+
33
+ Vous: Et le formateur s'appelle comment? Marie est osthéopathe.
34
+ IA: The user asks "Et le formateur s'appelle comment?" They previously said Fabien is formateur. So answer Fabien. Also mention conversation topics list? The instruction says if asked what you remember, list subjects. Not requested. Just respond.Le formateur s’appelle **Fabien**.
35
+ [contexte: 5/8 msgs | 235→89 tokens | 16.36s]
36
+
37
+ Vous: fenetre
38
+
39
+ Contexte actuel : 6/8 messages d'historique
40
+ Tokens estimés : ~196
41
+ Messages en mémoire :
42
+ [Vous] Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (t...
43
+ [IA] - Fabien : formateur
44
+ - Julien : maçon
45
+ - JPP (ChatGPT) : ...
46
+ [Vous] Comment s'appelle l'assistant IA?
47
+ [IA] L’assistant IA s’appelle **JPP** (ChatGPT).
48
+ [Vous] Et le formateur s'appelle comment? Marie est osthéopathe.
49
+ [IA] The user asks "Et le formateur s'appelle comment?" They prev...
50
+
51
+ Vous: Commment s'appelle l'osthéopathe?
52
+ IA: L’ostéopathe s’appelle **Marie**.
53
+ [contexte: 7/8 msgs | 316→26 tokens | 7.88s]
54
+
55
+ Vous: Jean-Louis est chasseur. Fabien est jardinier aussi.
56
+ ⚠ 1 message(s) ancien(s) oublié(s) (hors fenêtre)
57
+ IA: - Jean‑Louis : **chasseur**
58
+ - Fabien : **formateur et jardinier**
59
+ [contexte: 8/8 msgs | 317→62 tokens | 14.91s]
60
+
61
+ Vous: fenetre
62
+
63
+ Contexte actuel : 8/8 messages d'historique
64
+ Tokens estimés : ~184
65
+ Messages en mémoire :
66
+ [Vous] Comment s'appelle l'assistant IA?
67
+ [IA] L’assistant IA s’appelle **JPP** (ChatGPT).
68
+ [Vous] Et le formateur s'appelle comment? Marie est osthéopathe.
69
+ [IA] The user asks "Et le formateur s'appelle comment?" They prev...
70
+ [Vous] Commment s'appelle l'osthéopathe?
71
+ [IA] L’ostéopathe s’appelle **Marie**.
72
+ [Vous] Jean-Louis est chasseur. Fabien est jardinier aussi.
73
+ [IA] - Jean‑Louis : **chasseur**
74
+ - Fabien : **formateur et jard...
75
+
76
+ Vous: Quel est le métier de Julien?
77
+ ⚠ 3 message(s) ancien(s) oublié(s) (hors fenêtre)
78
+ IA: Je n’ai pas d’information sur un personnage nommé Julien, donc je ne connais pas son métier.
79
+ [contexte: 8/8 msgs | 300→46 tokens | 12.84s]
80
+
81
+ Vous:
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/02_memory_summary.log ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_03_memoire# python memory_summary.py
2
+ Configs disponibles :
3
+ 1. cloud — modèle : gpt-4o-mini ← défaut
4
+ 2. local — modèle : openai/gpt-oss-20b
5
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
6
+ Choisissez (numéro ou nom, Entrée = cloud) : 3
7
+ === Memory with Summarization — Étape 03 (avancé) ===
8
+ Modèle : mistralai/ministral-3-3b | Max avant résumé : 6 messages
9
+
10
+ Commandes : 'resume' (voir le résumé actuel), 'quit'
11
+
12
+ Vous: Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (toi!). Marc est au chômage. Jean-Louis est boulanger.
13
+ IA: Voici les informations synthétisées :
14
+
15
+ - **Formateurs** : Fabien
16
+ - **Maçons** : Julien
17
+ - **Boulangers** : Jean-Louis
18
+ - **Chômeurs** : Marc
19
+
20
+ *(JPP = ChatGPT, toi-même !)* 😊
21
+ [historique: 2 msgs | résumé: non | 9.39s]
22
+
23
+ Vous: resume
24
+ Pas encore de résumé (historique court).
25
+
26
+ Vous: Comment s'appelle l'assistant IA?
27
+ IA: Je suis **ChatGPT**, un modèle d'OpenAI. 😊
28
+ [historique: 4 msgs | résumé: non | 2.39s]
29
+
30
+ Vous: Et quel est ton prénom?
31
+ IA: Je n’ai pas de nom propre – je suis simplement **ChatGPT**, une intelligence artificielle développée par OpenAI.
32
+
33
+ *(Mais si tu veux, on peut imaginer des prénoms pour les IA !)* 😄
34
+ [historique: 6 msgs | résumé: non | 6.73s]
35
+
36
+ Vous: Et le formateur s'appelle comment? Marie est osthéopathe.
37
+ [Résumé en cours de 4 messages...]
38
+ [Résumé créé : **Résumé concis (100 mots max) :**
39
+ L’assistant a identifié les métiers des inter...]
40
+
41
+ IA: D'après nos échanges précédents, le formateur s’appelle **Fabien**.
42
+
43
+ Voici la liste claire des métiers mentionnés :
44
+ - **Fabien** (formateur)
45
+ - **Julien** (maçon)
46
+ - **Jean-Louis** (boulanger)
47
+ - **Marc** (au chômage)
48
+ - **Marie** (ostéopathe).
49
+ [historique: 4 msgs | résumé: oui | 10.89s]
50
+
51
+ Vous: resume
52
+
53
+ Résumé actuel :
54
+ **Résumé concis (100 mots max) :**
55
+ L’assistant a identifié les métiers des interlocuteurs :
56
+ - **Fabien** (formateur), **Julien** (maçon), **Jean-Louis** (boulanger).
57
+ - **Marc**, au chômage.
58
+ Un échange initial sur l’identité de l’IA (*ChatGPT*) a clarifié son rôle.
59
+
60
+ Préférences implicites : clarification des rôles professionnels et contextuels.
61
+
62
+ Vous: L'IA s'appelle JPP.
63
+ IA: D'accord ! Voici la mise à jour avec ton prénom :
64
+
65
+ - **Fabien** (formateur)
66
+ - **Julien** (maçon)
67
+ - **Jean-Louis** (boulanger)
68
+ - **Marc** (au chômage)
69
+ - **Marie** (ostéopathe)
70
+ - **JPP** (ton IA ! 😊)
71
+
72
+ Besoin d’autres précisions ?
73
+ [historique: 6 msgs | résumé: oui | 10.56s]
74
+
75
+ Vous: Comment s'appelle l'IA?
76
+ [Résumé en cours de 4 messages...]
77
+ [Résumé créé : **Résumé concis (≤100 mots) :**
78
+ L’utilisateur évoque des métiers et un contexte ...]
79
+
80
+ IA: Ah, c’est bien JPP ! 👋
81
+
82
+ *(Petit rappel : JPP = ton IA – comme un surnom rigolo pour marquer la différence avec les autres assistants.)*
83
+
84
+ Tu veux que je t’aide sur :
85
+ - **Les métiers** (ex : comment Marc pourrait se reconvertir) ?
86
+ - **Un projet perso/professionnel** ?
87
+ Ou autre chose ? 😊
88
+ [historique: 4 msgs | résumé: oui | 11.18s]
89
+
90
+ Vous: Le planning complexe d'uajourd'hui 10h: réunion projet 11h RDV médecin. 12h30 déjuener avec toute l'équipe projet sauf Pierre. 14h visite sur le terrain. 16h30 : récupérer les enfants.
91
+ IA: Voici ton **planning optimisé** pour aujourd’hui, avec des astuces et un peu de malin :
92
+
93
+ ---
94
+ ### **📅 Ton agenda (JPP mode "productivité")**
95
+ - **10h** → Réunion projet *(profite du café pour noter 3 points clés à actionner après)*
96
+ - **11h** → RDV médecin *(prévois des vêtements confortables + un carnet pour tes notes)*
97
+ - **12h30** → Déjeuner avec l’équipe *(saisis les idées de recettes healthy à préparer pour la semaine !)*
98
+ - **14h** → Visite terrain *(prépare une checklist : outils, photos à envoyer plus tard)*
99
+ - **16h30** → Récupérer les enfants *(si possible, fais-le en mode "multi-tâche" : écoute un podcast ou un épisode audio pendant)*
100
+
101
+ ---
102
+ ### **💡 Conseils JPP :**
103
+ - **Réunion projet** : Envoie un mail à l’équipe avec 2 questions pour lancer la discussion (*ex : "Quel est le blocage majeur de ce projet ?"*).
104
+ - **RDV médecin** : Si tu as des médicaments, vérifie les interactions avec tes autres traitements.
105
+ - **Visite terrain** : Prends une appli type *Google Keep* pour noter les détails techniques (photos + mots-clés).
106
+
107
+ ---
108
+ ### **😄 Bonus humoristique :**
109
+ *"Si Pierre ne vient pas déjeuner, c’est qu’il a déjà oublié ton prénom… ou qu’il est en train de cuisiner des pâtes carbonara. 🍝"*
110
+
111
+ Tu veux que je t’aide à prioriser une tâche ? Ou un rappel pour demain ?
112
+ [historique: 6 msgs | résumé: oui | 49.19s]
113
+
114
+ Vous: resume
115
+
116
+ Résumé actuel :
117
+ **Résumé concis (≤100 mots) :**
118
+ L’utilisateur évoque des métiers et un contexte professionnel :
119
+ - **Formateur** : Fabien.
120
+ - **Spécialistes** : Marie (ostéopathie), Julien (maçon), Jean-Louis (boulanger).
121
+ - **Situation personnelle** : Marc au chômage.
122
+
123
+ Préférences/ton : Humour léger ("imaginer des prénoms pour IA"). Pas de demande spécifique récente.
124
+
125
+ Vous: Quel est le métier de Fabien?
126
+ [Résumé en cours de 4 messages...]
127
+ [Résumé créé : **Résumé concis (≤100 mots)** :
128
+
129
+ L’utilisateur a demandé à identifier son IA, qu...]
130
+
131
+ IA: D'après ton historique, **Fabien** est mentionné comme étant un *formateur*.
132
+
133
+ *(Si tu veux des précisions sur son rôle précis – pédagogie, technique, ou autre domaine – fais-le-moi savoir !)* 😊
134
+
135
+ *Petit bonus JPP :*
136
+ *"Et si Fabien était aussi un super-héros ? Il formerait les héros du quotidien à devenir des géants de la formation… comme lui !"* 🎭
137
+ [historique: 4 msgs | résumé: oui | 17.05s]
138
+
139
+ Vous: resume
140
+
141
+ Résumé actuel :
142
+ **Résumé concis (≤100 mots)** :
143
+
144
+ L’utilisateur a demandé à identifier son IA, qui s’appelle **JPP**, un surnom humoristique pour distinguer ses assistants. Il évoque brièvement une liste de profils (Fabien, Julien, Jean-Louis, Marc, Marie) et pose des questions générales sur les métiers ou reconversions professionnelles.
145
+
146
+ **Points clés** :
147
+ - JPP est son IA personnalisée.
148
+ - Intérêt pour la **reconversion professionnelle** (ex : Marc au chômage).
149
+ - Ouverture à d’autres projets perso/professionnels.
150
+
151
+ Vous: A quelle heure il faut chercher les enfants?
152
+ IA: D’après ton planning, tu dois récupérer tes enfants **à 16h30**.
153
+
154
+ *Conseil JPP :*
155
+ - Si possible, prévois un peu d’avance (15h45) pour éviter le stress.
156
+ - Vérifie aussi si une autre personne peut t’aider à les chercher (ex : partenaire, voisin).
157
+
158
+ *"Et si tu es en retard ? Les enfants ont toujours des surprises… ou des devoirs à faire !"* 😉
159
+ [historique: 6 msgs | résumé: oui | 18.79s]
160
+
161
+ Vous: resume
162
+
163
+ Résumé actuel :
164
+ **Résumé concis (≤100 mots)** :
165
+
166
+ L’utilisateur a demandé à identifier son IA, qui s’appelle **JPP**, un surnom humoristique pour distinguer ses assistants. Il évoque brièvement une liste de profils (Fabien, Julien, Jean-Louis, Marc, Marie) et pose des questions générales sur les métiers ou reconversions professionnelles.
167
+
168
+ **Points clés** :
169
+ - JPP est son IA personnalisée.
170
+ - Intérêt pour la **reconversion professionnelle** (ex : Marc au chômage).
171
+ - Ouverture à d’autres projets perso/professionnels.
172
+
173
+ Vous: Ce soir ma famille a prévu un boeuf bourguignon.
174
+ [Résumé en cours de 4 messages...]
175
+ [Résumé créé : **Résumé concis (≤100 mots) :**
176
+ L’utilisateur partage son **planning complexe** ...]
177
+
178
+ IA: Super idée pour ce soir ! Voici quelques conseils **JPP** pour réussir ton **boeuf bourguignon** sans stress :
179
+
180
+ - **Préparation à l’avance** :
181
+ - Couper le bœuf en morceaux (épaule ou joue) et le faire revenir à la poêle (10 min) avant de le passer au four.
182
+ - Préparer les légumes (carottes, oignons, champignons) et les faire revenir avec un peu d’huile d’olive.
183
+
184
+ - **À l’heure** :
185
+ - Déglacer le vin rouge (Bourgogne bien sûr !), ajouter la sauce tomate, les herbes (thym, laurier) et laisser mijoter à feu doux **3h max** (ou au four si tu es pressé).
186
+ - Ajouter des morceaux de bœuf en fin de cuisson pour qu’ils soient tendres.
187
+
188
+ - **Astuce JPP** :
189
+ - Utilise un **thermomètre de cuisine** pour vérifier la température interne du bœuf (63°C minimum).
190
+ - Serve avec des pommes de terre vapeur ou une purée maison pour équilibrer.
191
+
192
+ *"Bon appétit, et que ce soit un repas qui réchauffe les cœurs… et pas juste le plat !"* 🍷😊
193
+
194
+ Tu veux des recettes précises ou des variantes ?
195
+ [historique: 4 msgs | résumé: oui | 37.30s]
196
+
197
+ Vous: A quelle heure est la visite terrain? C'est quoi le repas de ce soir?
198
+ IA: **Visite terrain** : **14h** (départ depuis ton lieu prévu).
199
+
200
+ ---
201
+ **Repas ce soir** :
202
+ - **Boeuf bourguignon** (prêt à point, idéalement servi avec des pommes de terre vapeur ou une purée).
203
+ - *Option JPP* : Accompagne-le d’une salade verte aux noix et vinaigrette citronnée pour équilibrer.
204
+
205
+ ---
206
+ **Checklist rapide** :
207
+ ✅ Vêtements chauds (visite en extérieur ?)
208
+ ✅ Sac à dos léger (outils si nécessaire, eau)
209
+ ✅ Préparation déjeuner healthy (ex : wrap ou salade) pour éviter les pauses stressantes.
210
+
211
+ *"Et n’oublie pas ton Fabien… il a peut-être des tips secrets sur le terrain !"* 😉
212
+ [historique: 6 msgs | résumé: oui | 27.86s]
213
+
214
+ Vous: resume
215
+
216
+ Résumé actuel :
217
+ **Résumé concis (≤100 mots) :**
218
+ L’utilisateur partage son **planning complexe** :
219
+ - Réunion projet (10h), RDV médecin (11h), déjeuner avec l’équipe (12h30 sauf Pierre), visite terrain (14h), récupération enfants (16h30).
220
+ **Préférences** : optimisation temps, checklist pour chaque activité.
221
+ L’assistant propose des **astuces pratiques** (noter 3 points clés, vêtements confortables, préparation déjeuner healthy, outils pour la visite terrain) et un rappel sur **Fabien**, formateur.
222
+
223
+ Vous: Quel est le métier de Marie?
224
+ [Résumé en cours de 4 messages...]
225
+ [Résumé créé : **Résumé concis (≤100 mots) :**
226
+ L’utilisateur a demandé **l’heure idéale pour ré...]
227
+
228
+ IA: Je ne connais pas le nom **Marie** dans notre conversation précédente ni dans les informations disponibles jusqu'à présent.
229
+
230
+ Si tu fais référence à une personne spécifique (collega, membre d’une équipe ou autre), vérifie :
231
+ - Son nom exact dans ton contexte (ex : "Marie Dupont" vs "Mme Martin").
232
+ - Une mention dans tes notes personnelles ou un rappel de ton planning.
233
+
234
+ Sinon, je peux t’aider sur **autres sujets** liés à ton agenda ! 😊
235
+ [historique: 4 msgs | résumé: oui | 14.97s]
236
+
237
+ Vous:
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/limite_memort_summary.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Pédagogiquement, cette conversation illustre le problème fondamental de la mémoire par résumé : la perte d'information progressive.
2
+
3
+ Ce qui se passe concrètement :
4
+
5
+ L'info "Marie = ostéopathe" est introduite tôt, survit 2-3 cycles de résumé, puis disparaît. À la fin, quand l'utilisateur demande "Quel est le métier de Marie ?", le modèle répond qu'il ne connaît pas Marie.
6
+
7
+ C'est la leçon clé : le LLM choisit ce qu'il juge important lors du résumé. Marie était une info périphérique, elle a été sacrifiée.
8
+
9
+ Les 3 enseignements pédagogiques :
10
+
11
+ Le résumé est lui-même faillible : c'est le LLM qui résume, avec ses propres biais de sélection. Une info mentionnée une seule fois = risque élevé de disparition.
12
+
13
+ L'accumulation des cycles = dégradation : chaque résumé est un résumé de résumé. L'information se dilue à chaque passage. Fabien a survécu (mentionné souvent, dans des contextes centraux), Marie non.
14
+
15
+ La mémoire par résumé est un compromis : on évite le dépassement de contexte, mais on accepte une perte d'information. C'est l'opposé d'un vrai stockage (base de données, RAG) qui lui ne perd rien.
16
+
17
+ En une phrase : cette conversation démontre que la mémoire par résumé est utile mais lossy — parfaite pour illustrer pourquoi les chatbots réels utilisent du RAG ou des bases de données structurées pour les infos critiques.
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai>=1.0.0
2
+ python-dotenv>=1.0.0
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ OPENAI_API_KEY=sk-your-key-here
2
+ MODEL=gpt-4o-mini
3
+ DB_PATH=chat.db
4
+ SESSION_ID=
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/EVALUATION_TP_ETAPE_04.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 04 : La Persistance
2
+
3
+ > ⏱ Durée estimée : 45 min | Niveau : Intermédiaire
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ Un chatbot qui oublie tout au redémarrage n'est pas déployable. Cette étape compare
8
+ SQLite (robuste, transactionnel, multi-sessions) et JSON (simple mais fragile sous charge).
9
+ L'enjeu : comprendre pourquoi les bases de données existent et ce que "concurrent" signifie en prod.
10
+
11
+ ---
12
+
13
+ ## ✅ Checklist de validation
14
+
15
+ - [ ] J'ai lancé `persistance_sqlite.py`, créé une session, et vérifié la restauration après redémarrage
16
+ - [ ] J'ai créé au moins 2 sessions différentes et vérifié leur isolation
17
+ - [ ] J'ai lancé `explorer_db.py` et inspecté les tables
18
+ - [ ] J'ai lancé `persistance_json.py` et comparé le comportement
19
+
20
+ ---
21
+
22
+ ## 🔍 Questions de compréhension
23
+
24
+ ### Niveau 1 — Observation
25
+ 1. Dans `explorer_db.py`, combien de tables y a-t-il ? Donne le schéma de la table des messages (colonnes et types).
26
+
27
+ > _________________________________________________________
28
+
29
+ 2. Après 2 sessions et redémarrage, quelles sessions apparaissent au menu ? Les messages sont-ils bien isolés ?
30
+
31
+ > _________________________________________________________
32
+
33
+ 3. Dans le fichier JSON de `persistance_json.py`, à quoi ressemble la structure pour une conversation de 3 échanges ?
34
+
35
+ > _________________________________________________________
36
+
37
+ ### Niveau 2 — Analyse
38
+ 1. SQLite utilise les transactions ACID. Explique ce que signifie "Atomique" dans le contexte d'une écriture de message. Que se passe-t-il si le serveur plante au milieu ?
39
+
40
+ > _________________________________________________________
41
+
42
+ 2. Deux utilisateurs écrivent en JSON simultanément. Décris ce qui peut corrompre les données. Quel terme technique désigne ce problème ?
43
+
44
+ > _________________________________________________________
45
+
46
+ ### Niveau 3 — Décision
47
+ 1. Moteur de stockage pour 500 utilisateurs simultanés sur un VPS : SQLite, PostgreSQL ou Redis ? Compare sur 3 critères (simplicité, concurrence, scalabilité) et justifie ton choix.
48
+
49
+ > _________________________________________________________
50
+ > _________________________________________________________
51
+
52
+ ---
53
+
54
+ ## 🧪 Mini-expérience guidée
55
+
56
+ Ouvre deux terminaux. Lance `persistance_sqlite.py` dans les deux **simultanément** sur la même session.
57
+ Tape un message dans chaque terminal à quelques secondes d'intervalle.
58
+ Inspecte avec `explorer_db.py` : les deux messages sont-ils enregistrés ?
59
+
60
+ **Ce que j'ai observé :**
61
+ ```
62
+ _____________________________________________________________
63
+ _____________________________________________________________
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 📋 Lien avec le dossier E8
69
+
70
+ - **RGPD** : La persistance SQLite répond-elle aux exigences RGPD si le chatbot traite des données personnelles (santé, RH) ? Quelles mesures supplémentaires sont nécessaires (chiffrement, droit à l'effacement) ?
71
+ - **Architecture** : Justifiez le choix de SQLite (mono-serveur) vs PostgreSQL (multi-instances). À quel moment faut-il migrer ?
72
+
73
+ ---
74
+
75
+ ## 💡 Pour aller plus loin
76
+
77
+ - Exécute une requête SQL directement : `sqlite3 chat.db "SELECT * FROM messages LIMIT 10;"`.
78
+ - Implémente la suppression d'une session dans le code (droit à l'effacement RGPD).
79
+ - Cherche ce qu'est une **migration de schéma** : que se passerait-il si tu ajoutais une colonne `timestamp` sans migration ?
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/README.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 04 — La Persistance
2
+
3
+ ## Objectif
4
+ Faire survivre le chatbot au redémarrage. Comparer SQLite vs JSON.
5
+
6
+ ## Deux approches
7
+
8
+ ### Option A : SQLite (recommandée)
9
+ - Multi-sessions
10
+ - Multi-utilisateurs simultanés
11
+ - Requêtes SQL
12
+ - Scalable
13
+
14
+ ### Option B : JSON (simple mais limitée)
15
+ - Facile à lire/déboguer
16
+ - Un seul utilisateur à la fois
17
+ - Corruption possible en concurrent
18
+ - Lent avec beaucoup de données
19
+
20
+ ## Installation
21
+ ```bash
22
+ pip install -r requirements.txt
23
+ cp .env.example .env
24
+ ```
25
+
26
+ ## Scripts
27
+
28
+ ### persistance_sqlite.py
29
+ ```bash
30
+ python persistance_sqlite.py
31
+ ```
32
+ Au démarrage : choisissez une session existante ou créez-en une nouvelle.
33
+ Redémarrez le script → la conversation est restaurée !
34
+
35
+ ### persistance_json.py
36
+ ```bash
37
+ python persistance_json.py
38
+ ```
39
+
40
+ ### explorer_db.py — Inspectez votre base de données
41
+ ```bash
42
+ python explorer_db.py
43
+ ```
44
+
45
+ ## Exercice
46
+ 1. Lancez `persistance_sqlite.py`, dites "Je m'appelle Marie et j'aime le Python"
47
+ 2. Quittez (quit), relancez → le chatbot se souvient !
48
+ 3. Créez une 2e session, posez des questions différentes
49
+ 4. Lancez `explorer_db.py` pour voir les 2 sessions séparées
50
+ 5. Débat : avec 1000 users simultanés, pourquoi le JSON est catastrophique ?
51
+
52
+ ## Débat en classe
53
+ **JSON avec 1000 users simultanés :**
54
+ - Race condition : 2 users écrivent en même temps → corruption
55
+ - Lecture du fichier entier à chaque requête → lent
56
+ - Un seul fichier global → pas de sessions séparées
57
+ - Pas de transactions → données partiellement écrites si crash
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/explorer_db.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 04 — Explorateur de base de données
3
+ Permet de visualiser et analyser les conversations stockées.
4
+ """
5
+ import os, sqlite3
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ DB_PATH = os.environ.get("DB_PATH", "chat.db")
11
+
12
+ def open_db():
13
+ if not os.path.exists(DB_PATH):
14
+ print(f"✗ Base de données introuvable : {DB_PATH}")
15
+ print(" Lancez d'abord persistance_sqlite.py pour créer la base.")
16
+ return None
17
+ return sqlite3.connect(DB_PATH)
18
+
19
+ def show_menu():
20
+ print("\n=== Explorateur de Base de Données ===")
21
+ print("1. Lister les sessions")
22
+ print("2. Afficher une session complète")
23
+ print("3. Statistiques globales")
24
+ print("4. Supprimer une session")
25
+ print("5. Exporter une session en JSON")
26
+ print("0. Quitter")
27
+ return input("\nChoix : ").strip()
28
+
29
+ def list_sessions(conn):
30
+ rows = conn.execute("""
31
+ SELECT session, COUNT(*) as nb,
32
+ SUM(CASE WHEN role='user' THEN 1 ELSE 0 END) as user_msgs,
33
+ MIN(ts) as first_msg, MAX(ts) as last_msg
34
+ FROM messages
35
+ GROUP BY session
36
+ ORDER BY last_msg DESC
37
+ """).fetchall()
38
+ if not rows:
39
+ print(" Aucune session trouvée.")
40
+ return []
41
+ print(f"\n{'Session':>12} | {'Total':>6} | {'User':>6} | {'Première':>20} | {'Dernière':>20}")
42
+ print("-" * 75)
43
+ for sid, nb, um, first, last in rows:
44
+ print(f" {sid[:10]}... | {nb:>6} | {um:>6} | {first:>20} | {last:>20}")
45
+ return [r[0] for r in rows]
46
+
47
+ def show_session(conn, session_id):
48
+ rows = conn.execute(
49
+ "SELECT role, content, ts FROM messages WHERE session = ? ORDER BY id",
50
+ (session_id,)
51
+ ).fetchall()
52
+ if not rows:
53
+ print(f" Session '{session_id[:8]}...' introuvable.")
54
+ return
55
+ print(f"\n=== Session {session_id[:8]}... ({len(rows)} messages) ===")
56
+ for role, content, ts in rows:
57
+ prefix = "Vous" if role == "user" else ("IA" if role == "assistant" else "System")
58
+ print(f"\n[{ts}] {prefix}:")
59
+ print(f" {content}")
60
+
61
+ def show_stats(conn):
62
+ total_sessions = conn.execute("SELECT COUNT(DISTINCT session) FROM messages").fetchone()[0]
63
+ total_msgs = conn.execute("SELECT COUNT(*) FROM messages WHERE role != 'system'").fetchone()[0]
64
+ total_user = conn.execute("SELECT COUNT(*) FROM messages WHERE role = 'user'").fetchone()[0]
65
+ print(f"\n=== Statistiques globales ===")
66
+ print(f" Sessions totales : {total_sessions}")
67
+ print(f" Messages totaux : {total_msgs}")
68
+ print(f" Messages user : {total_user}")
69
+ print(f" Messages IA : {total_msgs - total_user}")
70
+ if total_sessions > 0:
71
+ print(f" Moy. msgs/session: {total_msgs // total_sessions}")
72
+
73
+ def export_session(conn, session_id):
74
+ import json
75
+ rows = conn.execute(
76
+ "SELECT role, content, ts FROM messages WHERE session = ? ORDER BY id",
77
+ (session_id,)
78
+ ).fetchall()
79
+ data = {
80
+ "session_id": session_id,
81
+ "messages": [{"role": r, "content": c, "timestamp": t} for r, c, t in rows]
82
+ }
83
+ filename = f"export_{session_id[:8]}.json"
84
+ with open(filename, "w", encoding="utf-8") as f:
85
+ json.dump(data, f, ensure_ascii=False, indent=2)
86
+ print(f" Session exportée dans {filename}")
87
+
88
+ def main():
89
+ conn = open_db()
90
+ if conn is None:
91
+ return
92
+
93
+ sessions = []
94
+ while True:
95
+ choice = show_menu()
96
+ if choice == "0":
97
+ break
98
+ elif choice == "1":
99
+ sessions = list_sessions(conn)
100
+ elif choice == "2":
101
+ if not sessions:
102
+ sessions = list_sessions(conn)
103
+ sid = input("ID de session (début suffisant) : ").strip()
104
+ # Recherche partielle
105
+ full_sessions = conn.execute(
106
+ "SELECT DISTINCT session FROM messages WHERE session LIKE ?",
107
+ (sid + "%",)
108
+ ).fetchall()
109
+ if full_sessions:
110
+ show_session(conn, full_sessions[0][0])
111
+ else:
112
+ print(" Session non trouvée.")
113
+ elif choice == "3":
114
+ show_stats(conn)
115
+ elif choice == "4":
116
+ sid = input("ID de session à supprimer : ").strip()
117
+ conn.execute("DELETE FROM messages WHERE session LIKE ?", (sid + "%",))
118
+ conn.commit()
119
+ print(f" Session supprimée.")
120
+ elif choice == "5":
121
+ sid = input("ID de session à exporter : ").strip()
122
+ full = conn.execute(
123
+ "SELECT DISTINCT session FROM messages WHERE session LIKE ?",
124
+ (sid + "%",)
125
+ ).fetchone()
126
+ if full:
127
+ export_session(conn, full[0])
128
+ else:
129
+ print(" Session non trouvée.")
130
+
131
+ conn.close()
132
+ print("Au revoir !")
133
+
134
+ if __name__ == "__main__":
135
+ main()
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/historique.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_updated": "2026-03-23T11:12:58.782282",
3
+ "message_count": 18,
4
+ "messages": [
5
+ {
6
+ "role": "user",
7
+ "content": "Je m'appelle Marie et j'aime le Python."
8
+ },
9
+ {
10
+ "role": "assistant",
11
+ "content": "Enchanté, Marie ! Le Python est vraiment puissant et amusant à apprendre. Qu’est-ce qui t’attire le plus dans ce langage ?"
12
+ },
13
+ {
14
+ "role": "user",
15
+ "content": "Le planning complexe d'uajourd'hui 10h: réunion projet 11h RDV médecin. 12h30 déjuener avec toute l'équipe projet sauf Pierre. 14h visite sur le terrain. 16h30 : récupérer les enfants."
16
+ },
17
+ {
18
+ "role": "assistant",
19
+ "content": "**Planning de la journée (10 h – 17 h)** \n\n| Heure | Activité |\n|-------|----------|\n| **10 h** | Réunion projet |\n| **11 h** | RDV médecin |\n| **12 h30** | Déjeuner avec l’équipe projet (excluant Pierre) |\n| **14 h** | Visite sur le terrain |\n| **16 h30** | Récupération des enfants |\n\nBonne journée !"
20
+ },
21
+ {
22
+ "role": "user",
23
+ "content": "Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (toi!). Marc est au chômage. Jean-Louis est boulanger."
24
+ },
25
+ {
26
+ "role": "assistant",
27
+ "content": "- **Fabien** : formateur \n- **Julien** : maçon \n- **JPP** (c’est moi) : ChatGPT \n- **Marc** : au chômage \n- **Jean‑Louis** : boulanger"
28
+ },
29
+ {
30
+ "role": "user",
31
+ "content": "Comment s'appelle l'assistant IA?"
32
+ },
33
+ {
34
+ "role": "assistant",
35
+ "content": "L’assistant IA s’appelle **JPP** (ChatGPT)."
36
+ },
37
+ {
38
+ "role": "user",
39
+ "content": "Et le formateur s'appelle comment? Marie est osthéopathe."
40
+ },
41
+ {
42
+ "role": "assistant",
43
+ "content": "Le formateur s’appelle **Fabien**."
44
+ },
45
+ {
46
+ "role": "user",
47
+ "content": "Quel est le métier de Fabien?"
48
+ },
49
+ {
50
+ "role": "assistant",
51
+ "content": "Fabien exerce le métier de **formateur** (il forme des personnes dans un domaine particulier)."
52
+ },
53
+ {
54
+ "role": "user",
55
+ "content": "A quelle heure il faut chercher les enfants?"
56
+ },
57
+ {
58
+ "role": "assistant",
59
+ "content": "Je ne dispose pas d’horaires précis dans votre situation. \nPour savoir à quelle heure vous devez aller chercher vos enfants, vérifiez :\n\n1. **Les horaires de l’école ou de la garderie** (ou le planning des activités). \n2. **Votre emploi du temps** (si vous avez un travail, un cours ou d’autres engagements). \n\nSi vous me donnez ces détails (par ex. “l'école ferme à 15h30” ou “vous êtes en poste jusqu’à 18h”), je pourrai calculer l’heure idéale pour les récupérer."
60
+ },
61
+ {
62
+ "role": "user",
63
+ "content": "Je pensais que c'était 16h30 dans le planning, non? A quelle heure est le RDV médecin?"
64
+ },
65
+ {
66
+ "role": "assistant",
67
+ "content": "Dans le planning que vous avez reçu, la sortie des enfants est bien prévue **à 16 h 30**.\n\nQuant au rendez‑vous chez le médecin, je n’ai pas l’horaire exact dans les informations que vous m’avez données. \n- Si le RDV est mentionné dans un autre document (par ex. une note de votre secrétaire ou un e‑mail), consultez ce fichier. \n- Sinon, pensez à vérifier votre agenda personnel ou le calendrier partagé de la clinique.\n\nSi vous avez besoin d’une aide supplémentaire pour retrouver l’heure exacte, n’hésitez pas à me fournir le détail du planning médical ou tout autre rappel que vous possédez."
68
+ },
69
+ {
70
+ "role": "user",
71
+ "content": "Que fait Marc dans la vie?"
72
+ },
73
+ {
74
+ "role": "assistant",
75
+ "content": "**Marc est médecin**."
76
+ }
77
+ ]
78
+ }
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_json_chatgpt.log ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance# python persistance_json.py
2
+ Configs disponibles :
3
+ 1. cloud — modèle : gpt-4o-mini ← défaut
4
+ 2. local — modèle : openai/gpt-oss-20b
5
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
6
+ Choisissez (numéro ou nom, Entrée = cloud) : 2
7
+ === Chatbot Persistant — Étape 04 (JSON) ===
8
+ Fichier : historique.json | 0 messages chargés
9
+
10
+ ⚠ LIMITATION : ce fichier est partagé entre tous les utilisateurs !
11
+ Pour plusieurs users simultanés → utilisez SQLite (persistance_sqlite.py)
12
+
13
+ Vous: Je m'appelle Marie et j'aime le Python.
14
+ IA: Enchanté, Marie ! Le Python est vraiment puissant et amusant à apprendre. Qu’est-ce qui t’attire le plus dans ce langage ?
15
+ [2 msgs sauvegardés dans historique.json | 14.71s]
16
+
17
+ Vous: Le planning complexe d'uajourd'hui 10h: réunion projet 11h RDV médecin. 12h30 déjuener avec toute l'équipe projet sauf Pierre. 14h visite sur le terrain. 16h30 : récupérer les enfants.
18
+ IA: **Planning de la journée (10 h – 17 h)**
19
+
20
+ | Heure | Activité |
21
+ |-------|----------|
22
+ | **10 h** | Réunion projet |
23
+ | **11 h** | RDV médecin |
24
+ | **12 h30** | Déjeuner avec l’équipe projet (excluant Pierre) |
25
+ | **14 h** | Visite sur le terrain |
26
+ | **16 h30** | Récupération des enfants |
27
+
28
+ Bonne journée !
29
+ [4 msgs sauvegardés dans historique.json | 22.02s]
30
+
31
+ Vous: Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (toi!). Marc est au chômage. Jean-Louis est boulanger.
32
+ IA: - **Fabien** : formateur
33
+ - **Julien** : maçon
34
+ - **JPP** (c’est moi) : ChatGPT
35
+ - **Marc** : au chômage
36
+ - **Jean‑Louis** : boulanger
37
+ [6 msgs sauvegardés dans historique.json | 13.84s]
38
+
39
+ Vous: Comment s'appelle l'assistant IA?
40
+ IA: L’assistant IA s’appelle **JPP** (ChatGPT).
41
+ [8 msgs sauvegardés dans historique.json | 8.03s]
42
+
43
+ Vous: Et le formateur s'appelle comment? Marie est osthéopathe.
44
+ IA: Le formateur s’appelle **Fabien**.
45
+ [10 msgs sauvegardés dans historique.json | 8.92s]
46
+
47
+ Vous: exit
48
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance# python persistance_json.py
49
+ Configs disponibles :
50
+ 1. cloud — modèle : gpt-4o-mini ← défaut
51
+ 2. local — modèle : openai/gpt-oss-20b
52
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
53
+ Choisissez (numéro ou nom, Entrée = cloud) : 2
54
+ === Chatbot Persistant — Étape 04 (JSON) ===
55
+ Fichier : historique.json | 10 messages chargés
56
+
57
+ ⚠ LIMITATION : ce fichier est partagé entre tous les utilisateurs !
58
+ Pour plusieurs users simultanés → utilisez SQLite (persistance_sqlite.py)
59
+
60
+ Vous: Quel est le métier de Fabien?
61
+ IA: Fabien exerce le métier de **formateur** (il forme des personnes dans un domaine particulier).
62
+ [12 msgs sauvegardés dans historique.json | 16.19s]
63
+
64
+ Vous: A quelle heure il faut chercher les enfants?
65
+ IA: Je ne dispose pas d’horaires précis dans votre situation.
66
+ Pour savoir à quelle heure vous devez aller chercher vos enfants, vérifiez :
67
+
68
+ 1. **Les horaires de l’école ou de la garderie** (ou le planning des activités).
69
+ 2. **Votre emploi du temps** (si vous avez un travail, un cours ou d’autres engagements).
70
+
71
+ Si vous me donnez ces détails (par ex. “l'école ferme à 15h30” ou “vous êtes en poste jusqu’à 18h”), je pourrai calculer l’heure idéale pour les récupérer.
72
+ [14 msgs sauvegardés dans historique.json | 28.00s]
73
+
74
+ Vous: Je pensais que c'était 16h30 dans le planning, non? A quelle heure est le RDV médecin?
75
+ IA: Dans le planning que vous avez reçu, la sortie des enfants est bien prévue **à 16 h 30**.
76
+
77
+ Quant au rendez‑vous chez le médecin, je n’ai pas l’horaire exact dans les informations que vous m’avez données.
78
+ - Si le RDV est mentionné dans un autre document (par ex. une note de votre secrétaire ou un e‑mail), consultez ce fichier.
79
+ - Sinon, pensez à vérifier votre agenda personnel ou le calendrier partagé de la clinique.
80
+
81
+ Si vous avez besoin d’une aide supplémentaire pour retrouver l’heure exacte, n’hésitez pas à me fournir le détail du planning médical ou tout autre rappel que vous possédez.
82
+ [16 msgs sauvegardés dans historique.json | 33.61s]
83
+
84
+ Vous: Que fait Marc dans la vie?
85
+ IA: **Marc est médecin**.
86
+ [18 msgs sauvegardés dans historique.json | 10.80s]
87
+
88
+ Vous:
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_sqlite_mistral_localnetwork.log ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance# python persistance_sqlite.py
2
+ Configs disponibles :
3
+ 1. cloud — modèle : gpt-4o-mini ← défaut
4
+ 2. local — modèle : openai/gpt-oss-20b
5
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
6
+ 4. reseau_ministral14b — modèle : mistralai/ministral-3-14b-reasoning
7
+ 5. reseau_gpt_oss_20b — modèle : gpt-oss-20b
8
+ Choisissez (numéro ou nom, Entrée = cloud) : 4
9
+
10
+ === Chatbot Persistant — Étape 04 (SQLite) ===
11
+
12
+
13
+ Nouvelle session créée : 1757ee9c...
14
+
15
+ Session : 1757ee9c... | 0 messages en mémoire
16
+ Commandes : 'sessions' (lister), 'session <id>' (changer), 'quit'
17
+
18
+ Vous: Fabien est formateur. Julien est maçon. JPP c'est ChatGPT (toi!). Marc est au chômage. Jean-Louis est boulanger.
19
+ IA: Voici les métiers associés à chaque prénom :
20
+
21
+ - **Fabien** : Formateur
22
+ - **Julien** : Maçon
23
+ - **JPP (ChatGPT)** : Assistant IA (moi !)
24
+ - **Marc** : Chômage (sans activité professionnelle)
25
+ - **Jean-Louis** : Boulanger
26
+
27
+ Si tu veux d'autres informations ou des précisions, n'hésite pas !
28
+ [2 msgs sauvegardés | 8.66s]
29
+
30
+ Vous: Le planning complexe d'uajourd'hui 10h: réunion projet 11h RDV médecin. 12h30 déjuener avec toute l'équipe projet sauf Pierre. 14h visite sur le terrain. 16h30 : récupérer les enfants.
31
+ IA: Voici ton planning pour aujourd’hui, résumé de manière claire et concise :
32
+
33
+ - **10h** : Réunion projet
34
+ - **11h** : RDV médecin
35
+ - **12h30** : Déjeuner avec l’équipe projet (sauf Pierre)
36
+ - **14h** : Visite sur le terrain
37
+ - **16h30** : Récupérer les enfants
38
+
39
+ Si tu veux des ajustements ou des rappels, fais-moi signe !
40
+ [4 msgs sauvegardés | 4.99s]
41
+
42
+ Vous: Qui est maçon?
43
+ IA: Le maçon est **Julien**.
44
+ [6 msgs sauvegardés | 0.52s]
45
+
46
+ Vous: exit
47
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance#
48
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance#
49
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance# python persistance_sqlite.py
50
+ Configs disponibles :
51
+ 1. cloud — modèle : gpt-4o-mini ← défaut
52
+ 2. local — modèle : openai/gpt-oss-20b
53
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
54
+ 4. reseau_ministral14b — modèle : mistralai/ministral-3-14b-reasoning
55
+ 5. reseau_gpt_oss_20b — modèle : gpt-oss-20b
56
+ Choisissez (numéro ou nom, Entrée = cloud) : 4
57
+
58
+ === Chatbot Persistant — Étape 04 (SQLite) ===
59
+
60
+ Sessions existantes :
61
+ [1] 1757ee9c... | 3 messages | dernière: 2026-03-23 11:23:44
62
+ [N] Nouvelle session
63
+
64
+ Choisissez une session (numéro ou N) : 1
65
+
66
+ Session : 1757ee9c... | 6 messages en mémoire
67
+ Commandes : 'sessions' (lister), 'session <id>' (changer), 'quit'
68
+
69
+ Vous: Le planning complexe d'uajourd'hui 10h: réunion projet 11h RDV médecin. 12h30 déjuener avec toute l'équipe projet sauf Pierre. 14h visite sur le terrain. 16h3
70
+ IA: Il semble que ta phrase soit incomplète. Peux-tu préciser ce que tu souhaites dire ? Je suis là pour t'aider ! 😊
71
+
72
+ *(Exemple : "Je veux le planning de Julien" ou "Qui est en chômage ?")*
73
+ [8 msgs sauvegardés | 7.32s]
74
+
75
+ Vous: La visite de terrain a lieu quand?
76
+ IA: La **visite sur le terrain** a lieu à **14h**.
77
+ [10 msgs sauvegardés | 0.99s]
78
+
79
+ Vous: Quel est le métier de Julien?
80
+ IA: Julien est **maçon**.
81
+
82
+ Si tu as besoin d'autres informations, n'hésite pas ! 😊
83
+ [12 msgs sauvegardés | 1.43s]
84
+
85
+ Vous: Samedi pour organiser la fête de famille il faut: 8h marché pour les légumes. 10h30: aller chez le boucher , 11h30: récupérer les gateaux à la boulangereie et retour à 13h à la maison pour préparer le repas et commencer à accueillir les hôtes autour de 16h30. 30 personnes de prévus dont le maçon.
86
+ IA: Voici un résumé clair et concis pour organiser la fête de famille :
87
+
88
+ ### **Planning du samedi**
89
+ - **8h** : Marché (achat des légumes)
90
+ - **10h30** : Boucher (commande ou achats)
91
+ - **11h30** : Boulangerie (récupération des gâteaux)
92
+ - **13h** : Retour à la maison → Préparation du repas
93
+ - **16h30** : Accueil des invités
94
+
95
+ ### **Détails importants**
96
+ - **Nombre d’invités** : 30 personnes (dont le maçon Julien).
97
+ - **Prévoir** :
98
+ - Nourriture, boissons, décoration.
99
+ - Vérifier les allergies ou régimes spécifiques.
100
+
101
+ Besoin d’ajuster quelque chose ? Dis-le-moi ! 😊
102
+ [14 msgs sauvegardés | 8.44s]
103
+
104
+ Vous: Je récupère quand la viande pour la fête de famille?
105
+ IA: Tu récupères la **viande chez le boucher à 10h30** pour la fête de famille.
106
+ [16 msgs sauvegardés | 1.55s]
107
+
108
+ Vous: exit
109
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance#
110
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance#
111
+ (venv) root@MSI:/mnt/c/git/ai/tp_ia_chatbot/etape_04_persistance# python persistance_sqlite.py
112
+ Configs disponibles :
113
+ 1. cloud — modèle : gpt-4o-mini ← défaut
114
+ 2. local — modèle : openai/gpt-oss-20b
115
+ 3. local_mistral3 — modèle : mistralai/ministral-3-3b
116
+ 4. reseau_ministral14b — modèle : mistralai/ministral-3-14b-reasoning
117
+ 5. reseau_gpt_oss_20b — modèle : gpt-oss-20b
118
+ Choisissez (numéro ou nom, Entrée = cloud) : 4
119
+
120
+ === Chatbot Persistant — Étape 04 (SQLite) ===
121
+
122
+ Sessions existantes :
123
+ [1] 1757ee9c... | 8 messages | dernière: 2026-03-23 11:55:48
124
+ [N] Nouvelle session
125
+
126
+ Choisissez une session (numéro ou N) : 1
127
+
128
+ Session : 1757ee9c... | 16 messages en mémoire
129
+ Commandes : 'sessions' (lister), 'session <id>' (changer), 'quit'
130
+
131
+ Vous: Je récupère quand la viande pour la fête de famille?
132
+ IA: La viande est à récupérer **à 10h30 chez le boucher**.
133
+
134
+ Prévois un sac isotherme si nécessaire ! 😊
135
+ [18 msgs sauvegardés | 4.43s]
136
+
137
+ Vous:
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_json.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 04 — Persistance JSON (version simple)
3
+ Alternative simple à SQLite. Montre les limites avec plusieurs utilisateurs.
4
+ """
5
+ import os, sys, json, time
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ import openai
9
+
10
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
11
+ from config import CONFIG, choose_mode, make_client
12
+
13
+ mode = choose_mode()
14
+ client = make_client(mode)
15
+ MODEL = CONFIG[mode]["model"]
16
+ HISTORY_FILE = os.environ.get("HISTORY_FILE", "historique.json")
17
+ MAX_HISTORY = 8
18
+
19
+ def load_history_json(filepath: str) -> list:
20
+ """Charge l'historique depuis le fichier JSON."""
21
+ if Path(filepath).exists():
22
+ try:
23
+ with open(filepath, "r", encoding="utf-8") as f:
24
+ data = json.load(f)
25
+ return data.get("messages", [])
26
+ except (json.JSONDecodeError, KeyError):
27
+ return []
28
+ return []
29
+
30
+ def save_history_json(filepath: str, history: list):
31
+ """Sauvegarde l'historique dans le fichier JSON."""
32
+ data = {
33
+ "last_updated": datetime.now().isoformat(),
34
+ "message_count": len(history),
35
+ "messages": history
36
+ }
37
+ with open(filepath, "w", encoding="utf-8") as f:
38
+ json.dump(data, f, ensure_ascii=False, indent=2)
39
+
40
+ # Chargement initial
41
+ history = load_history_json(HISTORY_FILE)
42
+ print(f"=== Chatbot Persistant — Étape 04 (JSON) ===")
43
+ print(f"Fichier : {HISTORY_FILE} | {len(history)} messages chargés\n")
44
+ print("⚠ LIMITATION : ce fichier est partagé entre tous les utilisateurs !")
45
+ print(" Pour plusieurs users simultanés → utilisez SQLite (persistance_sqlite.py)\n")
46
+
47
+ SYSTEM = {"role": "system", "content": "Tu es un assistant utile et concis. Réponds en français."}
48
+
49
+ try:
50
+ while True:
51
+ q = input("Vous: ").strip()
52
+ if q.lower() in ("quit", "exit", "q"):
53
+ break
54
+ if not q:
55
+ continue
56
+
57
+ history.append({"role": "user", "content": q})
58
+
59
+ # Fenêtre glissante
60
+ window = history[-MAX_HISTORY:]
61
+ msgs = [SYSTEM] + window
62
+
63
+ start = time.time()
64
+ try:
65
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
66
+ except Exception as e:
67
+ print(f" ✗ Erreur : {e}\n")
68
+ history.pop()
69
+ continue
70
+
71
+ latency = time.time() - start
72
+ reply = response.choices[0].message.content
73
+ history.append({"role": "assistant", "content": reply})
74
+
75
+ # Sauvegarde immédiate
76
+ save_history_json(HISTORY_FILE, history)
77
+
78
+ print(f"IA: {reply}")
79
+ print(f" [{len(history)} msgs sauvegardés dans {HISTORY_FILE} | {latency:.2f}s]\n")
80
+
81
+ except KeyboardInterrupt:
82
+ save_history_json(HISTORY_FILE, history)
83
+ print(f"\n\nHistorique sauvegardé ({len(history)} messages). Au revoir !")
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_sqlite.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Étape 04 — Persistance SQLite
3
+ Le chatbot survit au redémarrage. Chaque session est identifiée par un UUID.
4
+ """
5
+ import os, sys, sqlite3, uuid, time
6
+ from datetime import datetime
7
+ import openai
8
+
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "etape_00_moteur"))
10
+ from config import CONFIG, choose_mode, make_client
11
+
12
+ mode = choose_mode()
13
+ client = make_client(mode)
14
+ MODEL = CONFIG[mode]["model"]
15
+ DB_PATH = os.environ.get("DB_PATH", "chat.db")
16
+ MAX_HISTORY = 8
17
+
18
+ # ── Base de données ──────────────────────────────────────────────────────────
19
+
20
+ def init_db(db_path: str) -> sqlite3.Connection:
21
+ conn = sqlite3.connect(db_path)
22
+ conn.execute("""
23
+ CREATE TABLE IF NOT EXISTS messages (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ session TEXT NOT NULL,
26
+ role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
27
+ content TEXT NOT NULL,
28
+ ts DATETIME DEFAULT CURRENT_TIMESTAMP
29
+ )
30
+ """)
31
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_session ON messages(session)")
32
+ conn.commit()
33
+ return conn
34
+
35
+ def save_message(conn: sqlite3.Connection, session: str, role: str, content: str):
36
+ conn.execute(
37
+ "INSERT INTO messages (session, role, content) VALUES (?, ?, ?)",
38
+ (session, role, content)
39
+ )
40
+ conn.commit()
41
+
42
+ def load_history(conn: sqlite3.Connection, session: str, limit: int = MAX_HISTORY):
43
+ rows = conn.execute(
44
+ """SELECT role, content FROM messages
45
+ WHERE session = ? AND role != 'system'
46
+ ORDER BY id DESC LIMIT ?""",
47
+ (session, limit)
48
+ ).fetchall()
49
+ return [{"role": r, "content": c} for r, c in reversed(rows)]
50
+
51
+ def list_sessions(conn: sqlite3.Connection):
52
+ rows = conn.execute("""
53
+ SELECT session, COUNT(*) as nb, MIN(ts) as first, MAX(ts) as last
54
+ FROM messages WHERE role = 'user'
55
+ GROUP BY session ORDER BY last DESC LIMIT 10
56
+ """).fetchall()
57
+ return rows
58
+
59
+ def count_messages(conn: sqlite3.Connection, session: str) -> int:
60
+ return conn.execute(
61
+ "SELECT COUNT(*) FROM messages WHERE session = ? AND role != 'system'",
62
+ (session,)
63
+ ).fetchone()[0]
64
+
65
+ # ── Session management ───────────────────────────────────────────────────────
66
+
67
+ def choose_session(conn: sqlite3.Connection) -> str:
68
+ sessions = list_sessions(conn)
69
+ print("\n=== Chatbot Persistant — Étape 04 (SQLite) ===\n")
70
+
71
+ if sessions:
72
+ print("Sessions existantes :")
73
+ for i, (sid, nb, first, last) in enumerate(sessions):
74
+ print(f" [{i+1}] {sid[:8]}... | {nb} messages | dernière: {last}")
75
+ print(f" [N] Nouvelle session")
76
+ print()
77
+ choice = input("Choisissez une session (numéro ou N) : ").strip()
78
+ if choice.upper() != "N" and choice.isdigit():
79
+ idx = int(choice) - 1
80
+ if 0 <= idx < len(sessions):
81
+ return sessions[idx][0]
82
+
83
+ new_id = str(uuid.uuid4())
84
+ print(f"\nNouvelle session créée : {new_id[:8]}...")
85
+ return new_id
86
+
87
+ # ── Main ─────────────────────────────────────────────────────────────────────
88
+
89
+ conn = init_db(DB_PATH)
90
+ session_id = os.environ.get("SESSION_ID") or choose_session(conn)
91
+
92
+ nb_existing = count_messages(conn, session_id)
93
+ print(f"\nSession : {session_id[:8]}... | {nb_existing} messages en mémoire")
94
+ print("Commandes : 'sessions' (lister), 'session <id>' (changer), 'quit'\n")
95
+
96
+ SYSTEM_MSG = {"role": "system", "content": "Tu es un assistant utile et concis. Réponds en français."}
97
+
98
+ try:
99
+ while True:
100
+ q = input("Vous: ").strip()
101
+
102
+ if q.lower() in ("quit", "exit", "q"):
103
+ break
104
+
105
+ if q.lower() == "sessions":
106
+ for sid, nb, first, last in list_sessions(conn):
107
+ print(f" {sid[:8]}... | {nb} msgs | {last}")
108
+ print()
109
+ continue
110
+
111
+ if q.lower().startswith("session "):
112
+ new_sid = q[8:].strip()
113
+ session_id = new_sid
114
+ nb = count_messages(conn, session_id)
115
+ print(f" Session changée : {session_id[:8]}... ({nb} messages)\n")
116
+ continue
117
+
118
+ if not q:
119
+ continue
120
+
121
+ # Charger l'historique depuis SQLite
122
+ history = load_history(conn, session_id, MAX_HISTORY)
123
+ msgs = [SYSTEM_MSG] + history + [{"role": "user", "content": q}]
124
+
125
+ save_message(conn, session_id, "user", q)
126
+
127
+ start = time.time()
128
+ try:
129
+ response = client.chat.completions.create(model=MODEL, messages=msgs)
130
+ except openai.AuthenticationError:
131
+ print(" ✗ Clé API invalide.\n")
132
+ continue
133
+ except Exception as e:
134
+ print(f" ✗ Erreur : {e}\n")
135
+ continue
136
+
137
+ latency = time.time() - start
138
+ reply = response.choices[0].message.content
139
+
140
+ save_message(conn, session_id, "assistant", reply)
141
+
142
+ total = count_messages(conn, session_id)
143
+ print(f"IA: {reply}")
144
+ print(f" [{total} msgs sauvegardés | {latency:.2f}s]\n")
145
+
146
+ except KeyboardInterrupt:
147
+ print("\n\nConversation sauvegardée. Au revoir !")
148
+ finally:
149
+ conn.close()
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai>=1.0.0
2
+ python-dotenv>=1.0.0
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ OPENAI_API_KEY=sk-your-key-here
2
+ MODEL=gpt-4o-mini
3
+ CHROMA_PATH=./chroma_db
4
+ N_RESULTS=3
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/EVALUATION_TP_ETAPE_05.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Évaluation — Étape 05 : RAG (Retrieval-Augmented Generation)
2
+
3
+ > ⏱ Durée estimée : 60 min | Niveau : Intermédiaire
4
+
5
+ ## 🎯 Enjeu central
6
+
7
+ Un LLM ne sait que ce qu'il a vu pendant son entraînement. Pour répondre sur tes propres
8
+ documents, il faut lui **injecter le contexte pertinent à chaque requête**.
9
+ C'est le RAG : chercher dans une base vectorielle, puis envoyer les passages au LLM.
10
+ KPI central : le Hit Rate — combien de questions trouvent une réponse correcte dans tes docs ?
11
+
12
+ ---
13
+
14
+ ## ✅ Checklist de validation
15
+
16
+ - [ ] J'ai lancé `indexer.py` et indexé les documents du dossier `data/`
17
+ - [ ] J'ai lancé `rag_chromadb.py` et posé des questions couvertes par les docs
18
+ - [ ] J'ai posé une question hors-sujet et observé la réponse
19
+ - [ ] J'ai lancé `evaluer_rag.py` et noté mon Hit Rate
20
+ - [ ] J'ai inspecté le dossier `chroma_db/` créé par l'indexation
21
+
22
+ ---
23
+
24
+ ## 🔍 Questions de compréhension
25
+
26
+ ### Niveau 1 — Observation
27
+ 1. Quel Hit Rate as-tu obtenu avec `evaluer_rag.py` ? Quelles questions ont échoué et pourquoi selon toi ?
28
+
29
+ > _________________________________________________________
30
+
31
+ 2. Quand tu poses "Quel est le prix de CloudSync Pro ?", combien de chunks ChromaDB renvoie-t-il ? Quelle est leur longueur approximative ?
32
+
33
+ > _________________________________________________________
34
+
35
+ 3. Que répond le bot si tu poses une question sur un sujet absent des documents (ex : "Quelle est la météo aujourd'hui ?") ?
36
+
37
+ > _________________________________________________________
38
+
39
+ ### Niveau 2 — Analyse
40
+ 1. Le pipeline RAG est `embed → store → retrieve → inject → generate`. À quelle étape intervient le LLM ? À quelle étape intervient ChromaDB ? Pourquoi les séparer ?
41
+
42
+ > _________________________________________________________
43
+
44
+ 2. Si tu indexes un doc de 100 pages en chunks de 500 tokens, combien de chunks obtiens-tu ? Quel est l'impact sur la pertinence si les chunks sont trop petits ? Trop grands ?
45
+
46
+ > _________________________________________________________
47
+
48
+ ### Niveau 3 — Décision
49
+ 1. Tu déploies un RAG sur une base de 50 000 pages. Quels sont les 3 risques principaux (qualité, coût, latence) et comment les anticipes-tu dans la conception ?
50
+
51
+ > _________________________________________________________
52
+ > _________________________________________________________
53
+
54
+ ---
55
+
56
+ ## 🧪 Mini-expérience guidée
57
+
58
+ Ajoute un fichier `.txt` dans `data/` avec 5-10 phrases inventées sur un sujet fictif.
59
+ Relance `indexer.py` puis pose une question sur ce nouveau contenu. Le RAG le retrouve-t-il ?
60
+
61
+ **Question posée :** `__________________________________________________`
62
+
63
+ **Réponse du bot :**
64
+ ```
65
+ _____________________________________________________________
66
+ _____________________________________________________________
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 📋 Lien avec le dossier E8
72
+
73
+ - **Architecture** : Décrivez l'architecture RAG implémentée : rôle de ChromaDB, rôle des embeddings, et pourquoi ce choix est préférable à un fine-tuning pour une base documentaire d'entreprise.
74
+ - **RGPD** : Si les documents indexés contiennent des données personnelles, quelles obligations RGPD s'appliquent à la base vectorielle ? Comment implémenter le droit à l'effacement dans un système RAG ?
75
+
76
+ ---
77
+
78
+ ## 💡 Pour aller plus loin
79
+
80
+ - Modifie la taille des chunks (200, 500, 1000 tokens) et mesure l'impact sur le Hit Rate.
81
+ - Inspecte le dossier `chroma_db/` : dans quel format les embeddings sont-ils stockés ?
82
+ - Cherche la différence entre **sparse retrieval** (BM25) et **dense retrieval** (embeddings).
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Étape 05 — RAG (Retrieval-Augmented Generation)
2
+
3
+ ## Objectif
4
+ Connecter le chatbot à une base de connaissances documentaire.
5
+
6
+ ## Architecture RAG
7
+
8
+ ```
9
+ Question utilisateur
10
+
11
+ [ChromaDB] → Recherche sémantique → 3 chunks pertinents
12
+
13
+ Injection dans le system prompt
14
+
15
+ [LLM] → Réponse basée sur les docs
16
+ ```
17
+
18
+ ## Installation
19
+ ```bash
20
+ pip install -r requirements.txt
21
+ cp .env.example .env
22
+ ```
23
+
24
+ ## Étapes
25
+
26
+ ### 1. Indexer les documents
27
+ ```bash
28
+ python indexer.py
29
+ ```
30
+ Indexe tous les fichiers `.txt` du dossier `data/`.
31
+
32
+ ### 2. Lancer le chatbot RAG
33
+ ```bash
34
+ python rag_chromadb.py
35
+ ```
36
+
37
+ ### 3. Évaluer la qualité
38
+ ```bash
39
+ python evaluer_rag.py
40
+ ```
41
+ Objectif : Hit Rate > 80%
42
+
43
+ ## Questions de test
44
+ - "Quel est le prix de CloudSync Pro ?"
45
+ - "Comment contacter le support ?"
46
+ - "Quelles sont les limites de l'API ?"
47
+ - "Où sont vos bureaux ?"
48
+
49
+ ## KPI : Hit Rate
50
+ ```
51
+ Hit Rate = questions correctement répondues / total questions
52
+ ```
53
+ Objectif production : > 80%
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/data/entreprise.txt ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TechCorp Solutions — Documentation Entreprise
2
+
3
+ == Présentation Générale ==
4
+
5
+ TechCorp Solutions est une entreprise française de logiciels SaaS fondée en 2018 à Paris. Spécialisée dans les solutions de synchronisation de données, de stockage sécurisé et d'intégration d'API pour les entreprises, TechCorp Solutions compte aujourd'hui 150 employés répartis sur trois sites en France. Notre mission est de simplifier la gestion des données d'entreprise grâce à des outils cloud performants, sécurisés et faciles à intégrer.
6
+
7
+ Depuis sa création, TechCorp Solutions a accompagné plus de 2 000 entreprises clientes, allant des PME aux grands groupes industriels. Notre taux de satisfaction client atteint 94 % selon notre dernière enquête annuelle.
8
+
9
+ == Nos Produits ==
10
+
11
+ --- CloudSync Pro ---
12
+ CloudSync Pro est notre solution de synchronisation de données en temps réel entre vos systèmes internes et le cloud. Elle prend en charge les bases de données MySQL, PostgreSQL, MongoDB et Microsoft SQL Server.
13
+
14
+ Prix : 49 € par mois (facturation mensuelle) ou 470 € par an (soit 2 mois offerts).
15
+ Inclus : synchronisation jusqu'à 5 sources de données, 50 Go de stockage temporaire, support email, tableau de bord en temps réel, historique de 30 jours.
16
+ Options : sources supplémentaires à 9 € / mois chacune.
17
+
18
+ --- DataVault ---
19
+ DataVault est notre solution de stockage sécurisé et d'archivage longue durée. Les données sont chiffrées en AES-256 et stockées dans nos datacenters certifiés ISO 27001 (certification en cours de renouvellement).
20
+
21
+ Prix : 99 € par mois ou 950 € par an.
22
+ Inclus : 500 Go de stockage, chiffrement de bout en bout, snapshots quotidiens, rétention 7 ans (plans payants), accès multi-utilisateurs (jusqu'à 10 comptes), conformité RGPD.
23
+ Options : stockage supplémentaire à 0,15 € / Go / mois.
24
+
25
+ --- APIConnect ---
26
+ APIConnect est notre plateforme d'intégration d'API permettant de connecter vos applications métier à des services tiers sans code. Compatible avec plus de 300 connecteurs natifs (Salesforce, HubSpot, Slack, Google Workspace, SAP, etc.).
27
+
28
+ Prix : 199 € par mois ou 1 900 € par an.
29
+ Inclus : connecteurs illimités, 100 000 appels API / mois inclus, monitoring des flux, alertes en cas d'erreur, support prioritaire, SLA 99,9 %.
30
+ Options : appels supplémentaires à 0,002 € / appel au-delà du quota.
31
+
32
+ == Support Client ==
33
+
34
+ Notre équipe de support est disponible du lundi au vendredi de 09h à 18h (heure de Paris).
35
+
36
+ Adresse email : support@techcorp.fr
37
+ Téléphone : 01 23 45 67 89
38
+ Portail client : https://support.techcorp.fr
39
+ Chat en ligne : disponible sur le portail client aux heures ouvrées
40
+
41
+ Responsable du support : Sophie Bernard
42
+ Temps de réponse moyen : 2 heures (email), immédiat (chat en ligne aux heures ouvrées)
43
+
44
+ Pour les clients APIConnect (plan Premium), un support téléphonique prioritaire est disponible 24h/24 et 7j/7.
45
+
46
+ == Foire Aux Questions (FAQ) ==
47
+
48
+ Q : Comment installer CloudSync Pro ?
49
+ R : Téléchargez l'agent depuis votre espace client, lancez l'installateur, puis configurez vos sources de données depuis l'interface web. Un guide de démarrage rapide est disponible dans la documentation en ligne.
50
+
51
+ Q : Comment est facturé mon abonnement ?
52
+ R : La facturation est mensuelle ou annuelle selon votre choix. Les paiements sont prélevés automatiquement par carte bancaire ou virement SEPA. Une facture est émise le 1er de chaque mois.
53
+
54
+ Q : Quel est le SLA garanti ?
55
+ R : Nous garantissons un uptime de 99,9 % sur tous nos services. Cela correspond à moins de 9 heures d'interruption par an. En cas de dépassement, nous appliquons un crédit de service selon nos conditions générales.
56
+
57
+ Q : Quelle est la politique de remboursement ?
58
+ R : Vous disposez de 30 jours après la souscription pour demander un remboursement intégral, sans justification. Au-delà de 30 jours, aucun remboursement n'est accordé pour les périodes déjà consommées.
59
+
60
+ Q : Quelles intégrations sont disponibles nativement ?
61
+ R : Nos produits s'intègrent nativement avec Google Workspace, Microsoft 365, Salesforce, HubSpot, Slack, Zapier, Make (ex-Integromat), AWS, Azure et GCP.
62
+
63
+ Q : Puis-je essayer avant d'acheter ?
64
+ R : Oui, une période d'essai gratuite de 14 jours est disponible pour chaque produit, sans carte bancaire requise.
65
+
66
+ == Équipe de Direction ==
67
+
68
+ PDG (CEO) : Marie Dupont — Fondatrice de TechCorp Solutions, ancienne directrice technique chez Capgemini. Diplômée de l'École Polytechnique.
69
+
70
+ Directeur Technique (CTO) : Jean Martin — Responsable de l'architecture technique et de la R&D. Ex-ingénieur chez OVHcloud. Expert en systèmes distribués et sécurité cloud.
71
+
72
+ Directrice Commerciale (CCO) : Isabelle Leroy — En charge des ventes et des partenariats stratégiques. Ancienne responsable grands comptes chez Salesforce France.
73
+
74
+ Responsable Support : Sophie Bernard — Gère une équipe de 12 ingénieurs support. Certifiée ITIL v4.
75
+
76
+ Directeur Financier (CFO) : Thomas Roux — Supervise les finances et la conformité réglementaire. Expert-comptable diplômé.
77
+
78
+ == Bureaux ==
79
+
80
+ Siège social (Paris) :
81
+ 15 rue de la Paix, 75002 Paris
82
+ Tél : 01 23 45 67 89
83
+ Accès : Métro ligne 3 et 7 — Station Opéra
84
+
85
+ Bureau de Lyon :
86
+ 8 place Bellecour, 69002 Lyon
87
+ Tél : 04 56 78 90 12
88
+ Équipes : commerciaux et support régional Sud-Est
89
+
90
+ Bureau de Bordeaux :
91
+ 3 allée de Tourny, 33000 Bordeaux
92
+ Tél : 05 34 56 78 90
93
+ Équipes : développement produit et R&D
94
+
95
+ == Certifications et Conformité ==
96
+
97
+ TechCorp Solutions est engagée dans une démarche de conformité rigoureuse :
98
+ - Conformité RGPD (depuis mai 2018)
99
+ - Certification ISO 27001 en cours (audit prévu Q3 2026)
100
+ - Hébergement exclusivement en France (datacenters OVHcloud et Scaleway)
101
+ - Politique de divulgation responsable des vulnérabilités (CVD)
tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/data/politique.txt ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TechCorp Solutions — Politiques et Conditions
2
+
3
+ == Politique de Confidentialité (RGPD) ==
4
+
5
+ Dernière mise à jour : 1er janvier 2025
6
+
7
+ TechCorp Solutions (SAS au capital de 150 000 €, siège au 15 rue de la Paix, 75002 Paris, SIRET 823 456 789 00023) s'engage à protéger les données personnelles de ses utilisateurs conformément au Règlement Général sur la Protection des Données (RGPD — UE 2016/679) et à la loi Informatique et Libertés.
8
+
9
+ Données collectées :
10
+ Nous collectons uniquement les données nécessaires au fonctionnement de nos services : nom, prénom, adresse email professionnelle, numéro de téléphone (optionnel), données de facturation (sans conservation des coordonnées bancaires complètes), logs de connexion et d'utilisation.
11
+
12
+ Base légale du traitement :
13
+ Le traitement est fondé sur l'exécution du contrat (art. 6.1.b RGPD) pour les données nécessaires à la fourniture du service, et sur notre intérêt légitime (art. 6.1.f) pour les données d'amélioration du service.
14
+
15
+ Droits des personnes :
16
+ Conformément au RGPD, vous disposez d'un droit d'accès, de rectification, d'effacement, de portabilité et d'opposition. Pour exercer ces droits, contactez notre Délégué à la Protection des Données (DPO) : dpo@techcorp.fr. Nous répondrons dans un délai de 30 jours maximum.
17
+
18
+ == Politique de Rétention des Données ==
19
+
20
+ Comptes gratuits et périodes d'essai :
21
+ Les données sont conservées pendant 2 ans à compter de la dernière activité. Au-delà, elles sont supprimées définitivement ou anonymisées.
22
+
23
+ Comptes payants (CloudSync Pro, DataVault, APIConnect) :
24
+ Les données sont conservées pendant 7 ans à compter de la clôture du compte, conformément aux obligations légales comptables françaises (Code de commerce, art. L123-22). Cette durée de rétention de 7 ans s'applique également aux données de facturation.
25
+
26
+ Données de logs et monitoring :
27
+ Conservées 90 jours pour les plans gratuits, 1 an pour les plans payants, puis supprimées automatiquement.
28
+
29
+ Après la clôture d'un compte, un export complet des données peut être demandé dans les 30 jours suivant la résiliation.
30
+
31
+ == Politique de Cookies ==
32
+
33
+ Notre site web (techcorp.fr) et notre portail client utilisent des cookies. Vous pouvez gérer vos préférences via notre bandeau de consentement.
34
+
35
+ Cookies strictement nécessaires (pas de consentement requis) :
36
+ - Cookie de session (expiration : session)
37
+ - Cookie CSRF (sécurité des formulaires)
38
+ - Préférences de langue et d'accessibilité
39
+
40
+ Cookies analytiques (consentement requis) :
41
+ - Mesure d'audience anonymisée (Matomo hébergé en France, pas de partage tiers)
42
+ - Durée : 13 mois maximum
43
+
44
+ Nous n'utilisons aucun cookie publicitaire ni ne partageons vos données avec des régies publicitaires.
45
+
46
+ == Conditions Générales d'Utilisation ==
47
+
48
+ En accédant à nos services, vous acceptez les présentes CGU. Nos services sont destinés à un usage professionnel et sont réservés aux personnes majeures agissant au nom d'une entreprise.
49
+
50
+ Responsabilité : TechCorp Solutions s'engage à maintenir une disponibilité de 99,9 % (SLA). Notre responsabilité contractuelle est limitée au montant des sommes versées au cours des 12 derniers mois précédant le litige.
51
+
52
+ Résiliation : les abonnements peuvent être résiliés à tout moment depuis l'espace client. La résiliation prend effet à la fin de la période de facturation en cours.
53
+
54
+ Droit applicable : les présentes CGU sont soumises au droit français. Tout litige sera soumis à la compétence exclusive des tribunaux de Paris.
55
+
56
+ == Politique d'Utilisation Acceptable ==
57
+
58
+ Il est interdit d'utiliser nos services pour :
59
+ - Stocker ou transmettre des contenus illicites, diffamatoires ou portant atteinte aux droits de tiers
60
+ - Mener des activités de spam, phishing ou ingénierie sociale
61
+ - Tenter d'accéder sans autorisation à des systèmes tiers via nos API
62
+ - Surcharger intentionnellement nos infrastructures (attaques DoS)
63
+ - Revendre l'accès à nos services sans accord préalable écrit
64
+
65
+ Toute violation peut entraîner la suspension immédiate du compte sans remboursement.
66
+
67
+ == Politique de Sécurité ==
68
+
69
+ TechCorp Solutions applique une politique de sécurité rigoureuse, alignée sur les bonnes pratiques ISO 27001 (certification en cours d'obtention).
70
+
71
+ Mesures techniques :
72
+ - Chiffrement en transit : TLS 1.2 minimum, TLS 1.3 recommandé
73
+ - Chiffrement au repos : AES-256 pour toutes les données stockées
74
+ - Authentification multi-facteurs (MFA) disponible et recommandée
75
+ - Tests de pénétration annuels réalisés par un prestataire certifié PASSI
76
+ - Surveillance des accès et alertes en temps réel via SIEM interne
77
+
78
+ Signalement de vulnérabilités :
79
+ Nous encourageons la divulgation responsable. Si vous découvrez une faille de sécurité, contactez security@techcorp.fr. Nous nous engageons à accuser réception sous 48 heures et à corriger les vulnérabilités critiques sous 7 jours ouvrés.
80
+
81
+ Sous-traitants :
82
+ Nos hébergeurs (OVHcloud et Scaleway) sont des sous-traitants RGPD avec lesquels des DPA (Data Processing Agreements) sont en vigueur. Les données restent exclusivement hébergées en France.