Chain-of-Thought RAG: Schrittweises Schlussfolgern für bessere Antworten
Umfassender Leitfaden zum Chain-of-Thought in RAG: Techniken des Schlussfolgerns, praktische Implementierung und Anwendungsfälle zur Verbesserung der Qualität komplexer Antworten.
TL;DR
Der Chain-of-Thought (CoT) zwingt das LLM dazu, sein Denken zu erläutern, bevor es antwortet. Im RAG verbessert diese Technik die Qualität komplexer Antworten um 35–45%, reduziert Halluzinationen und ermöglicht die Nachverfolgbarkeit des Dokumentensyntheseprozesses. Dieser Leitfaden behandelt die verschiedenen Varianten von CoT, deren Implementierung und optimale Anwendungsfälle.
Qu'est-ce que le Chain-of-Thought ?
Le problème des réponses directes
Sans CoT, un LLM génère une réponse immédiate, ce qui peut mener à des erreurs sur les questions complexes :
DEVELOPERpython# ❌ Réponse directe (problématique pour les questions complexes) prompt = """ Documents: [3 articles sur les politiques de retour] Question: Un client a acheté un produit personnalisé il y a 20 jours, peut-il le retourner ? """ # Le LLM peut sauter des étapes de raisonnement et se tromper
La solution Chain-of-Thought
Le CoT demande au LLM de raisonner étape par étape :
DEVELOPERpython# ✅ Avec Chain-of-Thought prompt = """ Documents: [3 articles sur les politiques de retour] Question: Un client a acheté un produit personnalisé il y a 20 jours, peut-il le retourner ? Raisonne étape par étape avant de répondre: 1. Identifie les règles pertinentes dans les documents 2. Vérifie chaque condition applicable 3. Tire une conclusion basée sur l'analyse Ton raisonnement: """ # Réponse du LLM: # "1. Le document 1 indique un délai de retour de 30 jours. # 2. Le document 2 précise que les produits personnalisés sont exclus. # 3. Bien que le délai (20 jours < 30 jours) soit respecté, # l'exclusion des produits personnalisés s'applique. # Conclusion: Non, le retour n'est pas possible car les produits # personnalisés sont exclus de la politique de retour."
Variantes de Chain-of-Thought
1. Zero-Shot CoT
La forme la plus simple : ajouter "Réfléchis étape par étape" :
DEVELOPERpythonZERO_SHOT_COT_PROMPT = """ Documents: {context} Question: {query} Réfléchis étape par étape, puis donne ta réponse finale. """
Avantages : Simple à implémenter Limites : Moins structuré, qualité variable
2. Few-Shot CoT
Fournir des exemples de raisonnement :
DEVELOPERpythonFEW_SHOT_COT_PROMPT = """ Voici comment analyser une question avec les documents: ## Exemple 1 Question: Le produit X est-il compatible avec Windows 11? Documents: "Le produit X fonctionne sur Windows 10 et macOS 12+" Raisonnement: - Le document mentionne Windows 10 et macOS 12+ - Windows 11 n'est pas explicitement mentionné - Cependant, Windows 11 est rétrocompatible avec Windows 10 - MAIS je ne dois pas supposer la compatibilité sans confirmation Réponse: La compatibilité Windows 11 n'est pas confirmée dans la documentation. Le produit fonctionne sur Windows 10. Je recommande de contacter le support pour confirmation. ## Exemple 2 Question: Puis-je annuler ma commande après expédition? Documents: "Les annulations sont possibles avant expédition. Après expédition, utilisez notre processus de retour standard." Raisonnement: - Le document distingue avant/après expédition - Avant expédition: annulation possible - Après expédition: pas d'annulation, mais retour possible Réponse: Une fois la commande expédiée, l'annulation n'est plus possible. Vous pouvez cependant effectuer un retour selon notre politique standard une fois le colis reçu. --- Maintenant, analyse cette question: Documents: {context} Question: {query} Raisonnement: """
3. Self-Consistency CoT
Générer plusieurs raisonnements et prendre le consensus :
DEVELOPERpythonimport asyncio from collections import Counter class SelfConsistencyCoT: def __init__(self, llm_client, num_paths=5): self.llm = llm_client self.num_paths = num_paths async def generate_with_consistency( self, context: str, query: str ) -> dict: """ Génère plusieurs chaînes de raisonnemenet et retourne la réponse majoritaire. """ # Générer N raisonnements en parallèle tasks = [ self._generate_single_path(context, query) for _ in range(self.num_paths) ] results = await asyncio.gather(*tasks) # Extraire les réponses finales answers = [r["answer"] for r in results] # Trouver le consensus answer_counts = Counter(answers) consensus_answer, count = answer_counts.most_common(1)[0] confidence = count / self.num_paths return { "answer": consensus_answer, "confidence": confidence, "reasoning_paths": results, "agreement": f"{count}/{self.num_paths}" } async def _generate_single_path( self, context: str, query: str ) -> dict: prompt = f""" Documents: {context} Question: {query} Raisonne étape par étape, puis donne ta réponse finale. Format ta réponse comme: RAISONNEMENT: [ton analyse] RÉPONSE: [ta conclusion] """ response = await self.llm.generate( prompt, temperature=0.7 # Température plus haute pour diversité ) return self._parse_response(response)
4. Tree-of-Thought (ToT)
Explorer plusieurs branches de raisonnement :
DEVELOPERpythonclass TreeOfThought: """ Explore plusieurs branches de raisonnement et sélectionne la meilleure. """ def __init__(self, llm_client, max_depth=3, branching_factor=3): self.llm = llm_client self.max_depth = max_depth self.branching_factor = branching_factor async def solve(self, context: str, query: str) -> dict: """Résout un problème avec Tree-of-Thought.""" # Racine: état initial root = ThoughtNode( state=f"Question: {query}\nContext: {context}", parent=None ) # Explorer l'arbre best_leaf = await self._explore(root, depth=0) return { "answer": best_leaf.conclusion, "reasoning_path": best_leaf.get_path(), "alternatives_explored": self._count_nodes(root) } async def _explore(self, node: ThoughtNode, depth: int) -> ThoughtNode: if depth >= self.max_depth: # Évaluer et conclure node.conclusion = await self._generate_conclusion(node) node.score = await self._evaluate(node) return node # Générer des branches (étapes de raisonnement possibles) branches = await self._generate_branches(node) # Évaluer et filtrer les branches prometteuses scored_branches = [] for branch in branches: branch.score = await self._evaluate(branch) scored_branches.append(branch) # Garder les meilleures branches top_branches = sorted( scored_branches, key=lambda x: x.score, reverse=True )[:self.branching_factor] # Explorer récursivement best_leaf = None for branch in top_branches: leaf = await self._explore(branch, depth + 1) if best_leaf is None or leaf.score > best_leaf.score: best_leaf = leaf return best_leaf async def _generate_branches(self, node: ThoughtNode) -> list: """Génère les prochaines étapes de raisonnement possibles.""" prompt = f""" État actuel du raisonnement: {node.state} Génère 3 prochaines étapes de raisonnement différentes. Format: ÉTAPE 1: [description] ÉTAPE 2: [description] ÉTAPE 3: [description] """ response = await self.llm.generate(prompt, temperature=0.8) return self._parse_branches(response, node)
Implémentation pratique pour RAG
Template CoT complet
DEVELOPERpythonRAG_COT_PROMPT = """ Tu es un assistant qui analyse les documents pour répondre aux questions. ## Documents disponibles {context} ## Question {query} ## Processus d'analyse (à suivre obligatoirement) ### Étape 1: Identification des informations pertinentes Parcours chaque document et identifie les passages qui concernent la question. Cite les passages exacts. ### Étape 2: Analyse des informations Pour chaque passage pertinent: - Que dit-il exactement? - Est-ce une réponse directe ou partielle? - Y a-t-il des conditions ou exceptions? ### Étape 3: Vérification de cohérence - Les documents se contredisent-ils? - Y a-t-il des informations manquantes? - Quelles sont mes certitudes et incertitudes? ### Étape 4: Synthèse et réponse Formule une réponse claire basée sur l'analyse ci-dessus. Cite les sources. --- ## Ton analyse ### Étape 1: Informations pertinentes """ def build_cot_prompt(context: str, query: str) -> str: return RAG_COT_PROMPT.format(context=context, query=query)
Parsing de la réponse CoT
DEVELOPERpythonimport re class CoTResponseParser: """Parse une réponse Chain-of-Thought structurée.""" def parse(self, response: str) -> dict: sections = { "relevant_info": self._extract_section(response, "Étape 1", "Étape 2"), "analysis": self._extract_section(response, "Étape 2", "Étape 3"), "consistency_check": self._extract_section(response, "Étape 3", "Étape 4"), "final_answer": self._extract_section(response, "Étape 4", None) } return { "reasoning": sections, "answer": self._extract_final_answer(sections["final_answer"]), "confidence": self._assess_confidence(sections), "sources_cited": self._extract_sources(response) } def _extract_section( self, text: str, start_marker: str, end_marker: str ) -> str: pattern = f"{start_marker}[^#]*?(?={end_marker}|$)" if end_marker else f"{start_marker}.*" match = re.search(pattern, text, re.DOTALL) return match.group(0) if match else "" def _assess_confidence(self, sections: dict) -> float: """Évalue la confiance basée sur le raisonnement.""" confidence = 1.0 # Réduire si des incertitudes sont mentionnées uncertainty_phrases = [ "pas certain", "incertain", "manque", "contradictoire", "pas mentionné", "pas clair", "ambigu" ] full_text = " ".join(sections.values()).lower() for phrase in uncertainty_phrases: if phrase in full_text: confidence -= 0.15 return max(0.2, min(1.0, confidence))
Validation du raisonnement
DEVELOPERpythonclass ReasoningValidator: """Valide la qualité du raisonnement CoT.""" def __init__(self, llm_client): self.llm = llm_client async def validate( self, context: str, query: str, reasoning: str, answer: str ) -> dict: """ Vérifie si le raisonnement est valide et si la conclusion découle logiquement. """ validation_prompt = f""" Évalue la qualité de ce raisonnement: QUESTION: {query} DOCUMENTS: {context} RAISONNEMENT: {reasoning} CONCLUSION: {answer} Vérifie: 1. Le raisonnement utilise-t-il les documents fournis? 2. La conclusion découle-t-elle logiquement du raisonnement? 3. Y a-t-il des sauts logiques ou des suppositions non justifiées? 4. La réponse est-elle fidèle aux documents (pas d'hallucination)? Réponds au format: VALIDE: [OUI/NON] SCORE: [1-10] PROBLÈMES: [liste si applicable] """ response = await self.llm.generate( validation_prompt, temperature=0.1 # Validation déterministe ) return self._parse_validation(response)
Optimisations pour RAG
1. CoT sélectif
Utiliser CoT uniquement pour les questions complexes :
DEVELOPERpythonclass SelectiveCoT: """Utilise CoT seulement quand nécessaire.""" def __init__(self, llm_client, complexity_threshold=0.6): self.llm = llm_client self.threshold = complexity_threshold async def answer(self, context: str, query: str) -> dict: # Évaluer la complexité de la question complexity = await self._assess_complexity(query, context) if complexity < self.threshold: # Question simple: réponse directe return await self._direct_answer(context, query) else: # Question complexe: Chain-of-Thought return await self._cot_answer(context, query) async def _assess_complexity(self, query: str, context: str) -> float: """Évalue la complexité de 0 à 1.""" complexity_indicators = { "multi_step": any(w in query.lower() for w in ["et", "puis", "ensuite", "aussi"]), "conditional": any(w in query.lower() for w in ["si", "quand", "lorsque", "condition"]), "comparison": any(w in query.lower() for w in ["comparer", "différence", "versus", "ou"]), "multi_doc": len(context.split("Document")) > 2, "long_query": len(query.split()) > 15 } return sum(complexity_indicators.values()) / len(complexity_indicators)
2. CoT avec citations inline
Forcer le LLM à citer pendant le raisonnement :
DEVELOPERpythonCOT_WITH_CITATIONS_PROMPT = """ Analyse les documents et réponds en citant tes sources à chaque étape. ## Documents {context} ## Question {query} ## Analyse avec citations ### Étape 1: Faits pertinents - Fait 1: "[citation exacte]" [Source: Doc X] - Fait 2: "[citation exacte]" [Source: Doc Y] ### Étape 2: Raisonnement En combinant le fait 1 [Doc X] et le fait 2 [Doc Y], on peut déduire que... ### Étape 3: Conclusion Basé sur [Doc X] et [Doc Y]: [réponse finale] """
3. CoT parallélisé
Accélérer le CoT avec de la parallélisation :
DEVELOPERpythonclass ParallelCoT: """Parallélise les étapes de raisonnement indépendantes.""" async def analyze_documents( self, documents: list, query: str ) -> dict: # Étape 1: Analyser chaque document en parallèle analysis_tasks = [ self._analyze_single_document(doc, query) for doc in documents ] doc_analyses = await asyncio.gather(*analysis_tasks) # Étape 2: Synthétiser les analyses synthesis = await self._synthesize(doc_analyses, query) # Étape 3: Formuler la réponse answer = await self._formulate_answer(synthesis, query) return { "document_analyses": doc_analyses, "synthesis": synthesis, "answer": answer } async def _analyze_single_document( self, document: str, query: str ) -> dict: prompt = f""" Document: {document} Question: {query} Analyse ce document par rapport à la question: 1. Informations pertinentes trouvées: [liste] 2. Répond-il à la question: [oui/partiellement/non] 3. Informations clés: [résumé] """ return await self.llm.generate(prompt)
Métriques et évaluation
Métriques CoT spécifiques
DEVELOPERpythonclass CoTMetrics: """Métriques pour évaluer la qualité du Chain-of-Thought.""" def evaluate(self, cot_response: dict) -> dict: return { "reasoning_depth": self._measure_depth(cot_response), "source_grounding": self._measure_grounding(cot_response), "logical_coherence": self._measure_coherence(cot_response), "conclusion_validity": self._measure_validity(cot_response) } def _measure_depth(self, response: dict) -> float: """Mesure la profondeur du raisonnement (nombre d'étapes).""" reasoning = response.get("reasoning", {}) steps = [v for v in reasoning.values() if v.strip()] return min(len(steps) / 4, 1.0) # 4 étapes = score max def _measure_grounding(self, response: dict) -> float: """Mesure l'ancrage dans les sources.""" citations = response.get("sources_cited", []) # Plus de citations = meilleur ancrage return min(len(citations) / 3, 1.0) def _measure_coherence(self, response: dict) -> float: """Mesure la cohérence logique du raisonnement.""" # Implémentation simplifiée - en production, utiliser un modèle reasoning_text = str(response.get("reasoning", "")) # Indicateurs de cohérence coherence_markers = ["donc", "par conséquent", "ainsi", "car", "parce que"] marker_count = sum(1 for m in coherence_markers if m in reasoning_text.lower()) return min(marker_count / 3, 1.0)
Cas d'usage optimaux pour CoT
Quand utiliser CoT en RAG
| Cas d'usage | Utiliser CoT ? | Justification |
|---|---|---|
| FAQ simple | Non | Réponses directes suffisent |
| Questions multi-documents | Oui | Nécessite synthèse |
| Raisonnement conditionnel | Oui | "Si X alors Y" |
| Comparaisons | Oui | Analyse de plusieurs options |
| Support technique | Parfois | Dépend de la complexité |
| Recherche juridique | Oui | Interprétation requise |
| Diagnostic médical | Oui | Analyse multi-facteurs |
Intégration avec Ailog
Ailog supporte le Chain-of-Thought nativement :
DEVELOPERpythonfrom ailog import AilogClient client = AilogClient(api_key="your-key") response = client.chat( channel_id="support-widget", message="Puis-je combiner la réduction membre et la promo en cours?", reasoning_mode="chain_of_thought", # Active le CoT cot_settings={ "show_reasoning": True, # Afficher le raisonnement à l'utilisateur "validate_reasoning": True, # Valider la logique "max_steps": 4 } ) print(response.reasoning) # Étapes de raisonnement print(response.answer) # Réponse finale print(response.confidence) # Score de confiance
Conclusion
Le Chain-of-Thought améliore significativement les réponses RAG complexes. Points clés :
- Utiliser CoT sélectivement pour les questions complexes
- Few-shot est plus fiable que zero-shot
- Self-consistency augmente la fiabilité
- Valider le raisonnement pour éviter les erreurs logiques
- Citer pendant le raisonnement pour la traçabilité
Ressources complémentaires
- Introduction au RAG - Fondamentaux
- Génération LLM pour RAG - Guide parent
- Prompt Engineering RAG - Optimiser les prompts
- Outputs structurés RAG - Formats de sortie
Envie de raisonnement avancé sans complexité ? Essayez Ailog - Chain-of-Thought intégré, validation automatique, confiance garantie.
FAQ
Tags
Verwandte Artikel
RAG-Generierung: LLM auswählen und optimieren
Umfassender Leitfaden zur Auswahl und Konfiguration Ihres LLM in einem RAG-System: prompting, temperature, tokens und Optimierung der Antworten.
RAG-Agenten: Orchestrierung von Multi-Agenten-Systemen
Konzipieren Sie RAG-basierte Multi-Agenten-Systeme: Orchestrierung, Spezialisierung, Zusammenarbeit und Fehlerbehandlung für komplexe Assistenten.
Konversationelles RAG: Gedächtnis und Kontext über mehrere Sitzungen
Implementieren Sie ein RAG mit konversationellem Gedächtnis: Verwaltung des Kontexts, Verlauf über mehrere Sitzungen und Personalisierung der Antworten.