Tu connais cette sensation ? Tu es SRE, il est 3h du matin, un service est down, et tu dois retrouver LA procédure de rollback dans une doc de 500 pages... Tu scrolles, tu cherches, tu maudis le collègue qui a écrit "voir section précédente" sans dire laquelle. 😩
Plot twist : Et si je te disais qu'on peut transformer cette galère en discussion fluide avec un chatbot IA qui connaît toute ta documentation par cœur ? Chez nous, avec 50 SRE qui jonglent entre incidents et maintenance, l'implémentation d'un système RAG a divisé par 3 le temps de recherche d'infos critiques.
Dans ce tutoriel RAG, tu vas apprendre à implémenter un système complet de Retrieval-Augmented Generation sur une documentation MkDocs Material en utilisant LangChain, ChromaDB et FastAPI. Ce guide étape par étape te montre comment construire un assistant documentaire intelligent qui fournit des réponses précises avec citations des sources.
Picture this : 50 SRE, 7 équipes, une documentation MkDocs Material avec :
Le quotidien de nos équipes :
# Scénario classique à 3h du matin
1. Incident détecté → Service X down
2. Recherche procédure → 15 minutes de navigation
3. "Ah non, c'est pas la bonne version"
4. Re-recherche → 10 minutes de plus
5. Procédure
Les stats qui piquent :
Insight clé : Le problème n'était pas la qualité de notre doc, mais son accessibilité cognitive !
Bien que MkDocs Material soit excellent, la recherche par mots-clés traditionnelle a des limites importantes qu'une implémentation RAG peut résoudre :
❌ Recherche par mots-clés uniquement (pas de compréhension sémantique)
❌ Pas de compréhension du contexte entre documents
❌ Résultats parfois trop nombreux ou hors-sujet
❌ Impossible de poser des questions en langage naturel
❌ Pas d'agrégation d'infos cross-documents
❌ Recherche limitée aux titres et premiers paragraphes
❌ Aucune notion de priorité ou d'urgenceLa communauté demande depuis longtemps l'amélioration de la recherche sémantique, comme en témoigne cette issue GitHub qui reste ouverte depuis plusieurs années.
Comparaison avec d'autres solutions :
| Solution | Recherche sémantique | IA conversationnelle | Intégration existante |
|---|---|---|---|
| MkDocs Material natif | ❌ | ❌ | ✅ |
| Algolia DocSearch | ⚠️ Limitée | ❌ | ⚠️ Setup complexe |
| RAG + LLM | ✅ | ✅ | ✅ |
| GitBook | ✅ | ⚠️ Basique | ❌ Migration requise |
Exemple concret :
Voici comment on a construit notre assistant documentaire IA avec une stack RAG complète :
# Stack technique complète pour RAG documentaire
TECH_STACK = {
"backend": "FastAPI", # API REST rapide pour endpoints RAG
"embeddings": "OpenAI text-embedding-3-small", # Embeddings vectoriels (512 dimensions, $0.02/1M tokens)
"vector_db": "ChromaDB", # Base de données vectorielle pour recherche sémantique (alternative: Pinecone, Weaviate)
"llm": "GPT-4o-mini", # LLM pour génération de réponses ($0.15/1M input tokens)
"framework": "LangChain", # Framework d'orchestration RAG
"docs_source": "MkDocs Material",
"deployment": "Docker + K8s",
"monitoring": "Prometheus + Grafana", # Suivi métriques RAG
"cache": "Redis", # Cache sémantique pour performance
}Workflow du RAG :


Le génie de notre approche : pas besoin de modifier MkDocs ! Ce tutoriel RAG te montre comment aspirer le contenu existant et construire un index de base de données vectorielle en parallèle, permettant la recherche sémantique sans changer ta configuration de documentation actuelle.
# Configuration de base pour l'indexation MkDocs
MKDOCS_CONFIG = {
"docs_path": "/app/docs",
"base_url": "https://docs.company.com",
"chunk_size": 1000, # Optimal pour les runbooks
"chunk_overlap": 200, # Maintient la cohérence
"file_types": [".md"],
"exclude_patterns": ["temp/", "drafts/"]
}Voici l'implémentation FastAPI complète pour notre système RAG avec embeddings OpenAI et streaming :
@app.post("/ask")
def ask_question_stream(request: QuestionRequest):
question = request.question
model = rag.llm
# Configuration pour l'URL de base
BASE_DOCS_URL = "https://docs.company.com"
# Retriever optimisé pour les docs techniques
retriever = rag.vector_store.as_retriever(
search_type="mmr", # Maximum Marginal Relevance
search_kwargs={
"k": 8, # 8 chunks pour du contexte riche
"fetch_k": 20, # Pool initial plus large
"lambda_mult": 0.7 # Balance pertinence/diversité
}
)
retrieved_docs =
Les améliorations clés qu'on a ajoutées :
🎯 Retrieval amélioré :
💡 Pro tip : Ces paramètres ont été ajustés après 2 semaines de tests avec nos équipes SRE !
import os
import yaml
from pathlib import Path
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Indexeur de documents basé sur LangChain pour implémentation RAG
class MkDocsIndexer:
def __init__(self, docs_path: str, base_url: str):
self.docs_path = Path(docs_path)
self.base_url = base_url
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=[
# Classification automatique par type de contenu
DOC_TYPES_CONFIG = {
"runbook": {
"weight": 1.5, # Priorité élevée pour incidents
"keywords": ["incident", "rollback", "emergency", "critical"]
},
"api_doc": {
"weight": 1.2,
"keywords": ["endpoint", "authentication", "request", "response"]
},
"troubleshooting": {
"weight": 1.4, # Priorité élevée pour debug
"keywords": ["error", "debug", "logs", "diagnostic"]
},
"general": {
"weight"
# docker-compose.yml pour dev local
version: '3.8'
services:
rag-api:
build: .
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- DOCS_PATH=/app/docs
- BASE_DOCS_URL=https://docs.company.com
volumes:
- ./docs:/app/docs:ro
- ./vector_db:/app/vector_db
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# Frontend simple pour les tests
rag-frontend:
build
// Component React simple mais efficace
function RAGChat() {
const [question, setQuestion] = useState('');
const [response, setResponse] = useState('');
const [loading, setLoading] = useState(false);
const askQuestion = async () => {
setLoading(true);
setResponse('');
try {
const response = await fetch('/api/ask', {
method: 'POST',
🎯 Chunking et indexation
# ✅ DO : Respecter la structure logique des docs
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # Optimal pour docs techniques
chunk_overlap=200, # Maintient le contexte
separators=[
"\n## ", # Sections principales d'abord
"\n### ", # Puis sous-sections
"\n\n", # Paragraphes
"\n", " ", "" # Enfin mots/caractères
]
)
# ✅ DO : Enrichir les métadonnées
metadata = {
"doc_type": "runbook", # Classification
"urgency": "critical"
🔍 Configuration de retrieval intelligente
# ✅ DO : Ajuster selon le type de question
def get_retriever_config(question_type):
if "emergency" in question.lower() or "incident" in question.lower():
return {"k": 12, "doc_types": ["runbook", "troubleshooting"]}
elif "api" in question.lower():
return {"k": 6, "doc_types": ["api_doc"]}
else:
return {"k": 8, "doc_types": "all"}🧠 Prompt engineering adaptatif
# ✅ DO : Adapter le prompt selon le contexte SRE
def build_system_prompt(urgency_level, doc_types):
base_prompt = "You are a specialized SRE assistant."
if urgency_level == "critical":
return base_prompt + """
🚨 CRITICAL INCIDENT MODE:
- Prioritize immediate actionable steps
- Include rollback procedures when relevant
- Mention escalation contacts if available
- Be concise but complete
"""
elif "api" in doc_types:
return base_prompt + """
📡 API DOCUMENTATION MODE:
- Provide exact endpoint syntax
- Include authentication details
- Show request/response examples
- Mention rate limits and error codes
"""
return base_prompt + "Standard documentation assistance mode."📈 Monitoring et métriques
# ✅ DO : Tracker les métriques importantes
METRICS_TO_TRACK = {
"usage": ["questions_per_day", "unique_users", "peak_hours"],
"quality": ["avg_response_time", "user_satisfaction", "sources_clicked"],
"content": ["most_asked_topics", "unused_docs", "missing_answers"],
"performance": ["search_latency", "llm_response_time", "error_rate"]
}
# ✅ DO : Logs structurés pour analytics
logger.info("rag_query", extra={
"question": hash(question), # Privacy-safe
"doc_count": len(retrieved_docs),
"response_time": response_time,
"user_id": user_id,
🔒 Sécurité et confidentialité
# ✅ DO : Implémenter des guardrails
def validate_question(question: str) -> bool:
"""Vérifie que la question est appropriée"""
# Pas de données sensibles dans les logs
if any(pattern in question.lower() for pattern in
["password", "secret", "token", "key"]):
return False
# Limite de taille pour éviter l'abus
if len(question) > 500:
return False
return True
# ✅ DO : Anonymiser les logs
def sanitize_for_logs(text: str) -> str:
"""Supprime les infos sensibles des logs"""
patterns
❌ DON'T : Négliger la fraîcheur des données
# ❌ DON'T : Index statique sans mise à jour
# Problème : Docs obsolètes = mauvais conseils en incident !
# ✅ DO : Système de mise à jour automatique
def schedule_index_updates():
"""Met à jour l'index quand les docs changent"""
# Webhook depuis Git pour déclenchements temps réel
@app.post("/webhook/docs-updated")
def handle_docs_update():
asyncio.create_task(reindex_documents())
# Backup : scan périodique des modifications
scheduler.add_job(
func=check_for_updates,
trigger="interval",
minutes=30,
id='docs_freshness_check'
)❌ DON'T : Ignorer le contexte utilisateur
# ❌ DON'T : Réponse identique pour tous
# Problème : Junior vs Senior SRE = besoins différents
# ✅ DO : Adapter selon l'utilisateur
def personalize_response(user_profile, question, base_answer):
if user_profile.experience_level == "junior":
return add_explanatory_context(base_answer)
elif user_profile.team == "security":
return emphasize_security_aspects(base_answer)
elif user_profile.on_call_status:
return prioritize_quick_actions(base_answer)
return base_answer❌ DON'T : Faire confiance aveuglément au LLM
# ❌ DON'T : Pas de validation des réponses critiques
# Problème : Hallucination = incident aggravé !
# ✅ DO : Validation pour procédures critiques
def validate_critical_response(question, response, doc_sources):
"""Valide les réponses pour procédures sensibles"""
critical_keywords = ["delete", "drop", "destroy", "remove", "rollback"]
if any(keyword in question.lower() for keyword in critical_keywords):
# Exige une source explicite et récente
if not doc_sources or not has_recent_source(doc_sources):
return add_validation_warning(response)
# Double-check avec pattern matching
if not validate_procedure_steps(response):
❌ DON'T : Oublier la performance en production
# ❌ DON'T : Pas de mise en cache intelligente
# Problème : Questions répétitives = coûts OpenAI explosés
# ✅ DO : Cache sémantique avec TTL adaptatif
from functools import lru_cache
import hashlib
class SemanticCache:
def __init__(self):
self.cache = {}
self.similarity_threshold = 0.92
def get_cache_key(self, question: str) -> str:
"""Clé basée sur l'embedding de la question"""
embedding = get_question_embedding(question)
return hashlib.md5(str(embedding).encode()).hexdigest()
def should_cache_response(self,
❌ DON'T : Négliger l'expérience utilisateur
# ❌ DON'T : Réponses trop techniques pour tous
# Problème : Manager qui pose une question = réponse illisible
# ✅ DO : Adaptation automatique du niveau
def adjust_technical_level(response: str, user_role: str) -> str:
"""Adapte le niveau technique selon l'utilisateur"""
if user_role in ["manager", "product", "business"]:
return simplify_technical_terms(response)
elif user_role in ["intern", "junior"]:
return add_educational_context(response)
elif user_role in ["senior", "staff", "principal"]:
return add_advanced_details(response)
return response
def
# Métriques avant/après RAG
RESULTS = {
"temps_recherche_moyen": {
"avant": "18 minutes/jour/SRE",
"après": "6 minutes/jour/SRE",
"amélioration": "-67%"
},
"résolution_incidents": {
"avant": "MTTR = 23 minutes",
"après": "MTTR = 16 minutes",
"amélioration": "-30%"
},
"satisfaction_équipe": {
"avant": "6.2/10",
"après": "8.7/10",
"amélioration": "+40%"
}
}Top 5 des questions les plus posées au RAG :
Au final, déployer un système RAG sur une documentation MkDocs Material, c'est un peu comme avoir embauché un SRE senior qui connaît toutes les procédures par cœur, ne dort jamais, et répond instantanément en situation d'urgence. Cette solution de recherche sémantique transforme l'accès aux connaissances.
Les bénéfices concrets de notre implémentation RAG :
Le plus beau dans tout ça ? Le système RAG s'améliore automatiquement grâce à la récupération intelligente de LangChain. Plus tes équipes posent de questions, plus la base de données vectorielle devient efficace pour trouver le contenu pertinent.
Prêt à implémenter RAG pour ta documentation ? Ce tutoriel te donne tout ce qu'il faut pour construire un assistant documentaire IA avec FastAPI, ChromaDB et OpenAI. Ton "3h du matin future-self" te remerciera ! 😄
🔥 Challenge bonus : Mesure le temps que tes équipes passent à chercher de l'info cette semaine. Puis remesure dans un mois après avoir implémenté ton RAG. Les résultats vont te surprendre !
Merci de me suivre dans cette aventure ! 🚀
Cet article a été écrit avec ❤️ pour la communauté DevOps.