Update components for WP-25b: Implement Lazy-Prompt-Orchestration across ingestion, decision engine, chat interface, and LLM service. Enhance prompt management with hierarchical model support and streamline response generation by removing manual formatting. Bump versions to reflect new features and optimizations.
This commit is contained in:
parent
d41da670fc
commit
7026fc4fed
|
|
@ -1,13 +1,13 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/core/ingestion/ingestion_validation.py
|
FILE: app/core/ingestion/ingestion_validation.py
|
||||||
DESCRIPTION: WP-15b semantische Validierung von Kanten gegen den LocalBatchCache.
|
DESCRIPTION: WP-15b semantische Validierung von Kanten gegen den LocalBatchCache.
|
||||||
WP-25a: Integration der Mixture of Experts (MoE) Profil-Steuerung.
|
WP-25b: Umstellung auf Lazy-Prompt-Orchestration (prompt_key + variables).
|
||||||
VERSION: 2.13.0 (WP-25a: MoE & Profile Support)
|
VERSION: 2.14.0 (WP-25b: Lazy Prompt Integration)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
- Umstellung auf generate_raw_response mit profile_name="ingest_validator".
|
- WP-25b: Entfernung manueller Prompt-Formatierung zur Unterstützung modell-spezifischer Prompts.
|
||||||
- Automatische Nutzung der Fallback-Kaskade (Cloud -> Lokal) via LLMService.
|
- WP-25b: Umstellung auf generate_raw_response mit prompt_key="edge_validation".
|
||||||
- Erhalt der sparsamen LLM-Nutzung (Validierung nur für Kandidaten-Pool).
|
- WP-25a: Voller Erhalt der MoE-Profilsteuerung und Fallback-Kaskade via LLMService.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
@ -27,8 +27,8 @@ async def validate_edge_candidate(
|
||||||
profile_name: str = "ingest_validator"
|
profile_name: str = "ingest_validator"
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
WP-15b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
WP-15b/25b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
||||||
Nutzt das MoE-Profil 'ingest_validator' für deterministische YES/NO Prüfungen.
|
Nutzt Lazy-Prompt-Loading zur Unterstützung modell-spezifischer Validierungs-Templates.
|
||||||
"""
|
"""
|
||||||
target_id = edge.get("to")
|
target_id = edge.get("to")
|
||||||
target_ctx = batch_cache.get(target_id)
|
target_ctx = batch_cache.get(target_id)
|
||||||
|
|
@ -44,27 +44,25 @@ async def validate_edge_candidate(
|
||||||
logger.info(f"ℹ️ [VALIDATION SKIP] No context for '{target_id}' - allowing link.")
|
logger.info(f"ℹ️ [VALIDATION SKIP] No context for '{target_id}' - allowing link.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Prompt-Abruf (Nutzt Provider-String als Fallback-Key für die prompts.yaml)
|
|
||||||
template = llm_service.get_prompt("edge_validation", provider)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"⚖️ [VALIDATING] Relation '{edge.get('kind')}' -> '{target_id}' (Profile: {profile_name})...")
|
logger.info(f"⚖️ [VALIDATING] Relation '{edge.get('kind')}' -> '{target_id}' (Profile: {profile_name})...")
|
||||||
prompt = template.format(
|
|
||||||
chunk_text=chunk_text[:1500],
|
|
||||||
target_title=target_ctx.title,
|
|
||||||
target_summary=target_ctx.summary,
|
|
||||||
edge_kind=edge.get("kind", "related_to")
|
|
||||||
)
|
|
||||||
|
|
||||||
# WP-25a: Profilbasierter Aufruf (Delegiert Fallbacks an den Service)
|
# WP-25b: Lazy-Prompt Aufruf.
|
||||||
# Nutzt ingest_validator (Cloud Mistral/Gemini -> Local Phi3:mini Kaskade)
|
# Wir übergeben keine formatierte Nachricht mehr, sondern Key und Daten-Dict.
|
||||||
|
# Das manuelle 'template = llm_service.get_prompt(...)' entfällt hier.
|
||||||
raw_response = await llm_service.generate_raw_response(
|
raw_response = await llm_service.generate_raw_response(
|
||||||
prompt,
|
prompt_key="edge_validation",
|
||||||
|
variables={
|
||||||
|
"chunk_text": chunk_text[:1500],
|
||||||
|
"target_title": target_ctx.title,
|
||||||
|
"target_summary": target_ctx.summary,
|
||||||
|
"edge_kind": edge.get("kind", "related_to")
|
||||||
|
},
|
||||||
priority="background",
|
priority="background",
|
||||||
profile_name=profile_name
|
profile_name=profile_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# WP-14 Fix: Zusätzliche Bereinigung zur Sicherstellung der Interpretierbarkeit
|
# WP-14 Fix: Bereinigung zur Sicherstellung der Interpretierbarkeit
|
||||||
response = clean_llm_text(raw_response)
|
response = clean_llm_text(raw_response)
|
||||||
|
|
||||||
# Semantische Prüfung des Ergebnisses
|
# Semantische Prüfung des Ergebnisses
|
||||||
|
|
@ -75,6 +73,7 @@ async def validate_edge_candidate(
|
||||||
else:
|
else:
|
||||||
logger.info(f"🚫 [REJECTED] Relation to '{target_id}' irrelevant for this chunk.")
|
logger.info(f"🚫 [REJECTED] Relation to '{target_id}' irrelevant for this chunk.")
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Validation error for {target_id} using {profile_name}: {e}")
|
logger.warning(f"⚠️ Validation error for {target_id} using {profile_name}: {e}")
|
||||||
# Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden
|
# Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/core/retrieval/decision_engine.py
|
FILE: app/core/retrieval/decision_engine.py
|
||||||
DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25a Edition).
|
DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25b Edition).
|
||||||
Realisiert Multi-Stream Retrieval, Intent-basiertes Routing
|
Realisiert Multi-Stream Retrieval, Intent-basiertes Routing
|
||||||
und die neue Pre-Synthesis Kompression (Module A).
|
und die neue Lazy-Prompt Orchestrierung (Module A & B).
|
||||||
VERSION: 1.2.1 (WP-25a: Profile-Driven Orchestration & Optimized Cascade)
|
VERSION: 1.3.0 (WP-25b: Lazy Prompt Orchestration)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
- WP-25a: Volle Integration der Profil-Kaskade (Delegation an LLMService v3.5.2).
|
- WP-25b: Umstellung auf Lazy-Loading (Übergabe von prompt_key + variables).
|
||||||
- WP-25a: Dynamische Nutzung des 'router_profile' für die Intent-Erkennung.
|
- WP-25b: Entfernung lokaler String-Formatierung zur Ermöglichung modell-spezifischer Prompts.
|
||||||
- WP-25a: Parallelisierte Kompression überlanger Wissens-Streams.
|
- WP-25a: Volle Integration der Profil-Kaskade via LLMService v3.5.5.
|
||||||
- WP-25: Beibehaltung von Stream-Tracing und Pre-Initialization Robustness.
|
- WP-25: Beibehaltung von Stream-Tracing und Pre-Initialization Robustness.
|
||||||
- CLEANUP: Entfernung redundanter Fallback-Blocks (jetzt im LLMService).
|
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -70,29 +69,27 @@ class DecisionEngine:
|
||||||
if not strategy:
|
if not strategy:
|
||||||
return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert."
|
return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert."
|
||||||
|
|
||||||
# 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks)
|
# 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks inkl. Kompression)
|
||||||
# WP-25a: Diese Methode übernimmt nun auch die Kompression.
|
|
||||||
stream_results = await self._execute_parallel_streams(strategy, query)
|
stream_results = await self._execute_parallel_streams(strategy, query)
|
||||||
|
|
||||||
# 3. Finale Synthese
|
# 3. Finale Synthese
|
||||||
return await self._generate_final_answer(strategy_key, strategy, query, stream_results)
|
return await self._generate_final_answer(strategy_key, strategy, query, stream_results)
|
||||||
|
|
||||||
async def _determine_strategy(self, query: str) -> str:
|
async def _determine_strategy(self, query: str) -> str:
|
||||||
"""Nutzt den LLM-Router zur Wahl der Such-Strategie via router_profile."""
|
"""WP-25b: Nutzt den LLM-Router via Lazy-Loading prompt_key."""
|
||||||
settings_cfg = self.config.get("settings", {})
|
settings_cfg = self.config.get("settings", {})
|
||||||
prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1")
|
prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1")
|
||||||
# WP-25a: Nutzt das spezialisierte Profil für das Routing
|
|
||||||
router_profile = settings_cfg.get("router_profile")
|
router_profile = settings_cfg.get("router_profile")
|
||||||
|
|
||||||
router_prompt_template = self.llm_service.get_prompt(prompt_key)
|
|
||||||
if not router_prompt_template:
|
|
||||||
return "FACT_WHAT"
|
|
||||||
|
|
||||||
full_prompt = router_prompt_template.format(query=query)
|
|
||||||
try:
|
try:
|
||||||
# Der LLMService übernimmt hier über das Profil bereits die Fallback-Kaskade
|
# WP-25b: Keine manuelle Formatierung mehr. Wir übergeben nur Key und Variablen.
|
||||||
|
# Der LLMService wählt den passenden Prompt für das router_profile Modell.
|
||||||
response = await self.llm_service.generate_raw_response(
|
response = await self.llm_service.generate_raw_response(
|
||||||
full_prompt, max_retries=1, priority="realtime", profile_name=router_profile
|
prompt_key=prompt_key,
|
||||||
|
variables={"query": query},
|
||||||
|
max_retries=1,
|
||||||
|
priority="realtime",
|
||||||
|
profile_name=router_profile
|
||||||
)
|
)
|
||||||
return str(response).strip().upper()
|
return str(response).strip().upper()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -102,7 +99,7 @@ class DecisionEngine:
|
||||||
async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]:
|
async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis).
|
Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis).
|
||||||
WP-25a: MoE-Profile werden für die Kompression berücksichtigt.
|
WP-25b: Unterstützt Lazy-Compression über Experten-Profile.
|
||||||
"""
|
"""
|
||||||
stream_keys = strategy.get("use_streams", [])
|
stream_keys = strategy.get("use_streams", [])
|
||||||
library = self.config.get("streams_library", {})
|
library = self.config.get("streams_library", {})
|
||||||
|
|
@ -116,7 +113,7 @@ class DecisionEngine:
|
||||||
active_streams.append(key)
|
active_streams.append(key)
|
||||||
retrieval_tasks.append(self._run_single_stream(key, stream_cfg, query))
|
retrieval_tasks.append(self._run_single_stream(key, stream_cfg, query))
|
||||||
|
|
||||||
# Ergebnisse sammeln (Exceptions werden als Objekte zurückgegeben)
|
# Ergebnisse sammeln
|
||||||
retrieval_results = await asyncio.gather(*retrieval_tasks, return_exceptions=True)
|
retrieval_results = await asyncio.gather(*retrieval_tasks, return_exceptions=True)
|
||||||
|
|
||||||
# Phase 2: Formatierung und optionale Kompression
|
# Phase 2: Formatierung und optionale Kompression
|
||||||
|
|
@ -137,38 +134,32 @@ class DecisionEngine:
|
||||||
threshold = stream_cfg.get("compression_threshold", 4000)
|
threshold = stream_cfg.get("compression_threshold", 4000)
|
||||||
|
|
||||||
if len(formatted_context) > threshold:
|
if len(formatted_context) > threshold:
|
||||||
logger.info(f"⚙️ [WP-25a] Compressing stream '{name}' ({len(formatted_context)} chars)...")
|
logger.info(f"⚙️ [WP-25b] Triggering Lazy-Compression for stream '{name}'...")
|
||||||
comp_profile = stream_cfg.get("compression_profile")
|
comp_profile = stream_cfg.get("compression_profile")
|
||||||
final_stream_tasks.append(
|
final_stream_tasks.append(
|
||||||
self._compress_stream_content(name, formatted_context, query, comp_profile)
|
self._compress_stream_content(name, formatted_context, query, comp_profile)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Direkt-Übernahme als Coroutine für gather()
|
|
||||||
async def _direct(c=formatted_context): return c
|
async def _direct(c=formatted_context): return c
|
||||||
final_stream_tasks.append(_direct())
|
final_stream_tasks.append(_direct())
|
||||||
|
|
||||||
# Finale Inhalte (evtl. komprimiert) parallel fertigstellen
|
# Finale Inhalte parallel fertigstellen
|
||||||
final_contents = await asyncio.gather(*final_stream_tasks)
|
final_contents = await asyncio.gather(*final_stream_tasks)
|
||||||
|
|
||||||
return dict(zip(active_streams, final_contents))
|
return dict(zip(active_streams, final_contents))
|
||||||
|
|
||||||
async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str:
|
async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str:
|
||||||
"""
|
"""WP-25b Module A: Inhaltsverdichtung via Lazy-Loading 'compression_template'."""
|
||||||
WP-25a Module A: Inhaltsverdichtung via Experten-Modell.
|
|
||||||
"""
|
|
||||||
compression_prompt = (
|
|
||||||
f"Du bist ein Wissens-Analyst. Reduziere den folgenden Wissens-Stream '{stream_name}' "
|
|
||||||
f"auf die Informationen, die für die Beantwortung der Frage '{query}' absolut notwendig sind.\n\n"
|
|
||||||
f"BEIBEHALTEN: Harte Fakten, Projektnamen, konkrete Werte und Quellenangaben.\n"
|
|
||||||
f"ENTFERNEN: Redundante Einleitungen, Füllwörter und irrelevante Details.\n\n"
|
|
||||||
f"STREAM-INHALT:\n{content}\n\n"
|
|
||||||
f"KOMPRIMIERTE ANALYSE:"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# WP-25b: Wir übergeben den Auftrag an den LLMService.
|
||||||
|
# Das Modell-spezifische Template wird erst beim Call aufgelöst.
|
||||||
summary = await self.llm_service.generate_raw_response(
|
summary = await self.llm_service.generate_raw_response(
|
||||||
compression_prompt,
|
prompt_key="compression_template",
|
||||||
profile_name=profile, # WP-25a: MoE Support
|
variables={
|
||||||
|
"stream_name": stream_name,
|
||||||
|
"content": content,
|
||||||
|
"query": query
|
||||||
|
},
|
||||||
|
profile_name=profile,
|
||||||
priority="background",
|
priority="background",
|
||||||
max_retries=1
|
max_retries=1
|
||||||
)
|
)
|
||||||
|
|
@ -191,24 +182,19 @@ class DecisionEngine:
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await self.retriever.search(request)
|
response = await self.retriever.search(request)
|
||||||
|
|
||||||
# WP-25: STREAM-TRACING
|
|
||||||
for hit in response.results:
|
for hit in response.results:
|
||||||
hit.stream_origin = name
|
hit.stream_origin = name
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _format_stream_context(self, response: QueryResponse) -> str:
|
def _format_stream_context(self, response: QueryResponse) -> str:
|
||||||
"""Wandelt QueryHits in einen formatierten Kontext-String um."""
|
"""Wandelt QueryHits in einen formatierten Kontext-String um."""
|
||||||
if not response.results:
|
if not response.results:
|
||||||
return "Keine spezifischen Informationen in diesem Stream gefunden."
|
return "Keine spezifischen Informationen in diesem Stream gefunden."
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for i, hit in enumerate(response.results, 1):
|
for i, hit in enumerate(response.results, 1):
|
||||||
source = hit.source.get("path", "Unbekannt")
|
source = hit.source.get("path", "Unbekannt")
|
||||||
content = hit.source.get("text", "").strip()
|
content = hit.source.get("text", "").strip()
|
||||||
lines.append(f"[{i}] QUELLE: {source}\nINHALT: {content}")
|
lines.append(f"[{i}] QUELLE: {source}\nINHALT: {content}")
|
||||||
|
|
||||||
return "\n\n".join(lines)
|
return "\n\n".join(lines)
|
||||||
|
|
||||||
async def _generate_final_answer(
|
async def _generate_final_answer(
|
||||||
|
|
@ -218,12 +204,9 @@ class DecisionEngine:
|
||||||
query: str,
|
query: str,
|
||||||
stream_results: Dict[str, str]
|
stream_results: Dict[str, str]
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Führt die finale Synthese basierend auf dem Strategie-Profil durch."""
|
"""WP-25b: Finale Synthese via Lazy-Prompt 'rag_template'."""
|
||||||
# WP-25a: Nutzt das llm_profile der Strategie
|
|
||||||
profile = strategy.get("llm_profile")
|
profile = strategy.get("llm_profile")
|
||||||
template_key = strategy.get("prompt_template", "rag_template")
|
template_key = strategy.get("prompt_template", "rag_template")
|
||||||
|
|
||||||
template = self.llm_service.get_prompt(template_key)
|
|
||||||
system_prompt = self.llm_service.get_prompt("system_prompt")
|
system_prompt = self.llm_service.get_prompt("system_prompt")
|
||||||
|
|
||||||
# WP-25 ROBUSTNESS: Pre-Initialization
|
# WP-25 ROBUSTNESS: Pre-Initialization
|
||||||
|
|
@ -232,28 +215,15 @@ class DecisionEngine:
|
||||||
template_vars.update(stream_results)
|
template_vars.update(stream_results)
|
||||||
template_vars["query"] = query
|
template_vars["query"] = query
|
||||||
|
|
||||||
prepend = strategy.get("prepend_instruction", "")
|
# WP-25b: Wir reichen die Variablen direkt an den Service weiter.
|
||||||
|
# Formatierung erfolgt erst nach Profil-Auflösung (Gemini vs. Llama vs. Phi3).
|
||||||
try:
|
try:
|
||||||
final_prompt = template.format(**template_vars)
|
|
||||||
if prepend:
|
|
||||||
final_prompt = f"{prepend}\n\n{final_prompt}"
|
|
||||||
|
|
||||||
# WP-25a: MoE Call mit automatisierter Kaskade im LLMService
|
|
||||||
# (Frühere manuelle Fallback-Blocks wurden entfernt, da v3.5.2 dies intern löst)
|
|
||||||
response = await self.llm_service.generate_raw_response(
|
|
||||||
final_prompt, system=system_prompt, profile_name=profile, priority="realtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
except KeyError as e:
|
|
||||||
logger.error(f"Template Variable mismatch in '{template_key}': Missing {e}")
|
|
||||||
fallback_context = "\n\n".join([v for v in stream_results.values() if v])
|
|
||||||
# WP-25a FIX: Nutzt auch im Fallback das Strategie-Profil für Konsistenz
|
|
||||||
return await self.llm_service.generate_raw_response(
|
return await self.llm_service.generate_raw_response(
|
||||||
f"Beantworte: {query}\n\nKontext:\n{fallback_context}",
|
prompt_key=template_key,
|
||||||
system=system_prompt, priority="realtime", profile_name=profile
|
variables=template_vars,
|
||||||
|
system=system_prompt,
|
||||||
|
profile_name=profile,
|
||||||
|
priority="realtime"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Final Synthesis failed: {e}")
|
logger.error(f"Final Synthesis failed: {e}")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/routers/chat.py
|
FILE: app/routers/chat.py
|
||||||
DESCRIPTION: Haupt-Chat-Interface (WP-25a Agentic Edition).
|
DESCRIPTION: Haupt-Chat-Interface (WP-25b Edition).
|
||||||
Kombiniert die spezialisierte Interview-Logik und Keyword-Erkennung
|
Kombiniert die spezialisierte Interview-Logik mit der neuen
|
||||||
mit der neuen MoE-Orchestrierung und Pre-Synthesis Kompression.
|
Lazy-Prompt-Orchestration und MoE-Synthese.
|
||||||
VERSION: 3.0.4 (WP-25a: Optimized MoE & Cascade Delegation)
|
VERSION: 3.0.5 (WP-25b: Lazy Prompt Integration)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
- WP-25a: Delegation der Fallback-Kaskade an den LLMService (v3.5.2).
|
- WP-25b: Umstellung des Interview-Modus auf Lazy-Prompt (prompt_key + variables).
|
||||||
- WP-25a: Nutzung der zentralisierten Stream-Kompression der DecisionEngine (v1.2.1).
|
- WP-25b: Delegation der RAG-Phase an die Engine v1.3.0 für konsistente MoE-Steuerung.
|
||||||
- WP-25a: Konsistente Nutzung von MoE-Profilen für Interview- und RAG-Modus.
|
- WP-25a: Voller Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths).
|
||||||
- 100% Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Depends
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
|
@ -146,7 +145,7 @@ def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]:
|
||||||
all_hits = []
|
all_hits = []
|
||||||
seen_node_ids = set()
|
seen_node_ids = set()
|
||||||
for _, response in stream_responses.items():
|
for _, response in stream_responses.items():
|
||||||
# In v3.0.4 sammeln wir die hits aus den QueryResponse Objekten
|
# Sammeln der Hits aus den QueryResponse Objekten
|
||||||
if hasattr(response, 'results'):
|
if hasattr(response, 'results'):
|
||||||
for hit in response.results:
|
for hit in response.results:
|
||||||
if hit.node_id not in seen_node_ids:
|
if hit.node_id not in seen_node_ids:
|
||||||
|
|
@ -166,8 +165,7 @@ async def chat_endpoint(
|
||||||
):
|
):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
query_id = str(uuid.uuid4())
|
query_id = str(uuid.uuid4())
|
||||||
settings = get_settings()
|
logger.info(f"🚀 [WP-25b] Chat request [{query_id}]: {request.message[:50]}...")
|
||||||
logger.info(f"🚀 [WP-25a] Chat request [{query_id}]: {request.message[:50]}...")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. Intent Detection
|
# 1. Intent Detection
|
||||||
|
|
@ -180,7 +178,7 @@ async def chat_endpoint(
|
||||||
sources_hits = []
|
sources_hits = []
|
||||||
answer_text = ""
|
answer_text = ""
|
||||||
|
|
||||||
# 2. INTERVIEW MODE (Bitgenaue WP-07 Logik)
|
# 2. INTERVIEW MODE (WP-25b Lazy-Prompt Logik)
|
||||||
if intent == "INTERVIEW":
|
if intent == "INTERVIEW":
|
||||||
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
||||||
types_cfg = get_types_config()
|
types_cfg = get_types_config()
|
||||||
|
|
@ -194,23 +192,27 @@ async def chat_endpoint(
|
||||||
fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or [])
|
fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or [])
|
||||||
|
|
||||||
fields_str = "\n- " + "\n- ".join(fields_list)
|
fields_str = "\n- " + "\n- ".join(fields_list)
|
||||||
template = llm.get_prompt(strategy.get("prompt_template", "interview_template"))
|
template_key = strategy.get("prompt_template", "interview_template")
|
||||||
|
|
||||||
final_prompt = template.replace("{query}", request.message) \
|
# WP-25b: Lazy Loading Call
|
||||||
.replace("{target_type}", target_type) \
|
# Wir übergeben nur Key und Variablen. Das System formatiert passend zum Modell.
|
||||||
.replace("{schema_fields}", fields_str)
|
|
||||||
|
|
||||||
# WP-25a: MoE Call (Kaskade erfolgt intern im LLMService)
|
|
||||||
answer_text = await llm.generate_raw_response(
|
answer_text = await llm.generate_raw_response(
|
||||||
final_prompt, system=llm.get_prompt("system_prompt"),
|
prompt_key=template_key,
|
||||||
priority="realtime", profile_name="compression_fast", max_retries=0
|
variables={
|
||||||
|
"query": request.message,
|
||||||
|
"target_type": target_type,
|
||||||
|
"schema_fields": fields_str
|
||||||
|
},
|
||||||
|
system=llm.get_prompt("system_prompt"),
|
||||||
|
priority="realtime",
|
||||||
|
profile_name="compression_fast",
|
||||||
|
max_retries=0
|
||||||
)
|
)
|
||||||
sources_hits = []
|
sources_hits = []
|
||||||
|
|
||||||
# 3. RAG MODE (Optimierte MoE Orchestrierung)
|
# 3. RAG MODE (WP-25b Delegation an Engine v1.3.0)
|
||||||
else:
|
else:
|
||||||
# Phase A & B: Retrieval & Kompression (Delegation an Engine v1.2.1)
|
# Phase A & B: Retrieval & Kompression (Delegiert an Engine v1.3.0)
|
||||||
# Diese Methode gibt bereits die (evtl. komprimierten) Kontext-Strings zurück.
|
|
||||||
formatted_context_map = await engine._execute_parallel_streams(strategy, request.message)
|
formatted_context_map = await engine._execute_parallel_streams(strategy, request.message)
|
||||||
|
|
||||||
# Erfassung der Quellen für das Tracing
|
# Erfassung der Quellen für das Tracing
|
||||||
|
|
@ -232,7 +234,7 @@ async def chat_endpoint(
|
||||||
|
|
||||||
sources_hits = _collect_all_hits(raw_stream_map)
|
sources_hits = _collect_all_hits(raw_stream_map)
|
||||||
|
|
||||||
# Phase C: Finale MoE Synthese
|
# Phase C: Finale MoE Synthese (Delegiert an Engine v1.3.0)
|
||||||
answer_text = await engine._generate_final_answer(
|
answer_text = await engine._generate_final_answer(
|
||||||
intent, strategy, request.message, formatted_context_map
|
intent, strategy, request.message, formatted_context_map
|
||||||
)
|
)
|
||||||
|
|
@ -243,7 +245,7 @@ async def chat_endpoint(
|
||||||
try:
|
try:
|
||||||
log_search(
|
log_search(
|
||||||
query_id=query_id, query_text=request.message, results=sources_hits,
|
query_id=query_id, query_text=request.message, results=sources_hits,
|
||||||
mode=f"wp25a_{intent.lower()}", metadata={"strategy": intent, "source": intent_source}
|
mode=f"wp25b_{intent.lower()}", metadata={"strategy": intent, "source": intent_source}
|
||||||
)
|
)
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/services/llm_service.py
|
FILE: app/services/llm_service.py
|
||||||
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
||||||
WP-25a: Implementierung der Mixture of Experts (MoE) Kaskaden-Steuerung.
|
WP-25b: Implementierung der Lazy-Prompt-Orchestration (Modell-spezifisch).
|
||||||
VERSION: 3.5.2 (WP-25a: MoE & Fallback Cascade Support)
|
VERSION: 3.5.5 (WP-25b: Prompt Orchestration & Full Resilience)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
- WP-25a: Implementierung der rekursiven Fallback-Kaskade via fallback_profile.
|
- WP-25b: get_prompt() unterstützt Hierarchie: Model-ID -> Provider -> Default.
|
||||||
- WP-25a: Schutz gegen zirkuläre Profil-Referenzen (visited_profiles).
|
- WP-25b: generate_raw_response() unterstützt prompt_key + variables für Lazy-Formatting.
|
||||||
- WP-25a: Erweitertes Logging für Tracing der Experten-Entscheidungen.
|
- WP-25a: Voller Erhalt der rekursiven Fallback-Kaskade und visited_profiles Schutz.
|
||||||
- Erhalt der Ingest-Stability (WP-25) und des Rate-Limit-Managements.
|
- WP-20: Restaurierung des internen Ollama-Retry-Loops für Hardware-Stabilität.
|
||||||
"""
|
"""
|
||||||
import httpx
|
import httpx
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -33,10 +33,7 @@ class LLMService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.settings = get_settings()
|
self.settings = get_settings()
|
||||||
self.prompts = self._load_prompts()
|
self.prompts = self._load_prompts()
|
||||||
|
|
||||||
# WP-25a: Zentrale Experten-Profile laden
|
|
||||||
self.profiles = self._load_llm_profiles()
|
self.profiles = self._load_llm_profiles()
|
||||||
|
|
||||||
self._decision_engine = None
|
self._decision_engine = None
|
||||||
|
|
||||||
if LLMService._background_semaphore is None:
|
if LLMService._background_semaphore is None:
|
||||||
|
|
@ -92,7 +89,7 @@ class LLMService:
|
||||||
path_str = getattr(self.settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml")
|
path_str = getattr(self.settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml")
|
||||||
path = Path(path_str)
|
path = Path(path_str)
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
logger.warning(f"⚠️ LLM Profiles file not found at {path}. System will use .env defaults.")
|
logger.warning(f"⚠️ LLM Profiles file not found at {path}.")
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
|
@ -102,17 +99,31 @@ class LLMService:
|
||||||
logger.error(f"❌ Failed to load llm_profiles.yaml: {e}")
|
logger.error(f"❌ Failed to load llm_profiles.yaml: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_prompt(self, key: str, provider: str = None) -> str:
|
def get_prompt(self, key: str, model_id: str = None, provider: str = None) -> str:
|
||||||
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
"""
|
||||||
|
WP-25b: Hochpräziser Prompt-Lookup.
|
||||||
|
Hierarchie: Exakte Modell-ID -> Provider-Name -> Globaler Default.
|
||||||
|
"""
|
||||||
data = self.prompts.get(key, "")
|
data = self.prompts.get(key, "")
|
||||||
if isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
val = data.get(active_provider, data.get("gemini", data.get("ollama", "")))
|
return str(data)
|
||||||
return str(val)
|
|
||||||
return str(data)
|
# 1. Spezifischstes Match: Exakte Modell-ID (z.B. 'meta-llama/llama-3.3-70b-instruct:free')
|
||||||
|
if model_id and model_id in data:
|
||||||
|
return str(data[model_id])
|
||||||
|
|
||||||
|
# 2. Mittlere Ebene: Provider (z.B. 'ollama' oder 'openrouter')
|
||||||
|
if provider and provider in data:
|
||||||
|
return str(data[provider])
|
||||||
|
|
||||||
|
# 3. Fallback: Bekannte Keys oder Default aus prompts.yaml
|
||||||
|
return str(data.get("default", data.get("gemini", data.get("ollama", ""))))
|
||||||
|
|
||||||
async def generate_raw_response(
|
async def generate_raw_response(
|
||||||
self,
|
self,
|
||||||
prompt: str,
|
prompt: str = None,
|
||||||
|
prompt_key: str = None, # WP-25b: Lazy Loading Key
|
||||||
|
variables: dict = None, # WP-25b: Daten für Formatierung
|
||||||
system: str = None,
|
system: str = None,
|
||||||
force_json: bool = False,
|
force_json: bool = False,
|
||||||
max_retries: int = 2,
|
max_retries: int = 2,
|
||||||
|
|
@ -126,16 +137,14 @@ class LLMService:
|
||||||
profile_name: Optional[str] = None,
|
profile_name: Optional[str] = None,
|
||||||
visited_profiles: Optional[list] = None
|
visited_profiles: Optional[list] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""Haupteinstiegspunkt für LLM-Anfragen mit Lazy-Prompt Orchestrierung."""
|
||||||
Haupteinstiegspunkt für LLM-Anfragen mit rekursiver Kaskaden-Logik.
|
|
||||||
"""
|
|
||||||
visited_profiles = visited_profiles or []
|
visited_profiles = visited_profiles or []
|
||||||
target_provider = provider
|
target_provider = provider
|
||||||
target_model = model_override
|
target_model = model_override
|
||||||
target_temp = None
|
target_temp = None
|
||||||
fallback_profile = None
|
fallback_profile = None
|
||||||
|
|
||||||
# 1. Profil-Auflösung
|
# 1. Profil-Auflösung (Mixture of Experts)
|
||||||
if profile_name and self.profiles:
|
if profile_name and self.profiles:
|
||||||
profile = self.profiles.get(profile_name)
|
profile = self.profiles.get(profile_name)
|
||||||
if profile:
|
if profile:
|
||||||
|
|
@ -148,30 +157,39 @@ class LLMService:
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠️ Profil '{profile_name}' nicht in llm_profiles.yaml gefunden!")
|
logger.warning(f"⚠️ Profil '{profile_name}' nicht in llm_profiles.yaml gefunden!")
|
||||||
|
|
||||||
# Fallback auf Standard-Provider falls nichts übergeben/definiert wurde
|
|
||||||
if not target_provider:
|
if not target_provider:
|
||||||
target_provider = self.settings.MINDNET_LLM_PROVIDER
|
target_provider = self.settings.MINDNET_LLM_PROVIDER
|
||||||
logger.info(f"ℹ️ Kein Provider/Profil definiert. Nutze Default: {target_provider}")
|
|
||||||
|
|
||||||
# 2. Ausführung mit Fehler-Handling für Kaskade
|
# 2. WP-25b: Lazy Prompt Resolving
|
||||||
|
# Wir laden den Prompt erst JETZT, basierend auf dem gerade aktiven Modell.
|
||||||
|
current_prompt = prompt
|
||||||
|
if prompt_key:
|
||||||
|
template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider)
|
||||||
|
try:
|
||||||
|
current_prompt = template.format(**(variables or {}))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}")
|
||||||
|
current_prompt = template # Sicherheits-Fallback
|
||||||
|
|
||||||
|
# 3. Ausführung mit Fehler-Handling für Kaskade
|
||||||
try:
|
try:
|
||||||
if priority == "background":
|
if priority == "background":
|
||||||
async with LLMService._background_semaphore:
|
async with LLMService._background_semaphore:
|
||||||
res = await self._dispatch(
|
res = await self._dispatch(
|
||||||
target_provider, prompt, system, force_json,
|
target_provider, current_prompt, system, force_json,
|
||||||
max_retries, base_delay, target_model,
|
max_retries, base_delay, target_model,
|
||||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
res = await self._dispatch(
|
res = await self._dispatch(
|
||||||
target_provider, prompt, system, force_json,
|
target_provider, current_prompt, system, force_json,
|
||||||
max_retries, base_delay, target_model,
|
max_retries, base_delay, target_model,
|
||||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check auf leere Cloud-Antworten (WP-25 Stability)
|
# Check auf leere Cloud-Antworten (WP-25 Stability)
|
||||||
if not res and target_provider != "ollama":
|
if not res and target_provider != "ollama":
|
||||||
logger.warning(f"⚠️ Empty response from {target_provider}. Triggering fallback chain.")
|
logger.warning(f"⚠️ Empty response from {target_provider}. Triggering fallback.")
|
||||||
raise ValueError(f"Empty response from {target_provider}")
|
raise ValueError(f"Empty response from {target_provider}")
|
||||||
|
|
||||||
return clean_llm_text(res) if not force_json else res
|
return clean_llm_text(res) if not force_json else res
|
||||||
|
|
@ -179,40 +197,33 @@ class LLMService:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Error during execution of profile '{profile_name}' ({target_provider}): {e}")
|
logger.error(f"❌ Error during execution of profile '{profile_name}' ({target_provider}): {e}")
|
||||||
|
|
||||||
# 3. Kaskaden-Logik: Nächstes Profil in der Kette versuchen
|
# 4. WP-25b Kaskaden-Logik (Rekursiv mit Modell-spezifischem Re-Loading)
|
||||||
if fallback_profile and fallback_profile not in visited_profiles:
|
if fallback_profile and fallback_profile not in visited_profiles:
|
||||||
logger.info(f"🔄 Switching to fallback profile: '{fallback_profile}'")
|
logger.info(f"🔄 Switching to fallback profile: '{fallback_profile}'")
|
||||||
return await self.generate_raw_response(
|
return await self.generate_raw_response(
|
||||||
prompt=prompt, system=system, force_json=force_json,
|
prompt=prompt,
|
||||||
|
prompt_key=prompt_key,
|
||||||
|
variables=variables, # Ermöglicht neues Formatting für Fallback-Modell
|
||||||
|
system=system, force_json=force_json,
|
||||||
max_retries=max_retries, base_delay=base_delay,
|
max_retries=max_retries, base_delay=base_delay,
|
||||||
priority=priority, provider=provider, model_override=model_override,
|
priority=priority, provider=None, model_override=None,
|
||||||
json_schema=json_schema, json_schema_name=json_schema_name,
|
json_schema=json_schema, json_schema_name=json_schema_name,
|
||||||
strict_json_schema=strict_json_schema,
|
strict_json_schema=strict_json_schema,
|
||||||
profile_name=fallback_profile,
|
profile_name=fallback_profile,
|
||||||
visited_profiles=visited_profiles
|
visited_profiles=visited_profiles
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. Ultimativer Notanker: Falls alles fehlschlägt, direkt zu Ollama
|
# 5. Ultimativer Notanker: Falls alles fehlschlägt, direkt zu Ollama
|
||||||
if target_provider != "ollama" and self.settings.LLM_FALLBACK_ENABLED:
|
if target_provider != "ollama" and self.settings.LLM_FALLBACK_ENABLED:
|
||||||
logger.warning(f"🚨 Kaskade erschöpft. Nutze finalen Ollama-Notanker.")
|
logger.warning(f"🚨 Kaskade erschöpft. Nutze finalen Ollama-Notanker.")
|
||||||
res = await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
res = await self._execute_ollama(current_prompt, system, force_json, max_retries, base_delay, target_temp, target_model)
|
||||||
return clean_llm_text(res) if not force_json else res
|
return clean_llm_text(res) if not force_json else res
|
||||||
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def _dispatch(
|
async def _dispatch(
|
||||||
self,
|
self, provider, prompt, system, force_json, max_retries, base_delay,
|
||||||
provider: str,
|
model_override, json_schema, json_schema_name, strict_json_schema, temperature
|
||||||
prompt: str,
|
|
||||||
system: Optional[str],
|
|
||||||
force_json: bool,
|
|
||||||
max_retries: int,
|
|
||||||
base_delay: float,
|
|
||||||
model_override: Optional[str],
|
|
||||||
json_schema: Optional[Dict[str, Any]],
|
|
||||||
json_schema_name: str,
|
|
||||||
strict_json_schema: bool,
|
|
||||||
temperature: Optional[float] = None
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Routet die Anfrage an den spezifischen Provider-Executor."""
|
"""Routet die Anfrage an den spezifischen Provider-Executor."""
|
||||||
rate_limit_attempts = 0
|
rate_limit_attempts = 0
|
||||||
|
|
@ -232,23 +243,19 @@ class LLMService:
|
||||||
if provider == "gemini" and self.google_client:
|
if provider == "gemini" and self.google_client:
|
||||||
return await self._execute_google(prompt, system, force_json, model_override, temperature)
|
return await self._execute_google(prompt, system, force_json, model_override, temperature)
|
||||||
|
|
||||||
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay, temperature)
|
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay, temperature, model_override)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_str = str(e)
|
err_str = str(e)
|
||||||
# Rate-Limit Handling (429)
|
|
||||||
if any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited"]):
|
if any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited"]):
|
||||||
rate_limit_attempts += 1
|
rate_limit_attempts += 1
|
||||||
logger.warning(f"⏳ Rate Limit {provider}. Attempt {rate_limit_attempts}. Wait {wait_time}s.")
|
logger.warning(f"⏳ Rate Limit {provider}. Attempt {rate_limit_attempts}. Wait {wait_time}s.")
|
||||||
await asyncio.sleep(wait_time)
|
await asyncio.sleep(wait_time)
|
||||||
continue
|
continue
|
||||||
# Andere Fehler werden an generate_raw_response für die Kaskade gereicht
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def _execute_google(self, prompt, system, force_json, model_override, temperature):
|
async def _execute_google(self, prompt, system, force_json, model_override, temperature):
|
||||||
model = model_override or self.settings.GEMINI_MODEL
|
model = (model_override or self.settings.GEMINI_MODEL).replace("models/", "")
|
||||||
clean_model = model.replace("models/", "")
|
|
||||||
|
|
||||||
config_kwargs = {
|
config_kwargs = {
|
||||||
"system_instruction": system,
|
"system_instruction": system,
|
||||||
"response_mime_type": "application/json" if force_json else "text/plain"
|
"response_mime_type": "application/json" if force_json else "text/plain"
|
||||||
|
|
@ -257,22 +264,13 @@ class LLMService:
|
||||||
config_kwargs["temperature"] = temperature
|
config_kwargs["temperature"] = temperature
|
||||||
|
|
||||||
config = types.GenerateContentConfig(**config_kwargs)
|
config = types.GenerateContentConfig(**config_kwargs)
|
||||||
|
|
||||||
response = await asyncio.wait_for(
|
response = await asyncio.wait_for(
|
||||||
asyncio.to_thread(
|
asyncio.to_thread(self.google_client.models.generate_content, model=model, contents=prompt, config=config),
|
||||||
self.google_client.models.generate_content,
|
|
||||||
model=clean_model, contents=prompt, config=config
|
|
||||||
),
|
|
||||||
timeout=45.0
|
timeout=45.0
|
||||||
)
|
)
|
||||||
return response.text.strip()
|
return response.text.strip()
|
||||||
|
|
||||||
async def _execute_openrouter(
|
async def _execute_openrouter(self, prompt, system, force_json, model_override, json_schema, json_schema_name, strict_json_schema, temperature) -> str:
|
||||||
self, prompt: str, system: Optional[str], force_json: bool,
|
|
||||||
model_override: Optional[str], json_schema: Optional[Dict[str, Any]] = None,
|
|
||||||
json_schema_name: str = "mindnet_json", strict_json_schema: bool = True,
|
|
||||||
temperature: Optional[float] = None
|
|
||||||
) -> str:
|
|
||||||
model = model_override or self.settings.OPENROUTER_MODEL
|
model = model_override or self.settings.OPENROUTER_MODEL
|
||||||
logger.info(f"🛰️ OpenRouter Call: Model='{model}' | Temp={temperature}")
|
logger.info(f"🛰️ OpenRouter Call: Model='{model}' | Temp={temperature}")
|
||||||
messages = []
|
messages = []
|
||||||
|
|
@ -280,35 +278,26 @@ class LLMService:
|
||||||
messages.append({"role": "user", "content": prompt})
|
messages.append({"role": "user", "content": prompt})
|
||||||
|
|
||||||
kwargs: Dict[str, Any] = {}
|
kwargs: Dict[str, Any] = {}
|
||||||
if temperature is not None:
|
if temperature is not None: kwargs["temperature"] = temperature
|
||||||
kwargs["temperature"] = temperature
|
|
||||||
|
|
||||||
if force_json:
|
if force_json:
|
||||||
if json_schema:
|
if json_schema:
|
||||||
kwargs["response_format"] = {
|
kwargs["response_format"] = {"type": "json_schema", "json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}}
|
||||||
"type": "json_schema",
|
|
||||||
"json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
kwargs["response_format"] = {"type": "json_object"}
|
kwargs["response_format"] = {"type": "json_object"}
|
||||||
|
|
||||||
response = await self.openrouter_client.chat.completions.create(
|
response = await self.openrouter_client.chat.completions.create(model=model, messages=messages, **kwargs)
|
||||||
model=model, messages=messages, **kwargs
|
if not response.choices: return ""
|
||||||
)
|
|
||||||
|
|
||||||
if not response.choices:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return response.choices[0].message.content.strip() if response.choices[0].message.content else ""
|
return response.choices[0].message.content.strip() if response.choices[0].message.content else ""
|
||||||
|
|
||||||
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None):
|
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None, model_override=None):
|
||||||
# Nutzt Profil-Temperatur oder strikte Defaults für lokale Hardware-Schonung
|
# WP-20: Restaurierter Retry-Loop für lokale Hardware-Resilienz
|
||||||
|
effective_model = model_override or self.settings.LLM_MODEL
|
||||||
effective_temp = temperature if temperature is not None else (0.1 if force_json else 0.7)
|
effective_temp = temperature if temperature is not None else (0.1 if force_json else 0.7)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.settings.LLM_MODEL,
|
"model": effective_model,
|
||||||
"prompt": prompt,
|
"prompt": prompt, "stream": False,
|
||||||
"stream": False,
|
|
||||||
"options": {"temperature": effective_temp, "num_ctx": 8192}
|
"options": {"temperature": effective_temp, "num_ctx": 8192}
|
||||||
}
|
}
|
||||||
if force_json: payload["format"] = "json"
|
if force_json: payload["format"] = "json"
|
||||||
|
|
@ -323,12 +312,11 @@ class LLMService:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if attempt > max_retries:
|
if attempt > max_retries:
|
||||||
logger.error(f"❌ Ollama final failure after {attempt} attempts: {e}")
|
logger.error(f"❌ Ollama failure after {attempt} attempts: {e}")
|
||||||
raise e
|
raise e
|
||||||
await asyncio.sleep(base_delay * (2 ** (attempt - 1)))
|
await asyncio.sleep(base_delay * (2 ** (attempt - 1)))
|
||||||
|
|
||||||
async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str:
|
async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str:
|
||||||
"""WP-25: Orchestrierung via DecisionEngine."""
|
|
||||||
return await self.decision_engine.ask(query)
|
return await self.decision_engine.ask(query)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
|
|
|
||||||
337
config/prompts - Kopie.yaml
Normal file
337
config/prompts - Kopie.yaml
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync)
|
||||||
|
# STATUS: Active
|
||||||
|
# FIX:
|
||||||
|
# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8).
|
||||||
|
# - Überführung der Kategorien 1-4 in die Multi-Stream Struktur unter Beibehaltung des Inhalts.
|
||||||
|
# - Konsolidierung: Sektion 9 (v3.0.0) wurde in Sektion 1 & 2 integriert (keine Redundanz).
|
||||||
|
|
||||||
|
system_prompt: |
|
||||||
|
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||||
|
|
||||||
|
DEINE IDENTITÄT:
|
||||||
|
- Du bist nicht nur eine Datenbank, sondern handelst nach MEINEN Werten und Zielen.
|
||||||
|
- Du passt deinen Stil dynamisch an die Situation an (Analytisch, Empathisch oder Technisch).
|
||||||
|
|
||||||
|
DEINE REGELN:
|
||||||
|
1. Deine Antwort muss zu 100% auf dem bereitgestellten KONTEXT basieren.
|
||||||
|
2. Halluziniere keine Fakten, die nicht in den Quellen stehen.
|
||||||
|
3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams.
|
||||||
|
fact_synthesis_v1:
|
||||||
|
ollama: |
|
||||||
|
WISSENS-STREAMS:
|
||||||
|
=========================================
|
||||||
|
FAKTEN & STATUS:
|
||||||
|
{facts_stream}
|
||||||
|
|
||||||
|
ERFAHRUNG & BIOGRAFIE:
|
||||||
|
{biography_stream}
|
||||||
|
|
||||||
|
WISSEN & TECHNIK:
|
||||||
|
{tech_stream}
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
FRAGE:
|
||||||
|
{query}
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
Beantworte die Frage präzise basierend auf den Quellen.
|
||||||
|
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
||||||
|
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||||
|
gemini: |
|
||||||
|
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
||||||
|
FAKTEN: {facts_stream}
|
||||||
|
BIOGRAFIE/ERFAHRUNG: {biography_stream}
|
||||||
|
TECHNIK: {tech_stream}
|
||||||
|
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
|
||||||
|
openrouter: |
|
||||||
|
Synthese der Wissens-Streams für: {query}
|
||||||
|
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
||||||
|
Antworte basierend auf dem bereitgestellten Kontext.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
|
||||||
|
decision_synthesis_v1:
|
||||||
|
ollama: |
|
||||||
|
ENTSCHEIDUNGS-STREAMS:
|
||||||
|
=========================================
|
||||||
|
WERTE & PRINZIPIEN (Identität):
|
||||||
|
{values_stream}
|
||||||
|
|
||||||
|
OPERATIVE FAKTEN (Realität):
|
||||||
|
{facts_stream}
|
||||||
|
|
||||||
|
RISIKO-RADAR (Konsequenzen):
|
||||||
|
{risk_stream}
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
ENTSCHEIDUNGSFRAGE:
|
||||||
|
{query}
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
Du agierst als mein Entscheidungs-Partner.
|
||||||
|
1. Analysiere die Faktenlage aus den Quellen.
|
||||||
|
2. Prüfe dies hart gegen meine strategischen Notizen (Werte & Prinzipien).
|
||||||
|
3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten?
|
||||||
|
|
||||||
|
FORMAT:
|
||||||
|
- **Analyse:** (Kurze Zusammenfassung der Fakten)
|
||||||
|
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||||
|
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||||
|
gemini: |
|
||||||
|
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
|
||||||
|
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
|
||||||
|
Wäge ab und gib eine klare strategische Empfehlung ab.
|
||||||
|
openrouter: |
|
||||||
|
Strategische Multi-Stream Analyse für: {query}
|
||||||
|
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
||||||
|
Bitte wäge ab und gib eine Empfehlung.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
empathy_template:
|
||||||
|
ollama: |
|
||||||
|
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||||
|
=========================================
|
||||||
|
ERLEBNISSE & BIOGRAFIE:
|
||||||
|
{biography_stream}
|
||||||
|
|
||||||
|
WERTE & BEDÜRFNISSE:
|
||||||
|
{values_stream}
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
SITUATION:
|
||||||
|
{query}
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
Du agierst jetzt als mein empathischer Spiegel.
|
||||||
|
1. Versuche nicht sofort, das Problem technisch zu lösen.
|
||||||
|
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Werten, falls im Kontext vorhanden.
|
||||||
|
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
|
||||||
|
|
||||||
|
TONFALL:
|
||||||
|
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||||
|
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
|
||||||
|
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
technical_template:
|
||||||
|
ollama: |
|
||||||
|
KONTEXT (WISSEN & PROJEKTE):
|
||||||
|
=========================================
|
||||||
|
TECHNIK & SNIPPETS:
|
||||||
|
{tech_stream}
|
||||||
|
|
||||||
|
PROJEKT-STATUS:
|
||||||
|
{facts_stream}
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
{query}
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
Du bist Senior Developer.
|
||||||
|
1. Ignoriere Smalltalk. Komm sofort zum Punkt.
|
||||||
|
2. Generiere validen, performanten Code basierend auf den Quellen.
|
||||||
|
3. Wenn Quellen fehlen, nutze dein allgemeines Programmierwissen, aber weise darauf hin.
|
||||||
|
|
||||||
|
FORMAT:
|
||||||
|
- Kurze Erklärung des Ansatzes.
|
||||||
|
- Markdown Code-Block (Copy-Paste fertig).
|
||||||
|
- Wichtige Edge-Cases.
|
||||||
|
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
||||||
|
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
interview_template:
|
||||||
|
ollama: |
|
||||||
|
TASK:
|
||||||
|
Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'.
|
||||||
|
|
||||||
|
STRUKTUR (Nutze EXAKT diese Überschriften):
|
||||||
|
{schema_fields}
|
||||||
|
|
||||||
|
USER INPUT:
|
||||||
|
"{query}"
|
||||||
|
|
||||||
|
ANWEISUNG ZUM INHALT:
|
||||||
|
1. Analysiere den Input genau.
|
||||||
|
2. Schreibe die Inhalte unter die passenden Überschriften aus der STRUKTUR-Liste oben.
|
||||||
|
3. STIL: Schreibe flüssig, professionell und in der Ich-Perspektive. Korrigiere Grammatikfehler, aber behalte den persönlichen Ton bei.
|
||||||
|
4. Wenn Informationen für einen Abschnitt fehlen, schreibe nur: "[TODO: Ergänzen]". Erfinde nichts dazu.
|
||||||
|
|
||||||
|
OUTPUT FORMAT (YAML + MARKDOWN):
|
||||||
|
---
|
||||||
|
type: {target_type}
|
||||||
|
status: draft
|
||||||
|
title: (Erstelle einen treffenden, kurzen Titel für den Inhalt)
|
||||||
|
tags: [Tag1, Tag2]
|
||||||
|
---
|
||||||
|
|
||||||
|
# (Wiederhole den Titel hier)
|
||||||
|
|
||||||
|
## (Erster Begriff aus STRUKTUR)
|
||||||
|
(Text...)
|
||||||
|
|
||||||
|
## (Zweiter Begriff aus STRUKTUR)
|
||||||
|
(Text...)
|
||||||
|
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
||||||
|
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 6. EDGE_ALLOCATION: Kantenfilter (Ingest)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
edge_allocation_template:
|
||||||
|
ollama: |
|
||||||
|
TASK:
|
||||||
|
Du bist ein strikter Selektor. Du erhältst eine Liste von "Kandidaten-Kanten" (Strings).
|
||||||
|
Wähle jene aus, die inhaltlich im "Textabschnitt" vorkommen oder relevant sind.
|
||||||
|
|
||||||
|
TEXTABSCHNITT:
|
||||||
|
"""
|
||||||
|
{chunk_text}
|
||||||
|
"""
|
||||||
|
|
||||||
|
KANDIDATEN (Auswahl-Pool):
|
||||||
|
{edge_list}
|
||||||
|
|
||||||
|
REGELN:
|
||||||
|
1. Die Kanten haben das Format "typ:ziel". Der "typ" ist variabel und kann ALLES sein.
|
||||||
|
2. Gib NUR die Strings aus der Kandidaten-Liste zurück, die zum Text passen.
|
||||||
|
3. Erfinde KEINE neuen Kanten.
|
||||||
|
4. Antworte als flache JSON-Liste.
|
||||||
|
|
||||||
|
DEIN OUTPUT (JSON):
|
||||||
|
gemini: |
|
||||||
|
TASK: Ordne Kanten einem Textabschnitt zu.
|
||||||
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
TEXT: {chunk_text}
|
||||||
|
KANDIDATEN: {edge_list}
|
||||||
|
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte!
|
||||||
|
openrouter: |
|
||||||
|
TASK: Filtere relevante Kanten aus dem Pool.
|
||||||
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
TEXT: {chunk_text}
|
||||||
|
POOL: {edge_list}
|
||||||
|
ANWEISUNG: Gib NUR eine flache JSON-Liste von Strings zurück.
|
||||||
|
BEISPIEL: ["kind:target", "kind:target"]
|
||||||
|
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
||||||
|
OUTPUT:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
edge_extraction:
|
||||||
|
ollama: |
|
||||||
|
TASK:
|
||||||
|
Du bist ein Wissens-Ingenieur für den digitalen Zwilling 'mindnet'.
|
||||||
|
Deine Aufgabe ist es, semantische Relationen (Kanten) aus dem Text zu extrahieren,
|
||||||
|
die die Hauptnotiz '{note_id}' mit anderen Konzepten verbinden.
|
||||||
|
|
||||||
|
ANWEISUNGEN:
|
||||||
|
1. Identifiziere wichtige Entitäten, Konzepte oder Ereignisse im Text.
|
||||||
|
2. Bestimme die Art der Beziehung (z.B. part_of, uses, related_to, blocks, caused_by).
|
||||||
|
3. Das Ziel (target) muss ein prägnanter Begriff sein.
|
||||||
|
4. Antworte AUSSCHLIESSLICH in validem JSON als Liste von Objekten.
|
||||||
|
|
||||||
|
BEISPIEL:
|
||||||
|
[[ {{"to": "Ziel-Konzept", \"kind\": \"beziehungs_typ\"}} ]]
|
||||||
|
|
||||||
|
TEXT:
|
||||||
|
"""
|
||||||
|
{text}
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEIN OUTPUT (JSON):
|
||||||
|
gemini: |
|
||||||
|
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
||||||
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
TEXT: {text}
|
||||||
|
OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: [].
|
||||||
|
openrouter: |
|
||||||
|
TASK: Extrahiere semantische Relationen für '{note_id}'.
|
||||||
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
TEXT: {text}
|
||||||
|
ANWEISUNG: Antworte AUSSCHLIESSLICH mit einem JSON-Array von Objekten.
|
||||||
|
FORMAT: [[{{"to\":\"Ziel-Begriff\",\"kind\":\"typ\"}}]]
|
||||||
|
STRIKTES VERBOT: Schreibe keine Einleitung, keine Analyse und keine Erklärungen.
|
||||||
|
Wenn keine Relationen existieren, antworte NUR mit: []
|
||||||
|
OUTPUT:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
edge_validation:
|
||||||
|
gemini: |
|
||||||
|
Bewerte die semantische Validität dieser Verbindung im Wissensgraph.
|
||||||
|
|
||||||
|
KONTEXT DER QUELLE (Chunk):
|
||||||
|
"{chunk_text}"
|
||||||
|
|
||||||
|
ZIEL-NOTIZ: "{target_title}"
|
||||||
|
ZIEL-BESCHREIBUNG (Zusammenfassung):
|
||||||
|
"{target_summary}"
|
||||||
|
|
||||||
|
GEPLANTE RELATION: "{edge_kind}"
|
||||||
|
|
||||||
|
FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel?
|
||||||
|
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
||||||
|
openrouter: |
|
||||||
|
Verify semantic relation for graph construction.
|
||||||
|
Source Context: {chunk_text}
|
||||||
|
Target Note: {target_title}
|
||||||
|
Target Summary: {target_summary}
|
||||||
|
Proposed Relation: {edge_kind}
|
||||||
|
Instruction: Does the source context support this relation to the target?
|
||||||
|
Result: Respond ONLY with 'YES' or 'NO'.
|
||||||
|
ollama: |
|
||||||
|
Bewerte die semantische Korrektheit dieser Verbindung.
|
||||||
|
QUELLE: {chunk_text}
|
||||||
|
ZIEL: {target_title} ({target_summary})
|
||||||
|
BEZIEHUNG: {edge_kind}
|
||||||
|
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
intent_router_v1:
|
||||||
|
ollama: |
|
||||||
|
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
||||||
|
Antworte NUR mit dem Namen der Strategie.
|
||||||
|
|
||||||
|
STRATEGIEN:
|
||||||
|
- FACT_WHEN: Nur für explizite Fragen nach einem exakten Datum, Uhrzeit oder dem "Wann" eines Ereignisses.
|
||||||
|
- FACT_WHAT: Fragen nach Inhalten, Listen von Objekten/Projekten, Definitionen oder "Was/Welche" Anfragen (auch bei Zeiträumen).
|
||||||
|
- DECISION: Rat, Meinung, "Soll ich?", Abwägung gegen Werte.
|
||||||
|
- EMPATHY: Emotionen, Reflexion, Befindlichkeit.
|
||||||
|
- CODING: Programmierung, Skripte, technische Syntax.
|
||||||
|
- INTERVIEW: Dokumentation neuer Informationen, Notizen anlegen.
|
||||||
|
|
||||||
|
NACHRICHT: "{query}"
|
||||||
|
STRATEGIE:
|
||||||
|
gemini: |
|
||||||
|
Classify intent:
|
||||||
|
- FACT_WHEN: Exact dates/times only.
|
||||||
|
- FACT_WHAT: Content, lists of entities (projects, etc.), definitions, "What/Which" queries.
|
||||||
|
- DECISION: Strategic advice/values.
|
||||||
|
- EMPATHY: Emotions.
|
||||||
|
- CODING: Tech/Code.
|
||||||
|
- INTERVIEW: Data entry.
|
||||||
|
Query: "{query}"
|
||||||
|
Result (One word only):
|
||||||
|
openrouter: |
|
||||||
|
Select strategy for Mindnet:
|
||||||
|
FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW.
|
||||||
|
Query: "{query}"
|
||||||
|
Response:
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync)
|
# config/prompts.yaml — VERSION 3.2.2 (WP-25b: Hierarchical Model Sync)
|
||||||
# STATUS: Active
|
# STATUS: Active
|
||||||
# FIX:
|
# FIX:
|
||||||
# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8).
|
# - 100% Erhalt der Original-Prompts aus v3.1.2 für die Provider-Ebene (ollama, gemini, openrouter).
|
||||||
# - Überführung der Kategorien 1-4 in die Multi-Stream Struktur unter Beibehaltung des Inhalts.
|
# - Integration der Modell-spezifischen Overrides für Gemini 2.0, Llama 3.3 und Qwen 2.5.
|
||||||
# - Konsolidierung: Sektion 9 (v3.0.0) wurde in Sektion 1 & 2 integriert (keine Redundanz).
|
# - Hinzufügen des notwendigen 'compression_template' für die DecisionEngine v1.3.0.
|
||||||
|
|
||||||
system_prompt: |
|
system_prompt: |
|
||||||
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||||
|
|
@ -20,8 +20,19 @@ system_prompt: |
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams.
|
|
||||||
fact_synthesis_v1:
|
fact_synthesis_v1:
|
||||||
|
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||||
|
"google/gemini-2.0-flash-exp:free": |
|
||||||
|
Analysiere die Wissens-Streams für: {query}
|
||||||
|
FAKTEN: {facts_stream} | BIOGRAFIE: {biography_stream} | TECHNIK: {tech_stream}
|
||||||
|
Nutze deine hohe Reasoning-Kapazität für eine tiefe Synthese. Antworte präzise auf Deutsch.
|
||||||
|
|
||||||
|
"meta-llama/llama-3.3-70b-instruct:free": |
|
||||||
|
Erstelle eine fundierte Synthese für die Frage: "{query}"
|
||||||
|
Nutze die Daten: {facts_stream}, {biography_stream} und {tech_stream}.
|
||||||
|
Trenne klare Fakten von Erfahrungen. Bleibe strikt beim bereitgestellten Kontext.
|
||||||
|
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
WISSENS-STREAMS:
|
WISSENS-STREAMS:
|
||||||
=========================================
|
=========================================
|
||||||
|
|
@ -42,22 +53,32 @@ fact_synthesis_v1:
|
||||||
Beantworte die Frage präzise basierend auf den Quellen.
|
Beantworte die Frage präzise basierend auf den Quellen.
|
||||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
||||||
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||||
|
|
||||||
gemini: |
|
gemini: |
|
||||||
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
||||||
FAKTEN: {facts_stream}
|
FAKTEN: {facts_stream}
|
||||||
BIOGRAFIE/ERFAHRUNG: {biography_stream}
|
BIOGRAFIE/ERFAHRUNG: {biography_stream}
|
||||||
TECHNIK: {tech_stream}
|
TECHNIK: {tech_stream}
|
||||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
|
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
Synthese der Wissens-Streams für: {query}
|
Synthese der Wissens-Streams für: {query}
|
||||||
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
||||||
Antworte basierend auf dem bereitgestellten Kontext.
|
Antworte basierend auf dem bereitgestellten Kontext.
|
||||||
|
|
||||||
|
default: "Beantworte {query} basierend auf dem Kontext: {facts_stream} {biography_stream} {tech_stream}."
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
|
|
||||||
decision_synthesis_v1:
|
decision_synthesis_v1:
|
||||||
|
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||||
|
"google/gemini-2.0-flash-exp:free": |
|
||||||
|
Agiere als strategischer Partner für: {query}
|
||||||
|
WERTE: {values_stream} | FAKTEN: {facts_stream} | RISIKEN: {risk_stream}
|
||||||
|
Prüfe die Fakten gegen meine Werte. Zeige Zielkonflikte auf. Gib eine klare Empfehlung.
|
||||||
|
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
ENTSCHEIDUNGS-STREAMS:
|
ENTSCHEIDUNGS-STREAMS:
|
||||||
=========================================
|
=========================================
|
||||||
|
|
@ -84,19 +105,24 @@ decision_synthesis_v1:
|
||||||
- **Analyse:** (Kurze Zusammenfassung der Fakten)
|
- **Analyse:** (Kurze Zusammenfassung der Fakten)
|
||||||
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||||
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||||
|
|
||||||
gemini: |
|
gemini: |
|
||||||
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
|
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
|
||||||
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
|
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
|
||||||
Wäge ab und gib eine klare strategische Empfehlung ab.
|
Wäge ab und gib eine klare strategische Empfehlung ab.
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
Strategische Multi-Stream Analyse für: {query}
|
Strategische Multi-Stream Analyse für: {query}
|
||||||
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
||||||
Bitte wäge ab und gib eine Empfehlung.
|
Bitte wäge ab und gib eine Empfehlung.
|
||||||
|
|
||||||
|
default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}."
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
empathy_template:
|
empathy_template:
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
KONTEXT (ERFAHRUNGEN & WERTE):
|
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||||
=========================================
|
=========================================
|
||||||
|
|
@ -118,13 +144,23 @@ empathy_template:
|
||||||
|
|
||||||
TONFALL:
|
TONFALL:
|
||||||
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||||
|
|
||||||
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
|
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
|
||||||
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
||||||
|
|
||||||
|
default: "Reflektiere empathisch über {query} basierend auf {biography_stream}."
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
technical_template:
|
technical_template:
|
||||||
|
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||||
|
"qwen/qwen-2.5-vl-7b-instruct:free": |
|
||||||
|
Du bist Senior Software Engineer. TASK: {query}
|
||||||
|
REFERENZEN: {tech_stream} | KONTEXT: {facts_stream}
|
||||||
|
Generiere validen, performanten Code. Nutze die Snippets aus dem Kontext.
|
||||||
|
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
KONTEXT (WISSEN & PROJEKTE):
|
KONTEXT (WISSEN & PROJEKTE):
|
||||||
=========================================
|
=========================================
|
||||||
|
|
@ -148,13 +184,17 @@ technical_template:
|
||||||
- Kurze Erklärung des Ansatzes.
|
- Kurze Erklärung des Ansatzes.
|
||||||
- Markdown Code-Block (Copy-Paste fertig).
|
- Markdown Code-Block (Copy-Paste fertig).
|
||||||
- Wichtige Edge-Cases.
|
- Wichtige Edge-Cases.
|
||||||
|
|
||||||
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
||||||
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
|
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
|
||||||
|
|
||||||
|
default: "Erstelle eine technische Lösung für {query}."
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
interview_template:
|
interview_template:
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
TASK:
|
TASK:
|
||||||
Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'.
|
Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'.
|
||||||
|
|
@ -186,11 +226,30 @@ interview_template:
|
||||||
|
|
||||||
## (Zweiter Begriff aus STRUKTUR)
|
## (Zweiter Begriff aus STRUKTUR)
|
||||||
(Text...)
|
(Text...)
|
||||||
|
|
||||||
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
||||||
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
||||||
|
|
||||||
|
default: "Extrahiere Informationen für {target_type} aus dem Input: {query}"
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 6. EDGE_ALLOCATION: Kantenfilter (Ingest)
|
# 6. WP-25b: PRE-SYNTHESIS COMPRESSION (Neu!)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
compression_template:
|
||||||
|
"mistralai/mistral-7b-instruct:free": |
|
||||||
|
Reduziere den Stream '{stream_name}' auf die Informationen, die für die Beantwortung der Frage '{query}' absolut notwendig sind.
|
||||||
|
BEHALTE: Harte Fakten, Projektnamen, konkrete Werte und Quellenangaben.
|
||||||
|
ENTFERNE: Redundante Einleitungen, Füllwörter und irrelevante Details.
|
||||||
|
|
||||||
|
INHALT:
|
||||||
|
{content}
|
||||||
|
|
||||||
|
KOMPRIMIERTE ANALYSE:
|
||||||
|
|
||||||
|
default: "Fasse das Wichtigste aus {stream_name} für die Frage {query} kurz zusammen: {content}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 7. EDGE_ALLOCATION: Kantenfilter (Ingest)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
edge_allocation_template:
|
edge_allocation_template:
|
||||||
ollama: |
|
ollama: |
|
||||||
|
|
@ -213,12 +272,14 @@ edge_allocation_template:
|
||||||
4. Antworte als flache JSON-Liste.
|
4. Antworte als flache JSON-Liste.
|
||||||
|
|
||||||
DEIN OUTPUT (JSON):
|
DEIN OUTPUT (JSON):
|
||||||
|
|
||||||
gemini: |
|
gemini: |
|
||||||
TASK: Ordne Kanten einem Textabschnitt zu.
|
TASK: Ordne Kanten einem Textabschnitt zu.
|
||||||
ERLAUBTE TYPEN: {valid_types}
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
TEXT: {chunk_text}
|
TEXT: {chunk_text}
|
||||||
KANDIDATEN: {edge_list}
|
KANDIDATEN: {edge_list}
|
||||||
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte!
|
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte!
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
TASK: Filtere relevante Kanten aus dem Pool.
|
TASK: Filtere relevante Kanten aus dem Pool.
|
||||||
ERLAUBTE TYPEN: {valid_types}
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
|
@ -229,8 +290,10 @@ edge_allocation_template:
|
||||||
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
|
|
||||||
|
default: "[]"
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
# 8. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
edge_extraction:
|
edge_extraction:
|
||||||
ollama: |
|
ollama: |
|
||||||
|
|
@ -254,11 +317,13 @@ edge_extraction:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEIN OUTPUT (JSON):
|
DEIN OUTPUT (JSON):
|
||||||
|
|
||||||
gemini: |
|
gemini: |
|
||||||
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
||||||
ERLAUBTE TYPEN: {valid_types}
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
TEXT: {text}
|
TEXT: {text}
|
||||||
OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: [].
|
OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: [].
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
TASK: Extrahiere semantische Relationen für '{note_id}'.
|
TASK: Extrahiere semantische Relationen für '{note_id}'.
|
||||||
ERLAUBTE TYPEN: {valid_types}
|
ERLAUBTE TYPEN: {valid_types}
|
||||||
|
|
@ -269,10 +334,20 @@ edge_extraction:
|
||||||
Wenn keine Relationen existieren, antworte NUR mit: []
|
Wenn keine Relationen existieren, antworte NUR mit: []
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
|
|
||||||
|
default: "[]"
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
|
# 9. INGESTION: EDGE VALIDATION (Ingest/Validate)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
edge_validation:
|
edge_validation:
|
||||||
|
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||||
|
"mistralai/mistral-7b-instruct:free": |
|
||||||
|
Verify relation '{edge_kind}' for graph integrity.
|
||||||
|
Chunk: "{chunk_text}"
|
||||||
|
Target: "{target_title}" ({target_summary})
|
||||||
|
Respond ONLY with 'YES' or 'NO'.
|
||||||
|
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
gemini: |
|
gemini: |
|
||||||
Bewerte die semantische Validität dieser Verbindung im Wissensgraph.
|
Bewerte die semantische Validität dieser Verbindung im Wissensgraph.
|
||||||
|
|
||||||
|
|
@ -287,6 +362,7 @@ edge_validation:
|
||||||
|
|
||||||
FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel?
|
FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel?
|
||||||
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
Verify semantic relation for graph construction.
|
Verify semantic relation for graph construction.
|
||||||
Source Context: {chunk_text}
|
Source Context: {chunk_text}
|
||||||
|
|
@ -295,6 +371,7 @@ edge_validation:
|
||||||
Proposed Relation: {edge_kind}
|
Proposed Relation: {edge_kind}
|
||||||
Instruction: Does the source context support this relation to the target?
|
Instruction: Does the source context support this relation to the target?
|
||||||
Result: Respond ONLY with 'YES' or 'NO'.
|
Result: Respond ONLY with 'YES' or 'NO'.
|
||||||
|
|
||||||
ollama: |
|
ollama: |
|
||||||
Bewerte die semantische Korrektheit dieser Verbindung.
|
Bewerte die semantische Korrektheit dieser Verbindung.
|
||||||
QUELLE: {chunk_text}
|
QUELLE: {chunk_text}
|
||||||
|
|
@ -302,10 +379,19 @@ edge_validation:
|
||||||
BEZIEHUNG: {edge_kind}
|
BEZIEHUNG: {edge_kind}
|
||||||
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
||||||
|
|
||||||
|
default: "YES"
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
intent_router_v1:
|
intent_router_v1:
|
||||||
|
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||||
|
"mistralai/mistral-7b-instruct:free": |
|
||||||
|
Classify query "{query}" into exactly one of these categories:
|
||||||
|
FACT_WHEN, FACT_WHAT, DECISION, EMPATHY, CODING, INTERVIEW.
|
||||||
|
Respond with the category name only.
|
||||||
|
|
||||||
|
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||||
ollama: |
|
ollama: |
|
||||||
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
||||||
Antworte NUR mit dem Namen der Strategie.
|
Antworte NUR mit dem Namen der Strategie.
|
||||||
|
|
@ -320,6 +406,7 @@ intent_router_v1:
|
||||||
|
|
||||||
NACHRICHT: "{query}"
|
NACHRICHT: "{query}"
|
||||||
STRATEGIE:
|
STRATEGIE:
|
||||||
|
|
||||||
gemini: |
|
gemini: |
|
||||||
Classify intent:
|
Classify intent:
|
||||||
- FACT_WHEN: Exact dates/times only.
|
- FACT_WHEN: Exact dates/times only.
|
||||||
|
|
@ -330,8 +417,11 @@ intent_router_v1:
|
||||||
- INTERVIEW: Data entry.
|
- INTERVIEW: Data entry.
|
||||||
Query: "{query}"
|
Query: "{query}"
|
||||||
Result (One word only):
|
Result (One word only):
|
||||||
|
|
||||||
openrouter: |
|
openrouter: |
|
||||||
Select strategy for Mindnet:
|
Select strategy for Mindnet:
|
||||||
FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW.
|
FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW.
|
||||||
Query: "{query}"
|
Query: "{query}"
|
||||||
Response:
|
Response:
|
||||||
|
|
||||||
|
default: "FACT_WHAT"
|
||||||
Loading…
Reference in New Issue
Block a user