Architecture future : Embeddings pour learnings.jsonl¶
Statut : DIFFÉRÉ — À implémenter quand
learnings.jsonldépasse 200 entréesCompteur actuel : ~57 entrées (2026-02-10)
Problème¶
La recherche par grep sur les tags devient inefficace au-delà de 200 learnings :
- Résultats trop nombreux pour les tags fréquents (
#backend,#auth) - Pas de pertinence sémantique (un learning sur "CORS" ne matche pas une query "security headers")
- Pas de ranking par similarité
Solution proposée¶
Remplacer grep par une recherche sémantique via embeddings vectoriels.
Architecture cible¶
data/
├── learnings.jsonl # Source de vérité (append-only, inchangé)
├── learnings-embeddings.npy # Vecteurs NumPy (régénéré)
├── learnings-index.faiss # Index FAISS pour recherche k-NN
└── embeddings-meta.json # Métadonnées: model, timestamp, count
Format embeddings-meta.json¶
{
"model": "text-embedding-3-small",
"dimensions": 1536,
"count": 247,
"last_rebuilt": "2026-04-15T10:32:00Z",
"source_hash": "sha256:abc123..."
}
Flow de recherche¶
1. Query: "security headers rate limiting CORS"
2. Embed query → vecteur 1536d
3. FAISS search top-k (k=10)
4. Retourner learnings pertinents triés par similarité
Intégration dans gov.md¶
Modifier l'étape 0 "Charger les learnings pertinents" :
0. **Charger les learnings pertinents** :
- SI `data/learnings-index.faiss` existe ET embeddings-meta.count > 200 :
- Construire query = "{domain} {tags probables}"
- Recherche sémantique top-5 via embeddings
- SINON :
- Fallback grep sur tags (comportement actuel)
- Injecter les learnings dans le contexte
Trigger de rebuild¶
L'index doit être reconstruit quand :
wc -l learnings.jsonl!=embeddings-meta.count- Ou manuellement via
scripts/rebuild-embeddings.py
Vérification au démarrage du workflow :
LINES=$(wc -l < data/learnings.jsonl)
META_COUNT=$(jq -r '.count' data/embeddings-meta.json 2>/dev/null || echo 0)
if [ "$LINES" -ne "$META_COUNT" ]; then
python scripts/rebuild-embeddings.py
fi
Dépendances requises¶
faiss-cpu>=1.7.4 # ou faiss-gpu pour GPU
sentence-transformers # si modèle local
openai>=1.0 # si OpenAI embeddings
numpy>=1.24
Script rebuild-embeddings.py (esquisse)¶
#!/usr/bin/env python3
import json
import numpy as np
import faiss
from openai import OpenAI # ou sentence_transformers
def main():
# 1. Lire learnings.jsonl
learnings = []
with open("data/learnings.jsonl") as f:
for line in f:
learnings.append(json.loads(line))
# 2. Préparer textes à embedder
texts = [f"{l['story']} {' '.join(l['tags'])} {l['learning']}" for l in learnings]
# 3. Générer embeddings
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
embeddings = np.array([e.embedding for e in response.data])
# 4. Construire index FAISS
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension) # Inner Product (cosine après normalisation)
faiss.normalize_L2(embeddings)
index.add(embeddings)
# 5. Sauvegarder
np.save("data/learnings-embeddings.npy", embeddings)
faiss.write_index(index, "data/learnings-index.faiss")
meta = {
"model": "text-embedding-3-small",
"dimensions": dimension,
"count": len(learnings),
"last_rebuilt": datetime.now().isoformat()
}
with open("data/embeddings-meta.json", "w") as f:
json.dump(meta, f, indent=2)
if __name__ == "__main__":
main()
Alternatives considérées¶
| Option | Avantages | Inconvénients |
|---|---|---|
| OpenAI embeddings | Haute qualité, pas de GPU | Coût API, latence réseau |
| sentence-transformers local | Gratuit, offline | Qualité moindre, RAM |
| ChromaDB | Persistance intégrée | Dépendance lourde |
| Qdrant | Production-ready | Overengineering pour ~500 learnings |
Recommandation : OpenAI text-embedding-3-small pour la qualité, avec fallback sentence-transformers si offline requis.
Critère de déclenchement¶
Implémenter quand :
wc -l data/learnings.jsonl> 200- ET recherche grep devient inefficace (retours > 20 résultats régulièrement)
Documenté le 2026-02-10 — À réviser lors du passage du seuil