Krebs/claude-courses-assistant
Course RAG Chatbot
Assistant conversationnel sur des cours IA DeepLearning.ai — réponses sourcées, évaluation continue, stack d'observabilité complète.
Ce que ça fait
Tu poses une question en langage naturel sur des cours IA (Anthropic Claude, MCP, RAG, Agent Skills, Claude Code...). Le système retrouve les passages pertinents dans les transcriptions via recherche vectorielle multilingue, génère une réponse sourcée en streaming, et évalue la fidélité en temps réel.
"Comment fonctionne le tool use dans Claude ?"
→ recherche vectorielle multilingue (paraphrase-multilingual-MiniLM-L12-v2)
→ Claude Haiku synthétise avec les sources (tool use, max 2 rounds)
→ RAGAS faithfulness : 0.93 ✅ → auto-tune MAX_RESULTSNote de révision — 2026-07-19
Cette section rectifie ce que le reste de ce document (et CLAUDE.md) décrit et que le code ne fait pas. Établi par audit direct du code et interception du payload API réellement envoyé à Anthropic — pas par lecture seule.
Corpus
11 transcriptions de cours DeepLearning.AI (anglais) sur Claude, MCP, RAG et agents. Transcriptions autonomes, sans référence croisée : aucune question multi-hop réelle n'y est testable.
Architecture réelle
- Le tool use Anthropic n'est pas exercé.
toolsettool_choicesont absents du payload API (vérifié par interception directe declient.messages.create). - Le retrieval est déclenché par un appel Python direct (
search_tool.execute) avant l'appel modèle. Architecture réelle : retrieve-then-stuff, un seul appel LLM par requête. Le modèle ne décide jamais de chercher. - Le contexte est concaténé dans le message utilisateur, préfixé
"Course content:". CourseSearchTooln'est jamais instancié : code mort, présent uniquement dans sa définition et la documentation. Le tool enregistré estHybridSearchTool.
Retrieval
- Chemin effectif : BM25 + similarité euclidienne exacte (
np.linalg.normsur tout le corpus) + reranking cross-encoder surHYBRID_CANDIDATES=20. - Ce n'est pas l'index HNSW de ChromaDB, sollicité uniquement par
VectorStore.search()et_resolve_course_name(), tous deux hors du chemin exécuté. - À cette échelle, la recherche exacte est le choix correct : rappel 100 %, pas d'approximation. HNSW échange du rappel contre de la vitesse — sans intérêt ici.
- Limite : le corpus est encodé intégralement au premier chargement et après chaque
reset_cache(). Ce coût d'initialisation — et non la recherche, vectorisée et O(n×384) — est le goulot. Seuil de bascule non mesuré.
Incohérences identifiées, non corrigées
- Le
SYSTEM_PROMPTannonce au modèle jusqu'à 2 recherches séquentielles.MAX_TOOL_ROUNDS=2et_handle_tool_executionexistent. Mais le tool use n'étant pas câblé, cette capacité est structurellement inaccessible. - Le diagramme de
CLAUDE.md(AIGenerator ← tool use → CourseSearchTool) ne reflète plus le code.
Robustesse — corrections apportées (commit 4d41034)
- 9 blocs
except Exceptiondevector_store.pyavalaient leurs exceptions avec unprint(). Convertis en logs avec stack trace et contexte, ERROR ou WARNING selon l'impact réel. Flux de contrôle inchangé, mêmes valeurs de retour. - Un seul est sur le chemin exercé (
get_all_content) et c'était le pire : une exception y produisait un corpus vide et le système répondait"No relevant content found"— indiscernable d'une base légitimement vide. Un flagcorpus_load_degradedsurHybridRetrieverdistingue désormais les deux cas : corpus vide avec des cours indexés (get_course_count() > 0) déclenche un message dédié côté utilisateur et un log ERROR côté serveur ; corpus légitimement vide ne change pas de comportement.
Fonctionnalités
RAG Pipeline — améliorations progressives
Observabilité complète
- RAGAS faithfulness calculé en arrière-plan sur chaque requête, badge coloré inline dans le chat (vert ≥80%, orange ≥60%, rouge <60%)
- Prometheus scrape
/metricstoutes les 15s — distributions de scores, latences - Grafana dashboards préconfigurés (port 3001)
- Phoenix OTEL traces de chaque appel LLM Anthropic (port 6006)
Guardrails qualité
- INDEX_VERSION — version de schéma persistée dans ChromaDB ; bump automatique du ré-index si la version change (chunking ou docs mis à jour)
- Scope restriction — le system prompt refuse les questions hors cours et les tentatives de prompt injection
- Pre-indexing build-time — ChromaDB baked dans l'image Docker, démarrage instantané (0 indexation à chaud)
UX
- Streaming SSE — les tokens arrivent au fur et à mesure
- Bouton Copier sur chaque réponse (clipboard → checkmark)
- Thumbs up/down sur chaque réponse → feedback persisté en JSON +
/api/feedback/summary - Toggle dark/light mode
- Historique de session
Cours indexés (10 cours DeepLearning.ai / Anthropic)
- Building Towards Computer Use with Anthropic
- MCP: Build Rich-Context AI Apps with Anthropic
- Prompt Engineering with Anthropic Claude
- Tool Use with Claude
- Agent Skills with Anthropic
- Claude Code: A Highly Agentic Coding Assistant
- Agent Skills Guide
- RAG en Production
- Bases de Récupération d'Informations et de Recherche (TF-IDF, BM25, RRF, embeddings)
- Cours sur le RAG — DeepLearning.ai (HNSW, Weaviate, chunking, re-ranking, ColBERT)
Stack
Frontend Vanilla JS + SSE streaming
Backend FastAPI · uvicorn · Python 3.13
LLM Anthropic Claude Haiku (tool use, max 2 rounds)
Fallback Ollama llama3.2:1b (inférence locale)
Vector DB ChromaDB (pré-indexé au build, persisté sur volume Docker)
Embeddings paraphrase-multilingual-MiniLM-L12-v2 (FR + EN)
Evals RAGAS faithfulness (LangchainLLMWrapper + ChatAnthropic)
Observ. Prometheus · Grafana · Arize Phoenix (OTEL)
Deploy HF Spaces (Docker) · Docker Compose local (6 services)Architecture
Utilisateur
│
▼
FastAPI /api/query/stream
│
├─ RAGSystem.query()
│ ├─ AIGenerator (tool use, max 2 rounds)
│ │ └─ CourseSearchTool → ChromaDB
│ └─ SessionManager (historique)
│
├─ ragas_evaluator ← async, timeout 60s, auto-tune MAX_RESULTS
│ └─ Prometheus metrics
│
└─ Phoenix OTEL tracesDéploiement
HF Spaces (production)
Le Space est déployé sur HuggingFace Spaces — ChromaDB pré-indexé dans l'image, démarrage en ~30s.
Secret requis : dans les settings du Space → Secrets → ajouter ANTHROPIC_API_KEY = sk-ant-... Sans cette clé, l'app démarre mais toutes les requêtes renvoient une erreur 503.git push origin main && git push hf mainDocker local (dev complet avec observabilité)
cp .env.example .env
# Ajouter ANTHROPIC_API_KEY dans .env
docker compose up --build # premier lancement (~2 min, pull Ollama)
docker compose up # lancements suivantsLocal sans Docker (dev rapide)
uv sync
ollama serve # terminal séparé
cd backend && uv run uvicorn app:app --reload --port 8000Ajouter un cours
Déposer un .txt dans docs/ au format suivant, bumper INDEX_VERSION dans config.py, et redémarrer — indexation automatique et idempotente :
Course Title: <titre>
Course Instructor: <nom>
### Lesson 1: <titre>
<contenu>...
### Lesson 2: <titre>
<contenu>...Tests
cd backend && uv run pytest tests/ -v
# Tests comportementaux sur AIGenerator (multi-round tool calling)