Optimierung von Abfragen: Verbesserung der Effizienz des Retrievals
Techniken zur Optimierung von Benutzeranfragen und zur Verbesserung des Retrievals: Query-Rewriting, Expansion, Decomposition und Routing-Strategien.
Das Problem der Anfragen
Benutzer formulieren ihre Fragen nicht immer im optimalen Format für die retrieval:
- Zu vage: "Wie funktioniert das?"
- Zu spezifisch: "Welchen hexadezimalen RGB-Code hat das Blau im Logo unserer mobilen App im Dunkelmodus?"
- Mehrdeutig: "Und das andere?"
- Falsch geschrieben: "Wie konfiguriere ich die Einstellungen?"
- Mehrere Absichten: "Welche Tarifpläne gibt es, wie kann ich upgraden und kann ich eine Rückerstattung erhalten?"
Die Anfrageoptimierung überbrückt die Lücke zwischen der Art, wie Benutzer ihre Fragen stellen, und der Art, wie das System seine searches durchführt.
Vorverarbeitung von Anfragen
Normalisierung
DEVELOPERpythondef normalize_query(query: str) -> str: # Lowercase query = query.lower() # Remove extra whitespace query = ' '.join(query.split()) # Fix common typos (optional) query = spell_check(query) # Remove stop words (optional, be careful) # query = remove_stop_words(query) return query
Rechtschreibkorrektur
DEVELOPERpythonfrom symspellpy import SymSpell sym_spell = SymSpell(max_dictionary_edit_distance=2) sym_spell.load_dictionary("frequency_dictionary.txt", 0, 1) def correct_spelling(query: str) -> str: words = query.split() corrected_words = [] for word in words: suggestions = sym_spell.lookup(word, max_edit_distance=2) if suggestions: corrected_words.append(suggestions[0].term) else: corrected_words.append(word) return ' '.join(corrected_words)
Umschreiben von Anfragen
Vorlagenbasiertes Umschreiben
DEVELOPERpythonREWRITE_TEMPLATES = { r"how (?:do|can) i (.+)\?": "Steps to {}", r"what is (.+)\?": "{} definition and explanation", r"why (.+)\?": "Reasons and explanation for {}", } def template_rewrite(query: str) -> str: for pattern, template in REWRITE_TEMPLATES.items(): match = re.match(pattern, query, re.IGNORECASE) if match: return template.format(match.group(1)) return query # Example query = "How do I reset my password?" rewritten = template_rewrite(query) # "Steps to reset my password"
LLM-basiertes Umschreiben
DEVELOPERpythonasync def llm_rewrite_query(query: str, llm) -> str: prompt = f"""Schreibe diese Frage um, damit sie spezifischer und besser für die Suche geeignet ist. Original: {query} Umschreibung:""" return await llm.generate(prompt, max_tokens=50) # Example query = "How does it work?" context = get_conversation_context() rewritten = await llm_rewrite_query(f"{context}\n{query}", llm) # "How does the password reset feature work?"
Erweiterung von Anfragen
Erweiterung durch Synonyme
DEVELOPERpythonfrom nltk.corpus import wordnet def expand_with_synonyms(query: str, max_synonyms=2) -> List[str]: words = query.split() expanded_queries = [query] # Original for word in words: synsets = wordnet.synsets(word) for synset in synsets[:max_synonyms]: for lemma in synset.lemmas()[:1]: # One synonym per synset synonym = lemma.name().replace('_', ' ') if synonym.lower() != word.lower(): # Replace word with synonym new_query = query.replace(word, synonym) expanded_queries.append(new_query) return list(set(expanded_queries)) # Example queries = expand_with_synonyms("repair broken device") # ["repair broken device", "fix broken device", "repair damaged device"]
LLM-basierte Erweiterung
DEVELOPERpythonasync def generate_query_variations(query: str, llm, num_variations=3) -> List[str]: prompt = f"""Generiere {num_variations} verschiedene Möglichkeiten, diese Frage zu stellen: Original: {query} Varianten: 1.""" response = await llm.generate(prompt) variations = parse_numbered_list(response) return [query] + variations # Include original # Example variations = await generate_query_variations("database performance issues", llm) # [ # "database performance issues", # "slow database queries", # "how to optimize database speed", # "database latency problems" # ]
Zerlegung von Anfragen
Komplexe Anfragen in einfachere Teilanfragen zerlegen.
Regelnbasierte Zerlegung
DEVELOPERpythondef decompose_query(query: str) -> List[str]: # Split by "and" if " and " in query.lower(): return [q.strip() for q in re.split(r'\s+and\s+', query, flags=re.IGNORECASE)] # Split by comma if ", " in query: return [q.strip() for q in query.split(", ")] # Single query return [query] # Example decompose_query("What are the pricing plans and how do I upgrade?") # ["What are the pricing plans", "how do I upgrade"]
LLM-basierte Zerlegung
DEVELOPERpythonasync def llm_decompose(complex_query: str, llm) -> List[str]: prompt = f"""Zerlege diese komplexe Frage in einfachere Teilfragen: Frage: {complex_query} Teilfragen: 1.""" response = await llm.generate(prompt) return parse_numbered_list(response) # Example sub_questions = await llm_decompose( "What are the system requirements and how much does it cost and is there a free trial?", llm ) # [ # "What are the system requirements?", # "How much does it cost?", # "Is there a free trial?" # ]
Mehrstufige Retrieval
DEVELOPERpythonasync def multi_step_retrieval(complex_query: str, llm, vector_db): # Decompose sub_queries = await llm_decompose(complex_query, llm) # Retrieve for each sub-query all_contexts = [] for sub_q in sub_queries: contexts = await vector_db.search(sub_q, k=3) all_contexts.extend(contexts) # Deduplicate unique_contexts = deduplicate_by_id(all_contexts) # Generate comprehensive answer answer = await llm.generate( query=complex_query, contexts=unique_contexts ) return answer
Anfrage-Routing
Verschiedene Anfragen zu unterschiedlichen retrieval-Strategien leiten.
Intent-Klassifikation
DEVELOPERpythonclass QueryRouter: def __init__(self, llm): self.llm = llm async def classify_intent(self, query: str) -> str: prompt = f"""Klassifiziere die Absicht dieser Anfrage: Anfrage: {query} Absicht (wähle eine): - factual: Anfrage nach spezifischen Fakten - procedural: Wie man etwas tut - troubleshooting: Ein Problem lösen - comparison: Optionen vergleichen - explanation: Ein Konzept verstehen Absicht:""" intent = await self.llm.generate(prompt, max_tokens=10) return intent.strip().lower() async def route_query(self, query: str, retrievers: dict): intent = await self.classify_intent(query) # Route based on intent if intent == "procedural": return await retrievers['docs'].retrieve(query) elif intent == "troubleshooting": return await retrievers['tickets'].retrieve(query) elif intent == "factual": return await retrievers['knowledge_base'].retrieve(query) else: # Default: try all and merge return await self.ensemble_retrieve(query, retrievers)
Komplexitätsbasiertes Routing
DEVELOPERpythondef estimate_complexity(query: str) -> str: # Simple heuristics word_count = len(query.split()) has_and_or = any(word in query.lower() for word in ['and', 'or', 'also']) has_multiple_questions = query.count('?') > 1 if word_count > 20 or has_and_or or has_multiple_questions: return 'complex' elif word_count > 10: return 'medium' else: return 'simple' async def complexity_based_retrieval(query: str): complexity = estimate_complexity(query) if complexity == 'simple': # Simple: vector search only return await vector_retrieve(query, k=3) elif complexity == 'medium': # Medium: hybrid search return await hybrid_retrieve(query, k=5) else: # Complex: decompose and multi-step return await multi_step_retrieval(query)
Kontextuelle Verbesserung von Anfragen
Den Gesprächsverlauf nutzen, um Anfragen zu verbessern.
Sitzungskontext
DEVELOPERpythonclass ContextualQueryEnhancer: def __init__(self): self.conversation_history = [] def add_turn(self, query: str, answer: str): self.conversation_history.append({ 'query': query, 'answer': answer }) async def enhance_query(self, current_query: str, llm) -> str: if not self.conversation_history: return current_query # Get recent context recent = self.conversation_history[-3:] # Last 3 turns context = "\n".join([ f"User: {turn['query']}\nAssistant: {turn['answer']}" for turn in recent ]) prompt = f"""Schreibe basierend auf dem Gesprächsverlauf die aktuelle Anfrage so um, dass sie eigenständig und klar ist. Gespräch: {context} Aktuelle Anfrage: {current_query} Eigenständige Anfrage:""" enhanced = await llm.generate(prompt, max_tokens=100) return enhanced.strip() # Example usage enhancer = ContextualQueryEnhancer() enhancer.add_turn( "What are the pricing plans?", "We offer Basic ($10/mo), Pro ($25/mo), and Enterprise (custom)." ) enhanced = await enhancer.enhance_query("What about the features?", llm) # "What are the features included in each pricing plan?"
Filterung von Anfragen
Erkennung unangemessener Anfragen
DEVELOPERpythonasync def filter_inappropriate(query: str, llm) -> bool: """ Check if query is appropriate for the RAG system """ prompt = f"""Ist diese Anfrage für ein Kundensupport-System angemessen? Anfrage: {query} Antworte mit 'yes' oder 'no':""" response = await llm.generate(prompt, max_tokens=5) return 'yes' in response.lower() # Usage if not await filter_inappropriate(user_query, llm): return "I can only help with product-related questions."
Erkennung von Anfragen außerhalb des Geltungsbereichs
DEVELOPERpythonSCOPE_KEYWORDS = { 'in_scope': ['pricing', 'features', 'setup', 'troubleshooting'], 'out_of_scope': ['weather', 'news', 'politics', 'recipes'] } def is_in_scope(query: str) -> bool: query_lower = query.lower() # Check for out-of-scope keywords if any(keyword in query_lower for keyword in SCOPE_KEYWORDS['out_of_scope']): return False # Check for in-scope keywords if any(keyword in query_lower for keyword in SCOPE_KEYWORDS['in_scope']): return True # Default: assume in scope (can also use LLM for better accuracy) return True
Anfrage-Anreicherung
Kontext hinzufügen, um das retrieval zu verbessern.
Metadaten-Injektion
DEVELOPERpythondef augment_with_metadata(query: str, user_context: dict) -> str: """ Add user-specific context to query """ plan = user_context.get('plan', 'basic') role = user_context.get('role', 'user') # Add metadata that might help retrieval augmented = f"{query} [user_plan:{plan}] [role:{role}]" return augmented # Example query = "How do I export data?" user_context = {'plan': 'enterprise', 'role': 'admin'} augmented = augment_with_metadata(query, user_context) # "How do I export data? [user_plan:enterprise] [role:admin]"
Zeitlicher Kontext
DEVELOPERpythonfrom datetime import datetime def add_temporal_context(query: str) -> str: """ Add current date/time to query for time-sensitive retrieval """ now = datetime.now() temporal_query = f"{query} [date:{now.strftime('%Y-%m-%d')}]" return temporal_query # Useful for queries like: # "What's new?" → "What's new? [date:2025-02-25]" # "Latest features" → "Latest features [date:2025-02-25]"
Optimierung mehrerer Anfragen
Beim Einsatz von Anfrageerweiterung oder Multi-Anfrage-Ansätzen:
Parallele Retrieval
DEVELOPERpythonimport asyncio async def parallel_multi_query(queries: List[str], vector_db, k=5): """ Retrieve for multiple queries in parallel """ tasks = [vector_db.search(q, k=k) for q in queries] results = await asyncio.gather(*tasks) # Merge and deduplicate all_docs = [] for result in results: all_docs.extend(result) unique_docs = deduplicate_by_id(all_docs) # Re-rank by frequency (documents appearing in multiple queries) doc_counts = Counter([doc['id'] for doc in all_docs]) sorted_docs = sorted( unique_docs, key=lambda doc: doc_counts[doc['id']], reverse=True ) return sorted_docs[:k]
Score-Fusion
DEVELOPERpythondef fuse_results(multi_query_results: List[List[dict]], method='rrf') -> List[dict]: """ Combine results from multiple queries """ if method == 'rrf': # Reciprocal Rank Fusion doc_scores = {} for results in multi_query_results: for rank, doc in enumerate(results, start=1): doc_id = doc['id'] if doc_id not in doc_scores: doc_scores[doc_id] = {'doc': doc, 'score': 0} doc_scores[doc_id]['score'] += 1 / (60 + rank) ranked = sorted( doc_scores.values(), key=lambda x: x['score'], reverse=True ) return [item['doc'] for item in ranked] elif method == 'max': # Take best score doc_scores = {} for results in multi_query_results: for doc in results: doc_id = doc['id'] score = doc.get('score', 0) if doc_id not in doc_scores or score > doc_scores[doc_id]['score']: doc_scores[doc_id] = {'doc': doc, 'score': score} ranked = sorted( doc_scores.values(), key=lambda x: x['score'], reverse=True ) return [item['doc'] for item in ranked]
Best Practices
- Einfach anfangen: Normalisierung und Rechtschreibkorrektur vor komplexen Optimierungen
- Wirkung messen: A/B-Tests der Anfrageoptimierungen
- Nicht überoptimieren: Manchmal funktionieren einfache Anfragen besser
- Original bewahren: Die ursprüngliche Anfrage für den Fallback aufbewahren
- Nutzerfeedback: Verfolgen, welche Optimierungen die Zufriedenheit verbessern
- Kontext zählt: Den Gesprächsverlauf nutzen, wenn verfügbar
- Überall asynchron: Mehrere Anfragevarianten parallelisieren
Wann welche Technik verwenden
| Technik | Verwenden wenn | Wirkung |
|---|---|---|
| Normalisierung | Immer | Gering (grundlegend) |
| Rechtschreibkorrektur | Anwendungen für Endnutzer | Mittel |
| Umschreiben von Anfragen | Häufig vage Anfragen | Mittel |
| Erweiterung von Anfragen | Recall priorisiert | Hoch |
| Zerlegung | Komplexe mehrteilige Anfragen | Hoch |
| Routing | Mehrere Datenquellen | Mittel-Hoch |
| Kontextuell | Chat/Konversation | Hoch |
Nächste Schritte
Nachdem die Anfragen optimiert wurden, ist ein effizientes Management des Kontextfensters entscheidend, um innerhalb der token-Limits zu bleiben und die Kosten zu optimieren. Der abschließende Leitfaden behandelt die Strategien zur Optimierung des Kontextfensters.
Tags
Verwandte Artikel
Hybride RAG-Suche: BM25 + Vektorsuche (2025)
+20–30% RAG-Genauigkeit mit hybrider Suche. Schritt-für-Schritt-Anleitung zum Kombinieren von BM25 und Vektorsuche mit Weaviate, Qdrant oder Pinecone.
Abfrageerweiterung: Relevantere Ergebnisse erhalten
Den Recall um 40 % verbessern: erweitern Sie Nutzeranfragen mit Synonymen, Unterabfragen und von LLM generierten Variationen.
Abruf übergeordneter Dokumente: rauschfreier Kontext
Durchsuchen Sie kleine Fragmente, rufen Sie die vollständigen Dokumente ab: die beste Kombination aus Präzision und Kontext für RAG-Systeme.