Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

RAG & Recherche sémantique

Combiner Django/DRF + vector store (pgvector/FAISS/Weaviate) + LLM pour des réponses sourcées et contextualisées.

DRF · Postgres+pgvector · OpenAI/Mistral ROI: élevé (support, self-service) Effort: moyen+ Complexité: moyenne
Architecture de référence
Client → DRF /api/query → Guard (allowlist + rate limit)
→ Embed(q) → VectorDB (pgvector IVFFLAT) kNN → topK docs → Rerank (optionnel)
→ Prompt template (citations) → LLM → Réponse + sources → Cache

Quick Wins

  • Indexer d’abord 100–500 docs pertinents (Pareto).
  • Toujours renvoyer les sources/citations.
  • Cache des embeddings et des réponses (clé: hash(prompt+topK)).

Pièges & Anti-patterns

  • Pas d’IVFFLAT/HNSW ⇒ latence trop haute.
  • Chunking trop grand/petit ⇒ perte de pertinence.
  • Prompt sans garde-fous ⇒ hallucinations non sourcées.

  • Installer l’extension pgvector et créer un index IVFFLAT sur la colonne embedding.
  • Stocker: Document(id, title, body, url, embedding vector(1536)) + métadonnées.
  • Mettre à jour l’index après un bulk insert (ANALYZE).
SQL — extension + index sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE ia_document (
  id SERIAL PRIMARY KEY,
  title TEXT, url TEXT, body TEXT,
  embedding vector(1536)
);
-- IVFFLAT (nlist à ajuster selon volume)
CREATE INDEX ON ia_document USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
ANALYZE ia_document;
models.py — Document (django-pgvector) python
from django.db import models
from pgvector.django import VectorField

class Document(models.Model):
    title = models.CharField(max_length=300)
    url = models.URLField(blank=True, null=True)
    body = models.TextField()
    embedding = VectorField(dimensions=1536, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
KPIs à suivre
  • Latence kNN (p95)
  • R@k / MRR
  • Taux de réponses ‘avec sources’
  • CSAT/Thumbs-up

  • Pipeline d’ingestion (PDF/HTML/MD) → chunking (300–800 tokens) + overlap 10–20%.
  • Normaliser (lowercase, remove boilerplate), puis créer les embeddings.
  • Idempotence: hash du chunk pour éviter les doublons.
management command — indexation python
from django.core.management.base import BaseCommand
from ia.models import Document

class Command(BaseCommand):
    help = 'Indexe/actualise les embeddings'

    def handle(self, *args, **opts):
        for doc in Document.objects.filter(embedding__isnull=True)[:1000]:
            # text = chunker(doc.body)
            # emb = openai.embeddings.create(model='text-embedding-3-large', input=text).data[0].embedding
            emb = [0.0]*1536  # placeholder
            doc.embedding = emb
            doc.save()

  • Guardrails: allowlist de tables/colonnes, rate-limit, longueur max de requête.
  • kNN par cosine, rerank optionnel (LLM cross-encoder).
  • Toujours renvoyer answer, sources[], confidence.
views.py — endpoint /api/query python
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.db import connection

@api_view(['POST'])
def semantic_query(request):
    q = request.data.get('q','')[:500]
    if not q or len(q) < 3:
        return Response({'error': 'empty'}, status=400)

    # emb = client.embeddings.create(model='text-embedding-3-large', input=q).data[0].embedding
    emb = [0.0] * 1536  # placeholder

    with connection.cursor() as cur:
        cur.execute(
            'SELECT id, title, url, body '
            'FROM ia_document '
            'ORDER BY embedding <=> %s '
            'LIMIT 6',
            [emb],
        )
        rows = cur.fetchall()

    docs = [
        {'id': r[0], 'title': r[1], 'url': r[2], 'body': r[3][:800]}
        for r in rows
    ]
    answer = 'Réponse (démo) basée sur les documents les plus proches.'
    return Response({'answer': answer, 'sources': docs, 'confidence': 0.62})

  • Groundedness: exiger au moins 2 sources avec score < seuil.
  • Refus poli si pas de source solide (fallback ‘je ne sais pas’).
  • Journaliser: prompt, topK IDs, score, latence, feedback utilisateur.
KPIs à suivre
  • % réponses avec ≥2 sources
  • Taux de refus légitimes
  • p95 latence end-to-end
  • Coût / 1k requêtes
Prochaines étapes : Ajouter rerank cross-encoder, filtres par métadonnées et évaluation offline (R@k, MRR) sur un set d’or.