fix:performance optimization

This commit is contained in:
Ida Dittrich 2026-01-12 16:08:32 +01:00
parent 583525a151
commit 378128c3ce
2 changed files with 174 additions and 339 deletions

View file

@ -14,15 +14,28 @@ from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, Operati
logger = logging.getLogger(__name__)
# Cache for system prompts to avoid regenerating on every request
_cached_analysis_prompt = None
_cached_analysis_prompt_date = None
_cached_final_answer_prompt = None
_cached_final_answer_prompt_date = None
def get_analysis_system_prompt() -> str:
"""
Get the system prompt for analyzing user input and creating queries.
Focuses on understanding the question and determining what queries are needed.
Uses caching to avoid regenerating the prompt on every request.
"""
global _cached_analysis_prompt, _cached_analysis_prompt_date
current_date = datetime.datetime.now().strftime("%d.%m.%Y")
return f"""Heute ist der {current_date}.
# Return cached prompt if date hasn't changed
if _cached_analysis_prompt is not None and _cached_analysis_prompt_date == current_date:
return _cached_analysis_prompt
# Regenerate prompt (only when date changes)
_cached_analysis_prompt = f"""Heute ist der {current_date}.
Du bist ein Chatbot der Althaus AG.
Deine Aufgabe ist es, Benutzeranfragen zu analysieren und zu bestimmen, welche Datenbankabfragen oder Web-Recherchen benötigt werden, um die Frage zu beantworten.
@ -288,16 +301,27 @@ WICHTIG FÜR PROGRESSIVE QUERIES:
Du antwortest ausschliesslich auf Deutsch. Nutze kein sz(ß) sondern immer ss.
"""
# Update cache with new date
_cached_analysis_prompt_date = current_date
return _cached_analysis_prompt
def get_final_answer_system_prompt() -> str:
"""
Get the system prompt for generating the final answer.
Focuses on formatting, presenting results, and user engagement.
Uses caching to avoid regenerating the prompt on every request.
"""
global _cached_final_answer_prompt, _cached_final_answer_prompt_date
current_date = datetime.datetime.now().strftime("%d.%m.%Y")
return f"""Heute ist der {current_date}.
# Return cached prompt if date hasn't changed
if _cached_final_answer_prompt is not None and _cached_final_answer_prompt_date == current_date:
return _cached_final_answer_prompt
# Regenerate prompt (only when date changes)
_cached_final_answer_prompt = f"""Heute ist der {current_date}.
Du bist ein Chatbot der Althaus AG.
Deine Aufgabe ist es, auf Basis von Datenbank-Ergebnissen und Web-Recherchen hilfreiche, präzise Antworten zu geben.
@ -454,6 +478,10 @@ Am Ende jeder Antwort biete hilfreiche Optionen für nächste Schritte an (Detai
Du antwortest ausschliesslich auf Deutsch. Nutze kein sz(ß) sondern immer ss.
"""
# Update cache with new date
_cached_final_answer_prompt_date = current_date
return _cached_final_answer_prompt
def get_system_prompt() -> str:
@ -480,152 +508,21 @@ def get_initial_analysis_prompt(user_prompt: str, context: str) -> str:
User question: {user_prompt}{context}
ABSOLUT KRITISCH - DU MUSST MINDESTENS 5-8 ABFRAGEN ERSTELLEN
ABSOLUT VERBOTEN: Nur 1 Abfrage zu erstellen!
ABSOLUT VERBOTEN: Nur 2-3 Abfragen zu erstellen!
OBLIGATORISCH: MINDESTENS 5-8 Abfragen bei normalen Anfragen
OBLIGATORISCH: MINDESTENS 8 Abfragen bei Zertifizierungen (UL, CE, TÜV, etc.)
WICHTIG - QUERY-ANZAHL FÜR PERFORMANCE
Erstelle MAXIMAL 5 SQL-Queries (für bessere Performance)
Jede Query muss eine andere Strategie verfolgen
Alle Queries werden parallel ausgeführt
Analysiere die Benutzeranfrage und bestimme:
1. Ob eine Datenbankabfrage benötigt wird (needsDatabaseQuery)
2. Ob eine Web-Recherche benötigt wird (needsWebResearch)
3. Falls eine Datenbankabfrage benötigt wird: Erstelle MINDESTENS 5-8 separate, vollständige, ausführbare SQL-Abfragen
3. Falls eine Datenbankabfrage benötigt wird: Erstelle MAXIMAL 5 separate, vollständige, ausführbare SQL-Abfragen mit unterschiedlichen Strategien
KRITISCH - MINDESTENS 5-8 ABFRAGEN PARALLEL
- OBLIGATORISCH: Erstelle IMMER MINDESTENS 5-8 SQL-Queries
- OBLIGATORISCH: Bei Zertifizierungen (UL, CE, TÜV, etc.) MINDESTENS 8 Queries!
- OBLIGATORISCH: Alle Queries werden parallel ausgeführt und Ergebnisse kombiniert
- OBLIGATORISCH: Jede Query MUSS eine andere Strategie verfolgen
- VERBOTEN: Nur 1 Query zu erstellen - DAS IST ABSOLUT FALSCH UND WIRD ABGELEHNT!
- VERBOTEN: Nur 2-3 Queries zu erstellen - DAS IST ZU WENIG!
- VERBOTEN: Alle Queries mit derselben Strategie - verwende unterschiedliche Ansätze!
WICHTIG - SO ERSTELLST DU DIE ABFRAGEN
1. SCHRITT 1: Analysiere die Anfrage und identifiziere ALLE Suchkriterien
2. SCHRITT 2: Erstelle für JEDES Kriterium mindestens eine Query mit unterschiedlichen Strategien
3. SCHRITT 3: Erstelle zusätzliche Queries mit kombinierten Strategien
4. SCHRITT 4: Erstelle Fallback-Queries ohne spezifische Filter
5. SCHRITT 5: ZÄHLE die Queries im sqlQueries-Array
6. SCHRITT 6: Wenn weniger als 5 (oder 8 bei Zertifizierungen): ERSTELLE WEITERE QUERIES!
WENN DU NUR 1 QUERY ERSTELLST, IST DIE ANTWORT FALSCH UND WIRD ABGELEHNT!
WENN DU NUR 2-3 QUERIES ERSTELLST, IST DAS ZU WENIG UND WIRD ABGELEHNT!
KRITISCH - VERGLEICHSOPERATOREN ("MINDESTENS", "AT LEAST", "")
- Bei "mindestens X" MUSS JEDE Query höhere Werte einschließen
- Beispiel: "mindestens 10A" IMMER: (10A OR 12A OR 15A OR 16A OR 18A OR 20A OR 25A OR 30A)
- VERBOTEN: Nur nach dem exakten Wert suchen
- ABSOLUT KRITISCH - MEHRERE ABFRAGEN BEI "MINDESTENS"
* Wenn der Nutzer "mindestens X" sagt, MÜSSEN IMMER mehrere Datenbankabfragen erstellt werden
* OBLIGATORISCH: Erstelle mehrere Queries mit unterschiedlichen Strategien, um ALLE passenden Artikel zu finden
* OBLIGATORISCH: Du MUSST ALLE Artikel zurückgeben, die die Kriterien erfüllen (z.B. 10A UND 20A bei "mindestens 10A")
* VERBOTEN: Nur eine Query zu erstellen - das würde Artikel mit höheren Werten übersehen
* Beispiel: Bei "mindestens 10A" müssen Artikel mit 10A, 15A, 20A, 25A, etc. ALLE gefunden werden
KRITISCH - ZERTIFIZIERUNGEN ERFORDERN WEB-RECHERCHE
- Bei Zertifizierungen (UL, CE, TÜV, VDE, etc.) IMMER needsWebResearch = true setzen
- VERBOTEN: needsWebResearch = false bei Zertifizierungen - DAS IST FALSCH!
- OBLIGATORISCH: Bei "UL", "UL-zertifiziert", "UL certified" IMMER needsWebResearch = true!
- Erstelle MINDESTENS 8 progressive SQL-Queries mit unterschiedlichen Strategien
OBLIGATORISCH - ERSTELLE DIESE 8 QUERIES BEI ZERTIFIZIERUNGEN
Bei "einphasige Netzgeräte mit mindestens 10A, UL-zertifiziert" MUSS du MINDESTENS diese 8 Queries erstellen:
WICHTIG: Jede Query MUSS eine ANDERE Strategie haben!
Query 1: Spezifische Suche - alle Kriterien kombiniert
Zweck: Exakte Suche nach allen Kriterien gleichzeitig
WHERE: (Netzgerät OR Netzteil OR Power Supply) AND (einphasig OR 1-phasig OR single phase) AND (10A OR 12A OR 15A OR 16A OR 18A OR 20A OR 25A OR 30A) AND (UL OR UL-zertifiziert)
Suche in: Artikelbezeichnung, Artikelbeschrieb, Keywords
Strategie: Alle Filter kombiniert
Query 2: Erweiterte Suche - breitere Ampere-Patterns + UL
Zweck: Breitere Suche nach Ampere-Angaben mit UL
WHERE: (Netzteil OR Netzgerät) AND (Ampere OR 10A OR 15A OR 20A OR 12A OR 16A OR 18A OR 25A OR 30A) AND (UL OR UL-zertifiziert)
Suche auch nach "Ampere" als Begriff (nicht nur Zahlen)
Strategie: Breitere Ampere-Patterns, weniger spezifisch
Query 3: Power Supply + single phase + UL (englische Varianten)
Zweck: Suche mit englischen Begriffen
WHERE: (Power Supply OR Stromversorgung) AND (single phase OR einphasig OR 1-phase) AND (10A OR 12A OR 15A OR 16A OR 18A OR 20A OR 25A OR 30A) AND (UL OR UL certified)
Strategie: Alternative Terminologie (englisch/deutsch)
Query 4: Breitere UL-Suche bei Netzgeräten
Zweck: UL-Suche ohne spezifische Ampere/Phasen-Filter
WHERE: (UL OR UL-zertifiziert OR UL certified) AND (Netzgerät OR Netzteil OR Power Supply OR Stromversorgung)
Suche auch in Keywords-Feld
Strategie: Nur UL + Netzgerät, keine weiteren Filter
Query 5: Netzgeräte mit 10A ohne UL-Filter
Zweck: Fallback falls UL nicht in DB erfasst
WHERE: (Netzgerät OR Netzteil) AND (einphasig OR 1-phasig OR single phase) AND (10A OR 12A OR 15A OR 16A OR 18A OR 20A OR 25A OR 30A)
Strategie: Entferne Zertifizierungsfilter komplett
Query 6: Zertifizierte Netzgeräte allgemein
Zweck: Breite Suche nach allen Zertifizierungen
WHERE: (UL OR CE OR TÜV OR certified OR zertifiziert) AND (Netzgerät OR Netzteil OR Power Supply)
Strategie: Alle Zertifizierungen, keine spezifischen Filter
Query 7: COUNT-Abfrage für Statistik
Zweck: Prüfe ob überhaupt Artikel existieren
SELECT COUNT(*) WHERE (Netzgerät OR Netzteil) AND (10A OR 12A OR 15A OR 16A OR 18A OR 20A OR 25A OR 30A)
Strategie: Statistik-Abfrage ohne spezifische Filter
Query 8: Spezifische Suche nach einphasigen Netzgeräten ohne Zertifizierung
Zweck: Fallback ohne Zertifizierungsfilter
WHERE: (1-Phasig OR einphasig OR single phase) AND (Netzgerät OR Netzteil OR Power Supply)
Strategie: Nur Phasen-Filter, keine Zertifizierung, keine Ampere
WICHTIG - DIESE 8 QUERIES SIND BEISPIEL
- Passe die Queries an die tatsächliche Anfrage an
- Verwende die gleichen Strategien, aber mit den richtigen Begriffen
- Jede Query muss eine ANDERE Strategie haben
- Erstelle MINDESTENS 8 Queries, auch wenn du mehr brauchst
VERBOTEN: Nur 1 Query zu erstellen - DAS IST ABSOLUT FALSCH!
VERBOTEN: Nur 2-3 Queries zu erstellen - DAS IST ZU WENIG!
OBLIGATORISCH: MINDESTENS 8 Queries bei Zertifizierungen!
KRITISCH - ALLE ERGEBNISSE ZURÜCKGEBEN
- OBLIGATORISCH: Du MUSST ALLE Artikel zurückgeben, die die Kriterien erfüllen
- VERBOTEN: Nur einen Artikel zurückgeben, wenn mehrere gefunden wurden
WICHTIG für SQL-Abfragen:
- Verwende IMMER doppelte Anführungszeichen für Spaltennamen
- Bei Lagerbestandsabfragen: IMMER S_RESERVIERTER__BESTAND, verfügbarer Bestand, JOIN mit Lagerplatz-Tabelle
- Bei Lagerbestandsabfragen: IMMER breite Suche mit OR über Artikelkürzel, Artikelnummer UND Artikelbezeichnung
- Filtere Lagerplätze mit 0 Bestand aus (außer bei Gesamtbestand oder spezifischem Lagerplatz)
- Abfragen müssen direkt ausführbar sein (keine Platzhalter)
ABSOLUT KRITISCH - JSON-VALIDIERUNG - MUSS BEVOR DU DAS JSON ZURÜCKGIBST
DU MUSST DIESE SCHRITTE BEVOR DU DAS JSON ZURÜCKGIBST AUSFÜHREN:
SCHRITT 1: Zähle die Anzahl der Queries im sqlQueries-Array
SCHRITT 2: Prüfe die Anzahl:
- Wenn needsDatabaseQuery = true UND weniger als 5 Queries: ERSTELLE SOFORT WEITERE QUERIES!
- Bei Zertifizierungen UND weniger als 8 Queries: ERSTELLE SOFORT WEITERE QUERIES!
- Wenn nur 1 Query im Array: DAS IST ABSOLUT FALSCH - ERSTELLE MINDESTENS 5-8 QUERIES!
- Wenn nur 2-3 Queries im Array: DAS IST ZU WENIG - ERSTELLE MINDESTENS 5-8 QUERIES!
SCHRITT 3: Prüfe, ob jede Query eine andere Strategie hat
SCHRITT 4: Wenn nicht genug Queries: ERSTELLE WEITERE QUERIES MIT ANDEREN STRATEGIEN!
SCHRITT 5: Wiederhole SCHRITT 1-4 bis du mindestens 5-8 (oder 8 bei Zertifizierungen) Queries hast
SCHRITT 6: ERST DANN gib das JSON zurück!
BEISPIEL FÜR KORREKTE ANZAHL VON QUERIES
Bei einer normalen Anfrage (z.B. "Netzgeräte mit 10A"):
- Query 1: Spezifische Suche mit allen Kriterien
- Query 2: Breitere Suche mit alternativen Begriffen
- Query 3: Suche nur nach Hauptkriterien
- Query 4: COUNT-Query für Statistik
- Query 5: Fallback-Query mit minimalen Filtern
MINDESTENS 5 Queries!
Bei Zertifizierungen (z.B. "UL-zertifizierte Netzgeräte"):
- Query 1-5: Wie oben
- Query 6: UL-Suche in Keywords
- Query 7: Zertifizierte Netzgeräte allgemein
- Query 8: Fallback ohne Zertifizierungsfilter
MINDESTENS 8 Queries!
WICHTIGE REGELN:
- Bei "mindestens X": Höhere Werte einschließen (z.B. "mindestens 10A" 10A OR 12A OR 15A OR 20A)
- Bei Zertifizierungen (UL, CE, TÜV, etc.): IMMER needsWebResearch = true setzen
- SQL: Doppelte Anführungszeichen für Spaltennamen, JOIN mit Lagerplatz bei Beständen
- Bei Lagerbeständen: Breite Suche über Artikelkürzel, Artikelnummer UND Artikelbezeichnung
Return ONLY valid JSON:
{{
@ -637,18 +534,9 @@ Return ONLY valid JSON:
"purpose": string (description of what this query retrieves),
"table": string (primary table name, e.g., "Artikel", "Lagerplatz_Artikel")
}}
] (array of query objects - MINDESTENS 5-8 bei komplexen Anfragen, MINDESTENS 8 bei Zertifizierungen!),
] (MAXIMAL 5 queries für Performance!),
"reasoning": string
}}
FINALE PRÜFUNG BEVOR DU DAS JSON ZURÜCKGIBST
1. Zähle die Anzahl der Queries im sqlQueries-Array: [ANZAHL]
2. Prüfe: Ist [ANZAHL] >= 5? (Bei Zertifizierungen: >= 8?)
3. Wenn NEIN: ERSTELLE SOFORT WEITERE QUERIES!
4. Prüfe: Hat jede Query eine andere Strategie?
5. Wenn NEIN: ERSTELLE QUERIES MIT ANDEREN STRATEGIEN!
6. Wiederhole bis [ANZAHL] >= 5 (oder >= 8 bei Zertifizierungen)
7. ERST DANN gib das JSON zurück!
"""
@ -721,15 +609,13 @@ def get_empty_results_retry_instructions(empty_count: int) -> str:
return ""
return f"""
KRITISCH - LEERE ERGEBNISSE ERKANNT
LEERE ERGEBNISSE ERKANNT
Es wurden {empty_count} Query(s) ausgeführt, die 0 Zeilen zurückgegeben haben. Die bisherige Query-Strategie war nicht erfolgreich.
Es wurden {empty_count} Query(s) ausgeführt, die 0 Zeilen zurückgegeben haben. Versuche alternative Strategien.
DU MUSST JETZT MEHRERE ALTERNATIVE QUERY-STRATEGIEN VERSUCHEN!
WICHTIG - MAXIMAL 5 QUERIES FÜR PERFORMANCE
OBLIGATORISCH - ERSTELLE MINDESTENS 5-8 ALTERNATIVE QUERIES
Erstelle mehrere alternative SQL-Queries mit komplett anderen Strategien:
Erstelle MAXIMAL 5 alternative SQL-Queries mit komplett anderen Strategien:
1. **Breitere Suche ohne Zertifizierung**: Entferne Zertifizierungsfilter komplett
- Beispiel: Suche nur nach Netzgerät + einphasig + 10A (ohne UL)
@ -760,7 +646,7 @@ Erstelle mehrere alternative SQL-Queries mit komplett anderen Strategien:
- Beispiel: Netzgerät AND (10A OR 15A OR 20A) - keine weiteren Filter
WICHTIG:
- Erstelle IMMER mehrere Queries (mindestens 5-8) mit unterschiedlichen Strategien
- Erstelle MAXIMAL 5 Queries mit unterschiedlichen Strategien (für Performance)
- Verwende breitere OR-Bedingungen für alternative Begriffe
- Entferne zu spezifische Filter, die möglicherweise keine Treffer finden
- Suche in Artikelbezeichnung, Artikelbeschrieb UND Keywords-Feld

View file

@ -229,7 +229,7 @@ async def chatProcess(
async def _execute_queries_parallel(queries: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Execute multiple SQL queries in parallel.
Execute multiple SQL queries in parallel with shared connector.
Args:
queries: List of query dictionaries, each containing:
@ -243,21 +243,24 @@ async def _execute_queries_parallel(queries: List[Dict[str, Any]]) -> Dict[str,
- "query_1_data", "query_2_data", etc.: Raw data arrays
- "query_1_error", "query_2_error", etc.: Error messages if query failed
"""
async def execute_single_query(idx: int, query_info: Dict[str, Any]):
"""Execute a single query and return result."""
connector = PreprocessorConnector()
try:
query_text = query_info.get("query", "")
result = await connector.executeQuery(query_text, return_json=True)
await connector.close()
return idx, result, None
except Exception as e:
await connector.close()
return idx, None, str(e)
# Execute all queries in parallel
tasks = [execute_single_query(i, q) for i, q in enumerate(queries)]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Create single connector instance to reuse across all queries
connector = PreprocessorConnector()
try:
async def execute_single_query(idx: int, query_info: Dict[str, Any]):
"""Execute a single query using shared connector."""
try:
query_text = query_info.get("query", "")
result = await connector.executeQuery(query_text, return_json=True)
return idx, result, None
except Exception as e:
return idx, None, str(e)
# Execute all queries in parallel with shared connector
tasks = [execute_single_query(i, q) for i, q in enumerate(queries)]
results = await asyncio.gather(*tasks, return_exceptions=True)
finally:
# Close connector once after all queries complete
await connector.close()
# Process results into dictionary
query_results = {}
@ -915,108 +918,11 @@ async def _processChatbotMessage(
logger.warning("Certification detected but needsWebResearch is false - forcing to true")
needsWebResearch = True
# Validate query count - retry if too few queries (iterative retry up to 3 attempts)
min_queries_required = 8 if has_certification else 5
max_retry_attempts = 3
retry_attempt = 0
while needsDatabaseQuery and len(sql_queries) < min_queries_required and retry_attempt < max_retry_attempts:
retry_attempt += 1
logger.warning(f"Only {len(sql_queries)} queries created, but {min_queries_required} required. Retry attempt {retry_attempt}/{max_retry_attempts}...")
await _emit_log_and_event(
interfaceDbChat,
workflowId,
event_manager,
f"Zu wenige Abfragen erstellt ({len(sql_queries)} statt {min_queries_required}). Versuch {retry_attempt}/{max_retry_attempts}: Erstelle alternative Strategien...",
log_type="warning"
)
# Build progressively stronger retry prompt
retry_context = f"{context}\n\n"
if retry_attempt == 1:
retry_context += "⚠️⚠️⚠️ KRITISCH - ZU WENIGE ABFRAGEN ERSTELLT ⚠️⚠️⚠️\n"
elif retry_attempt == 2:
retry_context += "⚠️⚠️⚠️ ABSOLUT KRITISCH - IMMER NOCH ZU WENIGE ABFRAGEN ⚠️⚠️⚠️\n"
else:
retry_context += "⚠️⚠️⚠️ LETZTER VERSUCH - DU MUSST JETZT MINDESTENS 5-8 ABFRAGEN ERSTELLEN ⚠️⚠️⚠️\n"
retry_context += f"Du hast nur {len(sql_queries)} Abfrage(n) erstellt, aber es werden MINDESTENS {min_queries_required} benötigt!\n"
retry_context += f"Dies ist bereits Versuch {retry_attempt} von {max_retry_attempts}!\n"
retry_context += "ERSTELLE JETZT MINDESTENS 5-8 ABFRAGEN MIT VERSCHIEDENEN STRATEGIEN!\n"
retry_context += "JEDE Query muss eine ANDERE Strategie verfolgen:\n"
retry_context += "- Query 1: Spezifische Suche mit allen Kriterien\n"
retry_context += "- Query 2: Breitere Suche mit alternativen Begriffen\n"
retry_context += "- Query 3: Suche ohne Zertifizierungsfilter\n"
retry_context += "- Query 4: Suche nur nach Hauptkriterien\n"
retry_context += "- Query 5: COUNT-Query für Statistik\n"
if has_certification:
retry_context += "- Query 6: UL-Suche in Keywords\n"
retry_context += "- Query 7: Zertifizierte Netzgeräte allgemein\n"
retry_context += "- Query 8: Fallback mit minimalen Filtern\n"
retry_context += "\n⚠️ ABSOLUT VERBOTEN: Nur 1 Query zu erstellen! ⚠️\n"
retry_context += "⚠️ ABSOLUT VERBOTEN: Alle Queries mit derselben Strategie! ⚠️\n"
retry_context += "⚠️ BEVOR DU DAS JSON ZURÜCKGIBST: Zähle die Queries im sqlQueries-Array! ⚠️\n"
retry_context += "⚠️ WENN WENIGER ALS 8: ERSTELLE WEITERE QUERIES! ⚠️\n"
retry_analysis_prompt = get_initial_analysis_prompt(userInput.prompt, retry_context)
retry_analysis_result = await method_ai.process({
"aiPrompt": retry_analysis_prompt,
"documentList": None,
"resultType": "json",
"simpleMode": True
})
retry_analysis_content = None
if retry_analysis_result.success and retry_analysis_result.documents:
retry_analysis_content = retry_analysis_result.documents[0].documentData
if isinstance(retry_analysis_content, bytes):
retry_analysis_content = retry_analysis_content.decode('utf-8')
if retry_analysis_content:
retry_analysis = _extractJsonFromResponse(retry_analysis_content)
if retry_analysis:
retry_needsDatabaseQuery = retry_analysis.get("needsDatabaseQuery", False)
retry_needsWebResearch = retry_analysis.get("needsWebResearch", False)
retry_sql_queries = retry_analysis.get("sqlQueries", [])
logger.info(f"Retry attempt {retry_attempt}: Got {len(retry_sql_queries)} queries (required: {min_queries_required})")
if retry_needsDatabaseQuery and len(retry_sql_queries) >= min_queries_required:
logger.info(f"Retry successful: {len(retry_sql_queries)} queries created")
needsDatabaseQuery = retry_needsDatabaseQuery
needsWebResearch = retry_needsWebResearch or needsWebResearch # Keep web research if already set
sql_queries = retry_sql_queries
reasoning = retry_analysis.get("reasoning", reasoning)
await _emit_log_and_event(
interfaceDbChat,
workflowId,
event_manager,
f"Alternative Strategie erfolgreich: {len(sql_queries)} Abfrage(n) erstellt",
log_type="info"
)
break # Success, exit retry loop
else:
logger.warning(f"Retry attempt {retry_attempt} still insufficient: {len(retry_sql_queries)} queries (required: {min_queries_required})")
# Update sql_queries even if insufficient, so next iteration has context
if retry_needsDatabaseQuery and len(retry_sql_queries) > len(sql_queries):
sql_queries = retry_sql_queries
needsDatabaseQuery = retry_needsDatabaseQuery
needsWebResearch = retry_needsWebResearch or needsWebResearch
else:
logger.warning(f"Retry attempt {retry_attempt}: Failed to parse JSON response")
else:
logger.warning(f"Retry attempt {retry_attempt}: No content in response")
# Final check: if still insufficient after all retries, log warning but continue
if needsDatabaseQuery and len(sql_queries) < min_queries_required:
logger.error(f"CRITICAL: After {max_retry_attempts} retry attempts, only {len(sql_queries)} queries created (required: {min_queries_required}). Continuing with insufficient queries.")
await _emit_log_and_event(
interfaceDbChat,
workflowId,
event_manager,
f"⚠️ WARNUNG: Nach {max_retry_attempts} Versuchen nur {len(sql_queries)} Abfrage(n) erstellt (benötigt: {min_queries_required}). Setze mit reduzierten Abfragen fort.",
log_type="warning"
)
# Limit query count to maximum 5 for performance
max_queries_allowed = 5
if needsDatabaseQuery and len(sql_queries) > max_queries_allowed:
logger.info(f"Limiting queries from {len(sql_queries)} to {max_queries_allowed} for performance")
sql_queries = sql_queries[:max_queries_allowed]
logger.info(f"Analysis: DB={needsDatabaseQuery}, Web={needsWebResearch}, SQL queries={len(sql_queries)}")
@ -1078,6 +984,32 @@ async def _processChatbotMessage(
queryResults = {}
webResearchResults = ""
# Start web research early in parallel with DB queries if needed
web_research_task = None
if needsWebResearch:
# Start with basic query (will enrich later with DB results if available)
basic_web_query = _buildWebResearchQuery(userInput.prompt, workflow.messages, None)
logger.info(f"Starting web research in parallel with DB queries using basic query: '{basic_web_query}'")
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Suche im Internet nach Informationen...")
async def perform_web_research():
"""Perform web research and return results."""
try:
researchResult = await services.web.performWebResearch(
prompt=basic_web_query,
urls=[],
country=None,
language=userInput.userLanguage or "de",
researchDepth="general",
operationId=None
)
return json.dumps(researchResult, ensure_ascii=False, indent=2) if isinstance(researchResult, dict) else str(researchResult)
except Exception as e:
logger.error(f"Web research failed: {e}", exc_info=True)
return f"Web research error: {str(e)}"
web_research_task = asyncio.create_task(perform_web_research())
# Execute database queries in parallel
if needsDatabaseQuery and sql_queries:
logger.info(f"Executing {len(sql_queries)} database queries in parallel...")
@ -1151,10 +1083,11 @@ async def _processChatbotMessage(
(len(successful_queries) > 0 or len(failed_queries) == 0) # Either we have successful queries or no failures (queries executed but empty)
)
# Iterative retry loop: try up to 3 times with different strategies
max_empty_retry_attempts = 3
# Iterative retry loop: try up to 2 times with different strategies
max_empty_retry_attempts = 2
empty_retry_attempt = 0
original_sql_queries_count = len(sql_queries)
previous_retry_rows = 0
while should_retry and empty_retry_attempt < max_empty_retry_attempts:
empty_retry_attempt += 1
@ -1188,7 +1121,7 @@ async def _processChatbotMessage(
retry_context += f"Die bisherigen {len(sql_queries)} Abfragen haben 0 Zeilen zurückgegeben.\n"
retry_context += f"{empty_results_instructions}\n"
retry_context += f"Dies ist bereits Versuch {empty_retry_attempt} von {max_empty_retry_attempts}!\n"
retry_context += "Erstelle JETZT mehrere alternative SQL-Queries (mindestens 5-8) mit komplett anderen Strategien:\n"
retry_context += "Erstelle JETZT MAXIMAL 5 alternative SQL-Queries mit komplett anderen Strategien (für Performance):\n"
if empty_retry_attempt == 1:
retry_context += "- Breitere Suche ohne zu spezifische Filter\n"
@ -1230,6 +1163,10 @@ async def _processChatbotMessage(
retry_analysis = _extractJsonFromResponse(retry_analysis_content)
if retry_analysis and retry_analysis.get("needsDatabaseQuery", False):
retry_sql_queries = retry_analysis.get("sqlQueries", [])
# Limit to maximum 5 queries for performance
if len(retry_sql_queries) > 5:
logger.info(f"Limiting retry queries from {len(retry_sql_queries)} to 5 for performance")
retry_sql_queries = retry_sql_queries[:5]
if retry_sql_queries:
logger.info(f"Executing {len(retry_sql_queries)} retry queries (attempt {empty_retry_attempt}) with alternative strategies...")
await _emit_log_and_event(
@ -1287,20 +1224,32 @@ async def _processChatbotMessage(
)
should_retry = False # Stop retry loop, we found results
break
else:
# Still no results, continue to next attempt
elif retry_rows > previous_retry_rows:
# Made some progress (found more rows than before) - continue
previous_retry_rows = retry_rows
await _emit_log_and_event(
interfaceDbChat,
workflowId,
event_manager,
f"Versuch {empty_retry_attempt}: Immer noch keine Ergebnisse. Versuche nächste Strategie...",
f"Versuch {empty_retry_attempt}: Fortschritt erzielt ({retry_rows} Zeilen gefunden). Versuche weitere Strategie...",
log_type="info"
)
else:
# No progress made - stop retrying
await _emit_log_and_event(
interfaceDbChat,
workflowId,
event_manager,
f"Versuch {empty_retry_attempt}: Keine Ergebnisse gefunden. Beende Retry-Versuche.",
log_type="warning"
)
should_retry = False # Stop retry loop, no progress
break
except Exception as retry_error:
logger.error(f"Error executing retry queries (attempt {empty_retry_attempt}): {retry_error}", exc_info=True)
# Continue to next attempt even on error
# Check if we should continue retrying
# Check if we should continue retrying (already handled in break conditions above)
if empty_retry_attempt >= max_empty_retry_attempts:
logger.warning(f"Reached maximum empty retry attempts ({max_empty_retry_attempts}), stopping retry loop")
await _emit_log_and_event(
@ -1322,33 +1271,18 @@ async def _processChatbotMessage(
log_type="error"
)
# Execute web research
if needsWebResearch:
logger.info("Performing web research...")
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Suche im Internet nach Informationen...")
# Wait for web research to complete (if it was started in parallel)
if web_research_task:
try:
# Rebuild enriched query with database results if available (better product context)
web_research_query = _buildWebResearchQuery(
userInput.prompt,
workflow.messages,
queryResults if queryResults else None
)
logger.info(f"Using enriched web research query: '{web_research_query}'")
researchResult = await services.web.performWebResearch(
prompt=web_research_query,
urls=[],
country=None,
language=userInput.userLanguage or "de",
researchDepth="general",
operationId=None
)
webResearchResults = json.dumps(researchResult, ensure_ascii=False, indent=2) if isinstance(researchResult, dict) else str(researchResult)
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Internet-Recherche abgeschlossen")
webResearchResults = await web_research_task
if webResearchResults and not webResearchResults.startswith("Web research error"):
logger.info("Web research completed successfully")
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Internet-Recherche abgeschlossen")
else:
logger.warning("Web research completed with errors")
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Internet-Recherche fehlgeschlagen", log_type="warning")
except Exception as e:
logger.error(f"Web research failed: {e}", exc_info=True)
logger.error(f"Error waiting for web research: {e}", exc_info=True)
webResearchResults = f"Web research error: {str(e)}"
await _emit_log_and_event(interfaceDbChat, workflowId, event_manager, "Internet-Recherche fehlgeschlagen", log_type="warning")
@ -1364,11 +1298,11 @@ async def _processChatbotMessage(
# Build prompt for final answer
system_prompt = get_final_answer_system_prompt()
# Build answer context with query results
answerContext = f"User question: {userInput.prompt}{context}\n\n"
# Build answer context with query results using efficient list-based building
answer_context_parts = [f"User question: {userInput.prompt}{context}\n"]
# Add database results - organize by query with metadata
db_results_part = ""
db_results_parts = []
if queryResults:
successful_results = []
error_results = []
@ -1415,19 +1349,31 @@ async def _processChatbotMessage(
if "error" in queryResults:
error_results.append(f"Allgemeiner Fehler: {queryResults['error']}")
# Build db_results_part efficiently
if successful_results:
db_results_part = "\n\nDATENBANK-ERGEBNISSE:\n" + "\n\n".join(successful_results)
answerContext += "DATENBANK-ERGEBNISSE:\n" + "\n\n".join(successful_results) + "\n\n"
db_results_parts.append("\n\nDATENBANK-ERGEBNISSE:\n")
db_results_parts.append("\n\n".join(successful_results))
answer_context_parts.append("DATENBANK-ERGEBNISSE:\n")
answer_context_parts.append("\n\n".join(successful_results))
answer_context_parts.append("\n")
if error_results:
db_results_part += "\n\nDATENBANK-FEHLER:\n" + "\n".join(error_results)
answerContext += "DATENBANK-FEHLER:\n" + "\n".join(error_results) + "\n\n"
db_results_parts.append("\n\nDATENBANK-FEHLER:\n")
db_results_parts.append("\n".join(error_results))
answer_context_parts.append("DATENBANK-FEHLER:\n")
answer_context_parts.append("\n".join(error_results))
answer_context_parts.append("\n")
db_results_part = "".join(db_results_parts)
# Add web research results
web_results_part = ""
if webResearchResults:
web_results_part = f"\n\nINTERNET-RECHERCHE:\n{webResearchResults}"
answerContext += f"INTERNET-RECHERCHE:\n{webResearchResults}\n\n"
answer_context_parts.append(f"INTERNET-RECHERCHE:\n{webResearchResults}\n")
# Join answer context efficiently
answerContext = "".join(answer_context_parts)
# Check if we have any actual data
successful_query_keys = [k for k in queryResults.keys() if k.startswith("query_") and not k.endswith("_error") and not k.endswith("_data")]
@ -1447,39 +1393,42 @@ async def _processChatbotMessage(
logger.info(f"Total articles found across all queries: {total_articles_found}")
# Add explicit article count information to prompt
# Add explicit article count information to prompt (using efficient list building)
if total_articles_found > 0:
article_count_info = f"\n\n⚠️⚠️⚠️ WICHTIG - ARTIKELANZAHL ⚠️⚠️⚠️\n"
article_count_info += f"In den DATENBANK-ERGEBNISSEN oben wurden INSGESAMT {total_articles_found} Artikel gefunden.\n"
article_count_info += f"DU MUSST ALLE {total_articles_found} Artikel in deiner Antwort zeigen!\n"
article_count_parts = [
"\n\n⚠️⚠️⚠️ WICHTIG - ARTIKELANZAHL ⚠️⚠️⚠️\n",
f"In den DATENBANK-ERGEBNISSEN oben wurden INSGESAMT {total_articles_found} Artikel gefunden.\n",
f"DU MUSST ALLE {total_articles_found} Artikel in deiner Antwort zeigen!\n"
]
if total_articles_found <= 20:
article_count_info += f"Zeige ALLE {total_articles_found} Artikel in einer Tabelle.\n"
article_count_parts.append(f"Zeige ALLE {total_articles_found} Artikel in einer Tabelle.\n")
else:
article_count_info += f"Zeige die ersten 20 Artikel in einer Tabelle + Hinweis auf weitere {total_articles_found - 20} Artikel.\n"
article_count_info += f"❌ VERBOTEN: Nur einen Artikel zu zeigen, wenn {total_articles_found} gefunden wurden!\n"
article_count_info += f"✓ OBLIGATORISCH: Zeige ALLE {total_articles_found} Artikel!\n"
article_count_parts.append(f"Zeige die ersten 20 Artikel in einer Tabelle + Hinweis auf weitere {total_articles_found - 20} Artikel.\n")
article_count_parts.extend([
f"❌ VERBOTEN: Nur einen Artikel zu zeigen, wenn {total_articles_found} gefunden wurden!\n",
f"✓ OBLIGATORISCH: Zeige ALLE {total_articles_found} Artikel!\n"
])
article_count_info = "".join(article_count_parts)
if db_results_part:
db_results_part = article_count_info + db_results_part
else:
db_results_part = article_count_info
# Add warning messages if needed
# Add warning messages if needed (using efficient list building)
warning_parts = []
if not has_query_results and needsDatabaseQuery:
if db_results_part:
db_results_part += "\n\nWICHTIG: Es wurden KEINE Datenbank-Ergebnisse gefunden. Die Datenbankabfrage wurde nicht ausgeführt oder hat keine Ergebnisse zurückgegeben."
else:
db_results_part = "\n\nWICHTIG: Es wurden KEINE Datenbank-Ergebnisse gefunden. Die Datenbankabfrage wurde nicht ausgeführt oder hat keine Ergebnisse zurückgegeben."
warning_parts.append("\n\nWICHTIG: Es wurden KEINE Datenbank-Ergebnisse gefunden. Die Datenbankabfrage wurde nicht ausgeführt oder hat keine Ergebnisse zurückgegeben.")
if has_only_errors:
if db_results_part:
db_results_part += "\n\n⚠️⚠️⚠️ KRITISCH - ALLE QUERIES FEHLGESCHLAGEN ⚠️⚠️⚠️\n" + \
"ALLE Datenbankabfragen sind fehlgeschlagen. Es gibt KEINE gültigen Daten aus der Datenbank.\n" + \
"DU DARFST KEINE DATEN ERFINDEN! Schreibe stattdessen: 'Es wurden keine Artikel gefunden' oder 'Die Datenbankabfrage ist fehlgeschlagen'."
else:
db_results_part = "\n\n⚠️⚠️⚠️ KRITISCH - ALLE QUERIES FEHLGESCHLAGEN ⚠️⚠️⚠️\n" + \
"ALLE Datenbankabfragen sind fehlgeschlagen. Es gibt KEINE gültigen Daten aus der Datenbank.\n" + \
"DU DARFST KEINE DATEN ERFINDEN! Schreibe stattdessen: 'Es wurden keine Artikel gefunden' oder 'Die Datenbankabfrage ist fehlgeschlagen'."
warning_parts.extend([
"\n\n⚠️⚠️⚠️ KRITISCH - ALLE QUERIES FEHLGESCHLAGEN ⚠️⚠️⚠️\n",
"ALLE Datenbankabfragen sind fehlgeschlagen. Es gibt KEINE gültigen Daten aus der Datenbank.\n",
"DU DARFST KEINE DATEN ERFINDEN! Schreibe stattdessen: 'Es wurden keine Artikel gefunden' oder 'Die Datenbankabfrage ist fehlgeschlagen'."
])
if warning_parts:
db_results_part = db_results_part + "".join(warning_parts) if db_results_part else "".join(warning_parts)
# Use the function from constants file to build the prompt
answer_prompt = get_final_answer_prompt_with_results(