Quand votre chatbot invente des réponses, c'est un problème d'architecture, pas de modèle
La plupart des chatbots échouent de la même façon prévisible : posez-leur une question légèrement hors de leur données d'entraînement, et ils hallucinent avec une confiance déconcertante. Le modèle de langage n'est pas en cause — c'est l'approche qui est fondamentalement limitée. Retrieval-Augmented Generation (RAG) résout ce problème en ancrant chaque réponse dans vos propres documents, en temps réel.
Cet article explore le pipeline RAG complet : de l'ingestion des documents à la génération de réponses contextualisées, avec un regard PHP/Symfony sur l'implémentation.
Source originale : Building Intelligent Chatbots with RAG and Vector Databases par Marcc Atayde sur DEV Community.
Ce qu'est vraiment le RAG
RAG signifie Retrieval-Augmented Generation. Au lieu de demander à un LLM de répondre uniquement depuis sa mémoire d'entraînement, on procède en deux temps :
- Récupérer les documents pertinents depuis une base de connaissances
- Générer une réponse en passant ces documents comme contexte au modèle, avec la question de l'utilisateur
Le pipeline complet tient en trois phases :
Ingestion → Découper les docs, générer des embeddings, stocker dans une base vectorielle
Recherche → Embedder la question, similarité vectorielle → top-K chunks
Génération → [question + chunks] → LLM → réponse fondée sur vos données
Le résultat : des réponses précises, traçables jusqu'à leur source, sans hallucination inventée.
Le cœur technique : embeddings et recherche vectorielle
Qu'est-ce qu'un embedding ?
Un embedding est une représentation numérique (un vecteur de centaines ou milliers de dimensions) du sens d'un texte. Deux textes sémantiquement proches auront des vecteurs proches dans cet espace mathématique, indépendamment des mots exacts utilisés.
Exemple concret : "comment résilier mon abonnement" et "je veux annuler mon contrat" produiront des embeddings très similaires, et une recherche vectorielle les associera naturellement.
La base vectorielle : pgvector en contexte PHP/Symfony
Les bases vectorielles stockent ces embeddings et permettent des recherches de similarité efficaces. Plusieurs options existent :
- pgvector : extension PostgreSQL, idéale si vous êtes déjà sur Postgres. Intégration naturelle avec Doctrine.
- Pinecone / Qdrant / Weaviate : solutions dédiées, pertinentes pour de gros volumes ou des besoins de scalabilité spécifiques.
Avec pgvector sous Symfony, une entité Document peut ressembler à :
#[Entity]
class Document
{
#[Column(type: 'vector', length: 1536)]
private array $embedding;
#[Column(type: 'text')]
private string $content;
#[Column(type: 'string')]
private string $source;
}
La recherche par similarité cosinus s'exprime ensuite directement en DQL ou SQL natif.
Implémenter le pipeline RAG étape par étape
Étape 1 — Ingestion et chunking
La qualité du RAG dépend largement du découpage des documents. Un chunk trop long dilue l'information pertinente ; trop court, il perd le contexte.
Stratégies courantes :
- Découpage fixe : 500 tokens avec 50 tokens de chevauchement (overlap)
- Découpage sémantique : sur les paragraphes ou sections naturelles
- Hiérarchique : résumé + chunks détaillés, pour des documents longs
class DocumentIngester
{
public function ingest(string $filePath): void
{
$text = $this->loader->load($filePath);
$chunks = $this->chunker->chunk($text, size: 500, overlap: 50);
foreach ($chunks as $chunk) {
$embedding = $this->embedder->embed($chunk);
$this->repository->save(new Document($chunk, $embedding));
}
}
}
Étape 2 — Recherche et retrieval
Lorsqu'un utilisateur pose une question, on génère son embedding et on cherche les chunks les plus proches dans la base vectorielle :
class VectorSearchService
{
public function search(string $question, int $topK = 5): array
{
$queryEmbedding = $this->embedder->embed($question);
return $this->repository->findSimilar($queryEmbedding, $topK);
}
}
La similarité cosinus est la mesure la plus courante : elle compare les angles entre vecteurs plutôt que leurs distances brutes, ce qui est plus robuste pour les embeddings textuels.
Étape 3 — Génération augmentée
Les chunks récupérés deviennent le contexte injecté dans le prompt système du LLM :
class RagChatbot
{
public function answer(string $question): string
{
$chunks = $this->searchService->search($question);
$context = implode("\n\n", array_column($chunks, 'content'));
$prompt = <<<PROMPT
Tu es un assistant expert. Réponds UNIQUEMENT en te basant sur le contexte fourni.
Si la réponse n'est pas dans le contexte, dis-le clairement.
Contexte :
{$context}
Question : {$question}
PROMPT;
return $this->llmClient->complete($prompt);
}
}
L'instruction "réponds UNIQUEMENT en te basant sur le contexte" est cruciale : elle empêche le modèle de compléter avec ses propres connaissances d'entraînement.
Pièges à éviter et bonnes pratiques
Chunks trop longs ou trop courts : testez empiriquement sur vos données. Un bon point de départ est 300-600 tokens avec overlap.
Embeddings incohérents : utilisez toujours le même modèle d'embedding pour l'ingestion et pour les requêtes. Changer de modèle invalide toute la base.
Ignorer le re-ranking : la similarité vectorielle n'est pas parfaite. Un re-ranker (modèle léger qui reclasse les top-K résultats) améliore significativement la pertinence.
Ne pas citer les sources : renvoyez toujours le champ source dans la réponse. C'est ce qui rend le système auditable et fait la différence avec un chatbot classique.
Contexte trop chargé : envoyer 20 chunks au LLM noie l'information. 3 à 7 chunks bien choisis donnent de meilleurs résultats que 15 chunks bruités.
Conclusion
Le RAG n'est pas une technologie futuriste réservée aux grandes équipes IA : c'est une architecture accessible, implémentable aujourd'hui avec des outils PHP/Symfony matures comme pgvector et l'API OpenAI ou des alternatives open-source (Mistral, Ollama).
Le gain est immédiat et mesurable : un chatbot RAG répond sur la base de vos données, cite ses sources, et peut être mis à jour sans ré-entraîner le moindre modèle. Pour une PME qui veut déployer un assistant IA sur sa documentation ou son support client, c'est l'architecture de référence.
L'étape suivante logique : coupler ce pipeline à des agents capables de décider eux-mêmes quand interroger la base vectorielle, quand appeler une API externe, et quand demander une clarification à l'utilisateur.