Percée de décomposition de requête : DecomposeRAG gère les questions complexes 50% mieux
Les chercheurs d'UC Berkeley introduisent DecomposeRAG, un framework de décomposition de requête automatisé qui améliore significativement les réponses aux questions multi-sauts.
- Auteur
- Équipe de Recherche Ailog
- Date de publication
- Temps de lecture
- 4 min de lecture
Aperçu de la recherche
Le laboratoire NLP d'UC Berkeley a publié DecomposeRAG, un framework qui décompose automatiquement les requêtes complexes en sous-requêtes plus simples, atteignant des résultats state-of-the-art sur les benchmarks de QA multi-sauts.
Le problème
Les questions complexes nécessitent un raisonnement multi-sauts :
Exemple : "Quelle est la population de la capitale du pays où se trouve la Tour Eiffel ?"
Nécessite : Où se trouve la Tour Eiffel ? → France Quelle est la capitale de la France ? → Paris Quelle est la population de Paris ? → 2,1 millions
Le RAG traditionnel récupère du contexte pour la question complète, manquant souvent les étapes intermédiaires.
Approche DecomposeRAG
Décomposition automatique
Utilise GPT-4 pour décomposer les requêtes en sous-questions :
``python def decompose_query(complex_query): prompt = f"""Break this question into simple sub-questions that must be answered in order.
Question: {complex_query}
Sub-questions (in order): 1."""
response = gpt4.generate(prompt) sub_questions = parse_questions(response)
return sub_questions `
Récupération séquentielle
Répondre aux sous-questions dans l'ordre, en utilisant les réponses précédentes comme contexte :
`python def sequential_rag(sub_questions): context = ""
for i, sub_q in enumerate(sub_questions): Retrieve for this sub-question docs = retrieve(sub_q + " " + context, k=5)
Generate answer answer = llm.generate( query=sub_q, context=docs, previous_answers=context )
Add to cumulative context context += f"\nQ{i+1}: {sub_q}\nA{i+1}: {answer}\n"
return answer Answer to final sub-question `
Validation des réponses
Valide chaque réponse intermédiaire avant de continuer :
`python def validate_answer(question, answer, retrieved_docs): prompt = f"""Is this answer supported by the documents?
Question: {question} Answer: {answer}
Documents: {retrieved_docs}
Supported? (yes/no):"""
validation = llm.generate(prompt)
return "yes" in validation.lower() `
Si la validation échoue, réessayer avec plus de contexte ou une stratégie de récupération alternative.
Résultats de benchmark
Testé sur quatre datasets de QA multi-sauts :
| Dataset | RAG Baseline | DecomposeRAG | Amélioration | |---------|--------------|--------------|-------------| | HotpotQA | 45.3% | 68.7% | +51.7% | | 2WikiMultihopQA | 38.2% | 57.9% | +51.6% | | MuSiQue | 32.1% | 49.8% | +55.1% | | IIRC | 41.7% | 62.3% | +49.4% |
Amélioration moyenne : +52%
Comparaison avec d'autres méthodes
| Méthode | F1 moy | Coût (relatif) | |--------|--------|-----------------| | RAG Standard | 39.3% | 1x | | Chain-of-Thought | 43.8% | 2x | | ReACT | 48.2% | 3x | | DecomposeRAG | 59.7% | 2.5x |
DecomposeRAG atteint la meilleure précision à un coût modéré.
Insights clés
Quand la décomposition aide
L'efficacité varie selon la complexité de la requête :
| Sauts | Baseline | DecomposeRAG | Gain | |------|----------|--------------|------| | 1 (simple) | 68.2% | 69.1% | +1.3% | | 2 (moyen) | 51.3% | 67.4% | +31.4% | | 3 (complexe) | 28.7% | 52.3% | +82.2% | | 4+ (très complexe) | 15.2% | 38.9% | +156.3% |
Constat : Plus de sauts = gains plus importants de la décomposition.
Qualité de la décomposition
Analyse de la qualité des décompositions générées par LLM : • Décomposition correcte : 87.3% • Étapes manquantes : 8.2% • Ordre incorrect : 3.1% • Logique circulaire : 1.4%
Même des décompositions imparfaites améliorent les résultats.
Analyse d'erreurs
Où DecomposeRAG échoue-t-il ? Erreurs de décomposition (23%) : Mauvaises sous-questions Échecs de récupération (34%) : Impossible de trouver des docs pertinents pour la sous-question Erreurs de réponse (28%) : Mauvaise réponse intermédiaire qui se propage Échecs d'intégration (15%) : Impossible de combiner les sous-réponses
Le plus courant : La récupération échoue toujours pour les sous-questions.
Implémentation
Version basique
`python class DecomposeRAG: def __init__(self, retriever, llm): self.retriever = retriever self.llm = llm
async def query(self, complex_question): Step 1: Decompose sub_questions = await self.decompose(complex_question)
Step 2: Sequential RAG context = "" for sub_q in sub_questions: Retrieve docs = await self.retriever.retrieve( sub_q + " " + context, k=5 )
Generate answer = await self.llm.generate( query=sub_q, context=docs, previous=context )
context += f"\n{sub_q} -> {answer}"
Return final answer return answer
async def decompose(self, query): Use LLM to decompose return await self.llm.decompose(query) `
Avancé : Avec validation
`python class ValidatedDecomposeRAG(DecomposeRAG): async def query(self, complex_question, max_retries=2): sub_questions = await self.decompose(complex_question)
context = "" for sub_q in sub_questions: for attempt in range(max_retries): docs = await self.retriever.retrieve(sub_q + " " + context) answer = await self.llm.generate(sub_q, docs, context)
Validate if await self.validate(sub_q, answer, docs): context += f"\n{sub_q} -> {answer}" break elif attempt == max_retries - 1: Failed validation, use best-effort answer context += f"\n{sub_q} -> {answer} (unverified)"
return answer `
Optimisations
Sous-requêtes parallèles
Quand les sous-questions sont indépendantes :
`python Identify independent sub-questions dependencies = analyze_dependencies(sub_questions)
Group independent questions independent_groups = group_by_dependencies(sub_questions, dependencies)
Process groups in parallel for group in independent_groups: Parallel retrieval for group results = await asyncio.gather(*[ self.retrieve_and_answer(q, context) for q in group ])
Add all to context for q, answer in zip(group, results): context += f"\n{q} -> {answer}" `
Cache des résultats intermédiaires
`python class CachedDecomposeRAG(DecomposeRAG): def __init__(self, retriever, llm): super().__init__(retriever, llm) self.cache = {}
async def retrieve_and_answer(self, sub_q, context): cache_key = hash(sub_q + context)
if cache_key in self.cache: return self.cache[cache_key]
result = await super().retrieve_and_answer(sub_q, context) self.cache[cache_key] = result
return result ``
Considérations pratiques
Latence
DecomposeRAG est 2-3x plus lent : • Requête 2-sauts : +2-3 secondes • Requête 3-sauts : +4-6 secondes • Requête 4-sauts : +6-10 secondes
Atténuation : • Sous-requêtes parallèles quand possible • Cache des décompositions courantes • Utiliser des LLMs plus rapides pour les étapes intermédiaires
Coût
Plus d'appels LLM = coût plus élevé : • Décomposition : 1 appel LLM • Chaque sous-question : 1 appel LLM • Validation (optionnelle) : 1 appel par sous-question
Exemple : • 3 sous-questions + validation = 7 appels LLM • vs. 1 appel pour RAG standard
Multiplicateur de coût : 2-5x selon la complexité
Quand utiliser
Utiliser DecomposeRAG quand : • Les questions sont complexes (multi-sauts) • La précision est plus importante que la vitesse • Le budget permet des coûts plus élevés
Utiliser RAG standard quand : • Recherches simples • Vitesse critique • Sensible au coût
Directions futures
Améliorations prévues : Meilleure décomposition : Affiner des modèles plus petits Stratégie adaptive : Détecter automatiquement quand décomposer Raffinement itératif : Réessayer les sous-questions échouées Multimodal : Décomposer à travers modalités
Ressources • Article : "DecomposeRAG: Automatic Query Decomposition for Multi-Hop Question Answering" • Code : github.com/berkeley-nlp/decomposerag • Démo : decomposerag.demo.berkeley.edu
Conclusion
DecomposeRAG démontre qu'une décomposition explicite des requêtes améliore significativement les réponses aux questions multi-sauts. Bien que plus coûteux et plus lent que le RAG standard, les gains de précision justifient le surcoût pour les requêtes complexes où l'exactitude est critique.