RAG pour docs infra : interroger vos runbooks avec retrieval
À la fin de ce tutoriel RAG docs infra, vous saurez monter un mini pipeline Retrieval-Augmented Generation sur des runbooks Markdown : ingestion, chunking, index vectoriel léger (TF‑IDF / cosine), requête top‑k avec citations de sources, et garde-fous pour ne jamais indexer de secrets — lab factice sous
/tmp/lab-rag-docs-infra.Niveau : Intermédiaire · Temps estimé : 50–60 min · Versions testées : Ubuntu 22.04 / 24.04, Python 3.10+ · Dernière vérification : 2026-09-11
Prérequis
- Hub IA pour DevOps : parcours 2026
- Voisins utiles : MCP servers DevOps, Agent workflows, ChatGPT runbooks DevOps
- Python 3.10+ :
python3 --version - Lab entièrement dans
/tmp/lab-rag-docs-infra; aucun token, kubeconfig ou.envréel - Paquets lab :
pip install --user scikit-learn(TF‑IDF + cosine) — pas de modèle multi‑Go à télécharger
Coût : 0 €. En prod : vrais embeddings + store Chroma / LanceDB / pgvector.
Ce que nous allons construire
/tmp/lab-rag-docs-infra
├── docs/
│ ├── runbook-nginx-restart.md
│ ├── runbook-disk-full.md
│ ├── runbook-k8s-scale.md
│ └── adr-observability.md
├── rag_lab.py (ingest + query + cite)
├── index.npz (vecteurs + métadonnées)
└── .gitignore-like (exclure secrets)
→ chunking Markdown par titres
→ TF-IDF + similarité cosine (proxy embeddings)
→ top-k + citations chemin/extrait
→ filtre anti-secrets · quiz · FAQ
(Schéma à remplacer par une image locale Excalidraw / draw.io, alt : « RAG docs infra : runbooks Markdown, chunking, index vectoriel, requête citée ».)
Objectif : question ops → bonne page + extrait (pas d’hallucination). Le RAG apporte le contexte hors fenêtre de prompt.
Pourquoi RAG pour la doc infra ?
Coller tout le wiki dans un prompt échoue : limite de contexte, coût, pas de provenance. Pattern retrieve puis generate : indexer des chunks → top‑k → LLM avec consigne de citer les fichiers. Ici on valide surtout l’index + retrieval ; le LLM peut venir ensuite.
Étape 1 — Lab : runbooks Markdown factices
mkdir -p /tmp/lab-rag-docs-infra/docs && cd /tmp/lab-rag-docs-infra
cat > docs/runbook-nginx-restart.md << 'EOF'
# Runbook — Redémarrer nginx (lab)
### Symptômes
Site 502 / connexion refusée sur le port 80.
### Diagnostic
```bash
systemctl is-active nginx
journalctl -u nginx -n 50 --no-pager
Remédiation
- Vérifier la config :
nginx -t - Recharger :
systemctl reload nginx - Si échec :
systemctl restart nginx - Smoke :
curl -I http://127.0.0.1/
Escalade
Si nginx -t échoue, corriger le vhost avant tout restart.
EOF
cat > docs/runbook-disk-full.md << ‘EOF’
Runbook — Disque plein (lab)
Symptômes
Écritures en échec, alerts disk > 90 %.
Diagnostic
df -h /
du -xh /var --max-depth=2 | sort -h | tail -n 20
journalctl --disk-usage
Remédiation
- Purger logs rotatés sous
/var/log(audit avant) docker system dfpuis nettoyage images inutiles- Étendre volume LVM / cloud si structurel
Ne pas faire
Supprimer des données applicatives sans ticket ni backup.
EOF
cat > docs/runbook-k8s-scale.md << ‘EOF’
Runbook — Scale Deployment Kubernetes (lab)
Symptômes
Latence haute, HPA saturé, files d’attente.
Diagnostic
kubectl -n demo get deploy api
kubectl -n demo get hpa
kubectl -n demo top pods
Remédiation
kubectl -n demo scale deploy/api --replicas=5
kubectl -n demo rollout status deploy/api
Vérification
Requêtes p95 en baisse ; pods Ready = replicas.
EOF
cat > docs/adr-observability.md << ‘EOF’
ADR — Observabilité minimale (lab)
Contexte
Besoin de métriques, logs et traces pour les services demo.
Décision
Prometheus + Grafana ; logs journald / Loki ; OTel optionnel.
Conséquences
Services avec /metrics ; runbooks citent les dashboards avant escalade.
EOF
find docs -type f -name ‘*.md’ | wc -l
Vous devez voir `4`.
## Étape 2 — Script `rag_lab.py` : chunk, index, query
Le lab utilise **TF‑IDF** (scikit-learn) comme **proxy d’embeddings** (même schéma retrieve + cite, sans modèle multi‑Go). En prod : embeddings réels + Chroma / LanceDB / pgvector.
```bash
cd /tmp/lab-rag-docs-infra
pip install --user -q scikit-learn
cat > rag_lab.py << 'PY'
#!/usr/bin/env python3
"""Mini RAG lab — chunk Markdown, TF-IDF index, query + citations."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
ROOT = Path(__file__).resolve().parent
DOCS = ROOT / "docs"
INDEX = ROOT / "index.npz"
META = ROOT / "index_meta.json"
SECRET_PAT = re.compile(
r"(.env|kubeconfig|.pem|.key|id_rsa|secret|password|token)",
re.I,
)
def should_skip(path: Path) -> bool:
return bool(SECRET_PAT.search(str(path)))
def chunk_markdown(text: str, source: str, max_chars: int = 700) -> list[dict]:
"""Découpe par titres #/## puis fusionne si trop long."""
parts = re.split(r"(?=^#{1,3} )", text, flags=re.M)
chunks: list[dict] = []
buf = ""
for part in parts:
part = part.strip()
if not part:
continue
if len(buf) + len(part) + 1 <= max_chars:
buf = f"{buf}nn{part}".strip() if buf else part
else:
if buf:
chunks.append({"source": source, "text": buf})
buf = part if len(part) <= max_chars else part[:max_chars]
if buf:
chunks.append({"source": source, "text": buf})
return chunks
def ingest() -> None:
all_chunks: list[dict] = []
for path in sorted(DOCS.rglob("*.md")):
if should_skip(path):
print(f"skip secret-like: {path}")
continue
rel = str(path.relative_to(ROOT))
all_chunks.extend(chunk_markdown(path.read_text(encoding="utf-8"), rel))
if not all_chunks:
raise SystemExit("aucun chunk — vérifiez docs/")
texts = [c["text"] for c in all_chunks]
vec = TfidfVectorizer(ngram_range=(1, 2), min_df=1)
X = vec.fit_transform(texts)
np.savez_compressed(
INDEX,
data=X.data,
indices=X.indices,
indptr=X.indptr,
shape=X.shape,
vocabulary=np.array(list(vec.vocabulary_.items()), dtype=object),
idf=vec.idf_,
)
META.write_text(json.dumps(all_chunks, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"index OK — {len(all_chunks)} chunks → {INDEX.name}")
def load_matrix():
z = np.load(INDEX, allow_pickle=True)
from scipy.sparse import csr_matrix
X = csr_matrix(
(z["data"], z["indices"], z["indptr"]), shape=tuple(z["shape"])
)
vocab = {str(k): int(v) for k, v in z["vocabulary"]}
idf = z["idf"]
chunks = json.loads(META.read_text(encoding="utf-8"))
return X, vocab, idf, chunks
def query(q: str, k: int = 3) -> None:
from scipy.sparse import csr_matrix
X, vocab, idf, chunks = load_matrix()
# vectorize query with same vocab/idf
analyzer = TfidfVectorizer(ngram_range=(1, 2)).build_analyzer()
toks = analyzer(q)
row = np.zeros(X.shape[1], dtype=float)
tf: dict[int, int] = {}
for t in toks:
if t in vocab:
j = vocab[t]
tf[j] = tf.get(j, 0) + 1
for j, c in tf.items():
row[j] = c * idf[j]
if row.sum() == 0:
print("aucun terme indexé dans la requête — reformulez")
return
qv = csr_matrix(row)
sims = cosine_similarity(qv, X).ravel()
order = np.argsort(-sims)[:k]
print(f"Q: {q}n")
for rank, i in enumerate(order, 1):
c = chunks[int(i)]
excerpt = c["text"].replace("n", " ")[:220]
print(f"{rank}. score={sims[i]:.3f} source={c['source']}")
print(f" {excerpt}…n")
def main() -> None:
p = argparse.ArgumentParser(description="RAG lab docs infra")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("ingest")
pq = sub.add_parser("query")
pq.add_argument("text")
pq.add_argument("-k", type=int, default=3)
args = p.parse_args()
if args.cmd == "ingest":
ingest()
else:
query(args.text, k=args.k)
if __name__ == "__main__":
main()
PY
Étape 3 — Ingest puis requêtes avec citations
cd /tmp/lab-rag-docs-infra
python3 rag_lab.py ingest
python3 rag_lab.py query "disque plein logs docker"
python3 rag_lab.py query "scale deployment kubernetes latence" -k 2
python3 rag_lab.py query "redémarrer nginx 502"
Validé si : top‑k pointe le bon fichier (disk-full, k8s-scale, nginx-restart) avec un extrait utilisable. Ensuite, prompt LLM du type : « réponds uniquement avec ces extraits et cite le chemin ; sinon dis que l’info manque ».
Étape 4 — Garde-fous secrets et qualité
# Simuler un fichier dangereux — doit être ignoré à l'ingest
echo 'API_TOKEN=lab-fake-not-real' > /tmp/lab-rag-docs-infra/docs/secrets.env.md
# Renommer pour matcher le filtre (extension .env dans le nom)
mv /tmp/lab-rag-docs-infra/docs/secrets.env.md
/tmp/lab-rag-docs-infra/docs/fake.env.notes.md
python3 rag_lab.py ingest # doit afficher skip secret-like
rm -f /tmp/lab-rag-docs-infra/docs/fake.env.notes.md
| Contrôle | Attendu |
|---|---|
| Question connue | Top‑1 = bon runbook |
| Hors corpus | Scores bas ; LLM dit « pas dans l’index » |
| Chunk trop gros | Réduire max_chars ; respecter ## |
| Secrets | Filtre path + deny-list CI |
Prod : embeddings réels, store Chroma / LanceDB / pgvector, corpus Git, expo MCP (MCP DevOps).
Nettoyage
rm -rf /tmp/lab-rag-docs-infra
echo "lab RAG docs infra nettoyé — OK"
Erreurs fréquentes
| Symptôme | Cause | Correction |
|---|---|---|
| Mauvais runbook en top‑1 | Chunks trop larges ou titres absents | Chunker sur ## ; baisser max_chars |
| Scores tous ~0 | Vocabulaire requête hors index | Reformuler avec termes du doc ; n‑grams |
ModuleNotFoundError: sklearn |
Paquet manquant | pip install --user scikit-learn |
| Hallucination LLM malgré RAG | Prompt sans contrainte de citation | Exiger « uniquement les extraits » + sources |
| Secret indexé | Fichier mal nommé / filtre faible | Patterns + deny-list CI ; jamais .env dans docs/ |
| Index obsolète | Docs modifiés sans re-ingest | rag_lab.py ingest dans le pipeline Git |
Quiz (4 questions)
1. À quoi sert le retrieval dans un RAG docs infra ?
– A. Compiler le noyau Linux
– B. Sélectionner les chunks de runbooks les plus pertinents avant génération
– C. Remplacer Git
2. Pourquoi citer le chemin source dans la réponse ?
– A. Pour décorer le terminal
– B. Pour permettre vérification humaine et réduire les hallucinations
– C. Pour compresser l’index
3. Que faire d’un fichier kubeconfig dans le dossier docs ?
– A. L’indexer pour « plus de contexte »
– B. L’exclure (filtre secrets) et le stocker hors corpus
– C. Le coller dans le prompt système
4. TF‑IDF dans ce lab remplace quoi pédagogiquement ?
– A. Kubernetes
– B. Les embeddings d’un vrai pipeline RAG (même schéma retrieve)
– C. systemd
Réponses : 1‑B · 2‑B · 3‑B · 4‑B
Compléments opérationnels
Mesure et amélioration continue
Définissez trois indicateurs simples : taux de DoD vert du premier coup, nombre de refus utiles par session, coût tokens par livrable Git accepté. Revoyez-les toutes les deux semaines. Si le taux DoD stagne sous 50 %, le problème est le brief ou le corpus de rules, pas « le besoin d’un modèle plus cher ». Si le coût explose, imposez un modèle fast par défaut et réservez strong aux plans. Publiez un mini tableau dans le dépôt docs pour rendre le coût visible à l’équipe, comme n’importe quelle ligne cloud.
Standard de brief réutilisable
Pour rag-docs-infra, adoptez un brief unique partagé par l’équipe. Il contient toujours cinq champs : objectif mesurable, contexte borné (lab/staging), liste blanche de fichiers ou ressources, interdits explicites (secrets, réseau, prod), et definition of done exécutable (commande de test ou checklist). Sans ces champs, l’outil improvisera — parfois avec élégance, souvent hors périmètre.
Stockez les briefs gagnants sous briefs/rag-docs-infra/ dans un dépôt de documentation interne. Chaque brief cite le commit de référence et le modèle utilisé. Au troisième succès comparable, ouvrez une PR qui transforme le brief en rule, Skill ou slash command. Voir ai-skills-concept, claude-skills-instructions et evaluer-qualite-skill.
Revue de diff comme compétence P0
La vitesse d’un agent n’a aucune valeur si personne ne sait lire un diff. Instituez une règle simple : aucun apply massif sans parcours hunk par hunk. Posez-vous trois questions : ce changement est-il demandé ? est-il testé ? est-il réversible ? Si une réponse est non, refusez ou exigez un plan plus petit.
En atelier, faites commenter un diff à voix haute pendant cinq minutes. Les juniors apprennent ainsi le vocabulaire du refus. Reliez cette pratique à ai-code-review-pr et aux workflows ai-agent-workflows.
Sécurité continue
Les surfaces rag-docs-infra multiplient les occasions d’injection et de fuite. Traitez tickets, README upstream, logs et ressources MCP comme des données non fiables. Séparez instructions et données. Allowlist tools. Confirmation humaine avant mutation. Guide : ai-prompt-injection-securite, mcp-servers-devops, claude-code-mcp.
Secrets : variables d’environnement de session, gestionnaire d’équipe, rotation, jamais de commit. Si un secret fuit dans un chat vendor, supposez-le compromis et rotatez.
Coûts, modèles et mode dégradé
Documentez un mapping fast/strong et un soft-cap mensuel dans la stack perso. OpenRouter peut aider à basculer — openrouter-multi-modeles. Le local (ollama-llm-locaux + continue-dev) sert de mode dégradé ou de bac à sable confidentialité.
Surveillez les changements silencieux de modèle par défaut : une régression de qualité est souvent un routing vendor, pas « l’équipe qui a oublié comment prompter ».
Ancrage parcours
rag-docs-infra n’est qu’une étape du parcours IA DevOps 2026. Tronc commun d’abord (prompts, injection, skills), puis surface spécialisée, puis industrialisation (routines, MCP, RAG). Hub de navigation : /ia/. Comparatifs utiles : claude-vs-gpt-code, codex-vs-claude-code, claude-vs-cursor-vs-copilot.
Rituals hebdomadaires
Hebdo : promouvoir un brief en Skill/rule ; archiver un échec instructif ; vérifier le budget tokens. Mensuel : revoir la stack (retirer l’inutile) ; rejouer un lab gold ; mettre à jour les versions d’outils. Ces rituals valent mieux que d’ajouter un cinquième abonnement.
Exemple de NOTES.md
# NOTES rag-docs-infra
Date:
Modèle:
Brief (résumé):
Fichiers touchés:
Commande DoD:
Refus utiles:
Commit:
Leçon:
Sans ce journal, vous réapprendrez les mêmes leçons chaque sprint. Avec ce journal, vous construisez un avantage composé d’équipe.
Quand arrêter l’outil
Arrêtez la session si : le DoD échoue deux fois de suite sans diagnostic clair ; l’outil insiste pour sortir du scope ; une commande dangereuse revient après refus ; vous ne comprenez plus le diff. Dans ces cas, revenir à l’éditeur manuel + checklist est un succès de gouvernance, pas un échec personnel.
Ponts concrets vers d’autres tutoriels
Après ce lab, enchaînez selon besoin : agent CLI (claude-code-demarrer, openai-codex-intro-2026, aider-pair-programming), IDE (windsurf-cascade, continue-dev, cursor-agent-composer-2026), qualité (evaluer-qualite-skill, ai-routines-agents), ou recherche (gemini-cli-deep-research, chatgpt-search-browsing).
FAQ
Faut-il un LLM pour commencer ?
Non. Validez le top‑k d’abord, puis branchez un modèle avec « cite les sources ».
Chunking tokens ou titres ?
Pour des runbooks Markdown : ## + plafond de caractères. Le découpage aveugle à N tokens casse les procédures.
Chroma, pgvector ou TF‑IDF lab ?
Le lab prouve le flux. Équipe : pgvector si Postgres ; Chroma/LanceDB en prototype ; embeddings dès que les synonymes comptent.
Comment re-indexer après une MAJ de runbook ?
Relancez python3 rag_lab.py ingest (CI Git idéalement). Un index obsolète renvoie d’anciennes procédures.
Le RAG remplace-t-il la recherche plein texte ?
Non : rg reste excellent pour une chaîne exacte. Le RAG aide surtout les questions en langage naturel et le passage au LLM avec citations.
Pour aller plus loin
- Concepts : retrieval-augmented generation, embeddings, vector store, chunking, citations
- Voisins IA : MCP servers DevOps · Agent workflows · Routines et agents · ChatGPT runbooks
- Hub : IA pour DevOps : parcours 2026
Maillage série IA Outils 2026
| ← Précédent | MCP servers : panorama DevOps |
| → Suite | Routines et agents planifiés |
| Hub | IA pour DevOps : parcours 2026 |
Mesure et amélioration continue
Définissez trois indicateurs simples : taux de DoD vert du premier coup, nombre de refus utiles par session, coût tokens par livrable Git accepté. Revoyez-les toutes les deux semaines. Si le taux DoD stagne sous 50 %, le problème est le brief ou le corpus de rules, pas « le besoin d’un modèle plus cher ». Si le coût explose, imposez un modèle fast par défaut et réservez strong aux plans. Publiez un mini tableau dans le dépôt docs pour rendre le coût visible à l’équipe, comme n’importe quelle ligne cloud.
Retour parcours IA — hub Claude, Codex, ChatGPT et Skills.