Spaces:
Runtime error
Runtime error
Upload 251 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/.gitignore +12 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/EVALUATION_TP.md +73 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/PRESENTATION_ETAPES.md +610 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/README.md +146 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/.env.example +28 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/EVALUATION_TP_ETAPE_00.md +66 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/README.md +125 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/config.py +154 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/demo_connexion.py +119 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_00_moteur/requirements.txt +2 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/.env.example +9 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/01_chatbot_naif_no_message_stack.py +57 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/02_chatbot_naif.py +57 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/EVALUATION_TP_ETAPE_01.md +79 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/README.md +91 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/logs/prompts_demo.txt +82 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_01_banane/requirements.txt +2 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/.env.example +9 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/01_mesurer_kpis.py +128 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/02_kpis_comparatif.py +142 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/EVALUATION_TP_ETAPE_02.md +79 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/README.md +46 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/logs/benchmark_results.json +92 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/prompts_benchmark.txt +8 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_02_kpis/requirements.txt +3 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/.env.example +3 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/01_memory_window.py +103 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/02_memory_summary.py +103 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/EVALUATION_TP_ETAPE_03.md +81 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/README.md +44 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/01_memory_window.log +81 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/02_memory_summary.log +237 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/logs/limite_memort_summary.txt +17 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_03_memoire/requirements.txt +2 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/.env.example +4 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/EVALUATION_TP_ETAPE_04.md +79 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/README.md +57 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/explorer_db.py +135 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/historique.json +78 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_json_chatgpt.log +88 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/logs/persistance_sqlite_mistral_localnetwork.log +137 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_json.py +83 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/persistance_sqlite.py +149 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_04_persistance/requirements.txt +2 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/.env.example +4 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/EVALUATION_TP_ETAPE_05.md +82 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/README.md +53 -0
- tp_ia_chatbot-main/tp_ia_chatbot-main/etape_05_rag/data/entreprise.txt +101 -0
- 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.
|