WP25b #21
|
|
@ -1,13 +1,13 @@
|
|||
"""
|
||||
FILE: app/core/ingestion/ingestion_validation.py
|
||||
DESCRIPTION: WP-15b semantische Validierung von Kanten gegen den LocalBatchCache.
|
||||
WP-25a: Integration der Mixture of Experts (MoE) Profil-Steuerung.
|
||||
VERSION: 2.13.0 (WP-25a: MoE & Profile Support)
|
||||
WP-25b: Umstellung auf Lazy-Prompt-Orchestration (prompt_key + variables).
|
||||
VERSION: 2.14.0 (WP-25b: Lazy Prompt Integration)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- Umstellung auf generate_raw_response mit profile_name="ingest_validator".
|
||||
- Automatische Nutzung der Fallback-Kaskade (Cloud -> Lokal) via LLMService.
|
||||
- Erhalt der sparsamen LLM-Nutzung (Validierung nur für Kandidaten-Pool).
|
||||
- WP-25b: Entfernung manueller Prompt-Formatierung zur Unterstützung modell-spezifischer Prompts.
|
||||
- WP-25b: Umstellung auf generate_raw_response mit prompt_key="edge_validation".
|
||||
- WP-25a: Voller Erhalt der MoE-Profilsteuerung und Fallback-Kaskade via LLMService.
|
||||
"""
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
|
@ -27,8 +27,8 @@ async def validate_edge_candidate(
|
|||
profile_name: str = "ingest_validator"
|
||||
) -> bool:
|
||||
"""
|
||||
WP-15b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
||||
Nutzt das MoE-Profil 'ingest_validator' für deterministische YES/NO Prüfungen.
|
||||
WP-15b/25b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
||||
Nutzt Lazy-Prompt-Loading zur Unterstützung modell-spezifischer Validierungs-Templates.
|
||||
"""
|
||||
target_id = edge.get("to")
|
||||
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.")
|
||||
return True
|
||||
|
||||
# Prompt-Abruf (Nutzt Provider-String als Fallback-Key für die prompts.yaml)
|
||||
template = llm_service.get_prompt("edge_validation", provider)
|
||||
|
||||
try:
|
||||
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)
|
||||
# Nutzt ingest_validator (Cloud Mistral/Gemini -> Local Phi3:mini Kaskade)
|
||||
# WP-25b: Lazy-Prompt Aufruf.
|
||||
# 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(
|
||||
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",
|
||||
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)
|
||||
|
||||
# Semantische Prüfung des Ergebnisses
|
||||
|
|
@ -75,6 +73,7 @@ async def validate_edge_candidate(
|
|||
else:
|
||||
logger.info(f"🚫 [REJECTED] Relation to '{target_id}' irrelevant for this chunk.")
|
||||
return is_valid
|
||||
|
||||
except Exception as 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
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
"""
|
||||
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
|
||||
und die neue Pre-Synthesis Kompression (Module A).
|
||||
VERSION: 1.2.1 (WP-25a: Profile-Driven Orchestration & Optimized Cascade)
|
||||
und die neue Lazy-Prompt Orchestrierung (Module A & B).
|
||||
VERSION: 1.3.0 (WP-25b: Lazy Prompt Orchestration)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-25a: Volle Integration der Profil-Kaskade (Delegation an LLMService v3.5.2).
|
||||
- WP-25a: Dynamische Nutzung des 'router_profile' für die Intent-Erkennung.
|
||||
- WP-25a: Parallelisierte Kompression überlanger Wissens-Streams.
|
||||
- WP-25b: Umstellung auf Lazy-Loading (Übergabe von prompt_key + variables).
|
||||
- WP-25b: Entfernung lokaler String-Formatierung zur Ermöglichung modell-spezifischer Prompts.
|
||||
- WP-25a: Volle Integration der Profil-Kaskade via LLMService v3.5.5.
|
||||
- WP-25: Beibehaltung von Stream-Tracing und Pre-Initialization Robustness.
|
||||
- CLEANUP: Entfernung redundanter Fallback-Blocks (jetzt im LLMService).
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
|
@ -70,29 +69,27 @@ class DecisionEngine:
|
|||
if not strategy:
|
||||
return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert."
|
||||
|
||||
# 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks)
|
||||
# WP-25a: Diese Methode übernimmt nun auch die Kompression.
|
||||
# 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks inkl. Kompression)
|
||||
stream_results = await self._execute_parallel_streams(strategy, query)
|
||||
|
||||
# 3. Finale Synthese
|
||||
return await self._generate_final_answer(strategy_key, strategy, query, stream_results)
|
||||
|
||||
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", {})
|
||||
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_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:
|
||||
# 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(
|
||||
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()
|
||||
except Exception as e:
|
||||
|
|
@ -102,7 +99,7 @@ class DecisionEngine:
|
|||
async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]:
|
||||
"""
|
||||
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", [])
|
||||
library = self.config.get("streams_library", {})
|
||||
|
|
@ -116,7 +113,7 @@ class DecisionEngine:
|
|||
active_streams.append(key)
|
||||
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)
|
||||
|
||||
# Phase 2: Formatierung und optionale Kompression
|
||||
|
|
@ -137,38 +134,32 @@ class DecisionEngine:
|
|||
threshold = stream_cfg.get("compression_threshold", 4000)
|
||||
|
||||
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")
|
||||
final_stream_tasks.append(
|
||||
self._compress_stream_content(name, formatted_context, query, comp_profile)
|
||||
)
|
||||
else:
|
||||
# Direkt-Übernahme als Coroutine für gather()
|
||||
async def _direct(c=formatted_context): return c
|
||||
final_stream_tasks.append(_direct())
|
||||
|
||||
# Finale Inhalte (evtl. komprimiert) parallel fertigstellen
|
||||
# Finale Inhalte parallel fertigstellen
|
||||
final_contents = await asyncio.gather(*final_stream_tasks)
|
||||
|
||||
return dict(zip(active_streams, final_contents))
|
||||
|
||||
async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str:
|
||||
"""
|
||||
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:"
|
||||
)
|
||||
|
||||
"""WP-25b Module A: Inhaltsverdichtung via Lazy-Loading 'compression_template'."""
|
||||
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(
|
||||
compression_prompt,
|
||||
profile_name=profile, # WP-25a: MoE Support
|
||||
prompt_key="compression_template",
|
||||
variables={
|
||||
"stream_name": stream_name,
|
||||
"content": content,
|
||||
"query": query
|
||||
},
|
||||
profile_name=profile,
|
||||
priority="background",
|
||||
max_retries=1
|
||||
)
|
||||
|
|
@ -191,24 +182,19 @@ class DecisionEngine:
|
|||
)
|
||||
|
||||
response = await self.retriever.search(request)
|
||||
|
||||
# WP-25: STREAM-TRACING
|
||||
for hit in response.results:
|
||||
hit.stream_origin = name
|
||||
|
||||
return response
|
||||
|
||||
def _format_stream_context(self, response: QueryResponse) -> str:
|
||||
"""Wandelt QueryHits in einen formatierten Kontext-String um."""
|
||||
if not response.results:
|
||||
return "Keine spezifischen Informationen in diesem Stream gefunden."
|
||||
|
||||
lines = []
|
||||
for i, hit in enumerate(response.results, 1):
|
||||
source = hit.source.get("path", "Unbekannt")
|
||||
content = hit.source.get("text", "").strip()
|
||||
lines.append(f"[{i}] QUELLE: {source}\nINHALT: {content}")
|
||||
|
||||
return "\n\n".join(lines)
|
||||
|
||||
async def _generate_final_answer(
|
||||
|
|
@ -218,12 +204,9 @@ class DecisionEngine:
|
|||
query: str,
|
||||
stream_results: Dict[str, str]
|
||||
) -> str:
|
||||
"""Führt die finale Synthese basierend auf dem Strategie-Profil durch."""
|
||||
# WP-25a: Nutzt das llm_profile der Strategie
|
||||
"""WP-25b: Finale Synthese via Lazy-Prompt 'rag_template'."""
|
||||
profile = strategy.get("llm_profile")
|
||||
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")
|
||||
|
||||
# WP-25 ROBUSTNESS: Pre-Initialization
|
||||
|
|
@ -232,28 +215,15 @@ class DecisionEngine:
|
|||
template_vars.update(stream_results)
|
||||
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:
|
||||
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(
|
||||
f"Beantworte: {query}\n\nKontext:\n{fallback_context}",
|
||||
system=system_prompt, priority="realtime", profile_name=profile
|
||||
prompt_key=template_key,
|
||||
variables=template_vars,
|
||||
system=system_prompt,
|
||||
profile_name=profile,
|
||||
priority="realtime"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Final Synthesis failed: {e}")
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
"""
|
||||
FILE: app/routers/chat.py
|
||||
DESCRIPTION: Haupt-Chat-Interface (WP-25a Agentic Edition).
|
||||
Kombiniert die spezialisierte Interview-Logik und Keyword-Erkennung
|
||||
mit der neuen MoE-Orchestrierung und Pre-Synthesis Kompression.
|
||||
VERSION: 3.0.4 (WP-25a: Optimized MoE & Cascade Delegation)
|
||||
DESCRIPTION: Haupt-Chat-Interface (WP-25b Edition).
|
||||
Kombiniert die spezialisierte Interview-Logik mit der neuen
|
||||
Lazy-Prompt-Orchestration und MoE-Synthese.
|
||||
VERSION: 3.0.5 (WP-25b: Lazy Prompt Integration)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-25a: Delegation der Fallback-Kaskade an den LLMService (v3.5.2).
|
||||
- WP-25a: Nutzung der zentralisierten Stream-Kompression der DecisionEngine (v1.2.1).
|
||||
- WP-25a: Konsistente Nutzung von MoE-Profilen für Interview- und RAG-Modus.
|
||||
- 100% Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths).
|
||||
- WP-25b: Umstellung des Interview-Modus auf Lazy-Prompt (prompt_key + variables).
|
||||
- WP-25b: Delegation der RAG-Phase an die Engine v1.3.0 für konsistente MoE-Steuerung.
|
||||
- WP-25a: Voller Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths).
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
|
|
@ -146,7 +145,7 @@ def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]:
|
|||
all_hits = []
|
||||
seen_node_ids = set()
|
||||
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'):
|
||||
for hit in response.results:
|
||||
if hit.node_id not in seen_node_ids:
|
||||
|
|
@ -166,8 +165,7 @@ async def chat_endpoint(
|
|||
):
|
||||
start_time = time.time()
|
||||
query_id = str(uuid.uuid4())
|
||||
settings = get_settings()
|
||||
logger.info(f"🚀 [WP-25a] Chat request [{query_id}]: {request.message[:50]}...")
|
||||
logger.info(f"🚀 [WP-25b] Chat request [{query_id}]: {request.message[:50]}...")
|
||||
|
||||
try:
|
||||
# 1. Intent Detection
|
||||
|
|
@ -180,7 +178,7 @@ async def chat_endpoint(
|
|||
sources_hits = []
|
||||
answer_text = ""
|
||||
|
||||
# 2. INTERVIEW MODE (Bitgenaue WP-07 Logik)
|
||||
# 2. INTERVIEW MODE (WP-25b Lazy-Prompt Logik)
|
||||
if intent == "INTERVIEW":
|
||||
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
||||
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_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) \
|
||||
.replace("{target_type}", target_type) \
|
||||
.replace("{schema_fields}", fields_str)
|
||||
|
||||
# WP-25a: MoE Call (Kaskade erfolgt intern im LLMService)
|
||||
# WP-25b: Lazy Loading Call
|
||||
# Wir übergeben nur Key und Variablen. Das System formatiert passend zum Modell.
|
||||
answer_text = await llm.generate_raw_response(
|
||||
final_prompt, system=llm.get_prompt("system_prompt"),
|
||||
priority="realtime", profile_name="compression_fast", max_retries=0
|
||||
prompt_key=template_key,
|
||||
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 = []
|
||||
|
||||
# 3. RAG MODE (Optimierte MoE Orchestrierung)
|
||||
# 3. RAG MODE (WP-25b Delegation an Engine v1.3.0)
|
||||
else:
|
||||
# Phase A & B: Retrieval & Kompression (Delegation an Engine v1.2.1)
|
||||
# Diese Methode gibt bereits die (evtl. komprimierten) Kontext-Strings zurück.
|
||||
# Phase A & B: Retrieval & Kompression (Delegiert an Engine v1.3.0)
|
||||
formatted_context_map = await engine._execute_parallel_streams(strategy, request.message)
|
||||
|
||||
# Erfassung der Quellen für das Tracing
|
||||
|
|
@ -232,7 +234,7 @@ async def chat_endpoint(
|
|||
|
||||
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(
|
||||
intent, strategy, request.message, formatted_context_map
|
||||
)
|
||||
|
|
@ -243,7 +245,7 @@ async def chat_endpoint(
|
|||
try:
|
||||
log_search(
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
"""
|
||||
FILE: app/services/llm_service.py
|
||||
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
||||
WP-25a: Implementierung der Mixture of Experts (MoE) Kaskaden-Steuerung.
|
||||
VERSION: 3.5.2 (WP-25a: MoE & Fallback Cascade Support)
|
||||
WP-25b: Implementierung der Lazy-Prompt-Orchestration (Modell-spezifisch).
|
||||
VERSION: 3.5.5 (WP-25b: Prompt Orchestration & Full Resilience)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-25a: Implementierung der rekursiven Fallback-Kaskade via fallback_profile.
|
||||
- WP-25a: Schutz gegen zirkuläre Profil-Referenzen (visited_profiles).
|
||||
- WP-25a: Erweitertes Logging für Tracing der Experten-Entscheidungen.
|
||||
- Erhalt der Ingest-Stability (WP-25) und des Rate-Limit-Managements.
|
||||
- WP-25b: get_prompt() unterstützt Hierarchie: Model-ID -> Provider -> Default.
|
||||
- WP-25b: generate_raw_response() unterstützt prompt_key + variables für Lazy-Formatting.
|
||||
- WP-25a: Voller Erhalt der rekursiven Fallback-Kaskade und visited_profiles Schutz.
|
||||
- WP-20: Restaurierung des internen Ollama-Retry-Loops für Hardware-Stabilität.
|
||||
"""
|
||||
import httpx
|
||||
import yaml
|
||||
|
|
@ -33,10 +33,7 @@ class LLMService:
|
|||
def __init__(self):
|
||||
self.settings = get_settings()
|
||||
self.prompts = self._load_prompts()
|
||||
|
||||
# WP-25a: Zentrale Experten-Profile laden
|
||||
self.profiles = self._load_llm_profiles()
|
||||
|
||||
self._decision_engine = 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 = Path(path_str)
|
||||
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 {}
|
||||
try:
|
||||
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}")
|
||||
return {}
|
||||
|
||||
def get_prompt(self, key: str, provider: str = None) -> str:
|
||||
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||
def get_prompt(self, key: str, model_id: str = None, provider: str = None) -> str:
|
||||
"""
|
||||
WP-25b: Hochpräziser Prompt-Lookup.
|
||||
Hierarchie: Exakte Modell-ID -> Provider-Name -> Globaler Default.
|
||||
"""
|
||||
data = self.prompts.get(key, "")
|
||||
if isinstance(data, dict):
|
||||
val = data.get(active_provider, data.get("gemini", data.get("ollama", "")))
|
||||
return str(val)
|
||||
return str(data)
|
||||
if not isinstance(data, dict):
|
||||
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(
|
||||
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,
|
||||
force_json: bool = False,
|
||||
max_retries: int = 2,
|
||||
|
|
@ -126,16 +137,14 @@ class LLMService:
|
|||
profile_name: Optional[str] = None,
|
||||
visited_profiles: Optional[list] = None
|
||||
) -> str:
|
||||
"""
|
||||
Haupteinstiegspunkt für LLM-Anfragen mit rekursiver Kaskaden-Logik.
|
||||
"""
|
||||
"""Haupteinstiegspunkt für LLM-Anfragen mit Lazy-Prompt Orchestrierung."""
|
||||
visited_profiles = visited_profiles or []
|
||||
target_provider = provider
|
||||
target_model = model_override
|
||||
target_temp = None
|
||||
fallback_profile = None
|
||||
|
||||
# 1. Profil-Auflösung
|
||||
# 1. Profil-Auflösung (Mixture of Experts)
|
||||
if profile_name and self.profiles:
|
||||
profile = self.profiles.get(profile_name)
|
||||
if profile:
|
||||
|
|
@ -148,30 +157,39 @@ class LLMService:
|
|||
else:
|
||||
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:
|
||||
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:
|
||||
if priority == "background":
|
||||
async with LLMService._background_semaphore:
|
||||
res = await self._dispatch(
|
||||
target_provider, prompt, system, force_json,
|
||||
target_provider, current_prompt, system, force_json,
|
||||
max_retries, base_delay, target_model,
|
||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||
)
|
||||
else:
|
||||
res = await self._dispatch(
|
||||
target_provider, prompt, system, force_json,
|
||||
target_provider, current_prompt, system, force_json,
|
||||
max_retries, base_delay, target_model,
|
||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||
)
|
||||
|
||||
# Check auf leere Cloud-Antworten (WP-25 Stability)
|
||||
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}")
|
||||
|
||||
return clean_llm_text(res) if not force_json else res
|
||||
|
|
@ -179,40 +197,33 @@ class LLMService:
|
|||
except Exception as 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:
|
||||
logger.info(f"🔄 Switching to fallback profile: '{fallback_profile}'")
|
||||
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,
|
||||
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,
|
||||
strict_json_schema=strict_json_schema,
|
||||
profile_name=fallback_profile,
|
||||
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:
|
||||
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
|
||||
|
||||
raise e
|
||||
|
||||
async def _dispatch(
|
||||
self,
|
||||
provider: str,
|
||||
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
|
||||
self, provider, prompt, system, force_json, max_retries, base_delay,
|
||||
model_override, json_schema, json_schema_name, strict_json_schema, temperature
|
||||
) -> str:
|
||||
"""Routet die Anfrage an den spezifischen Provider-Executor."""
|
||||
rate_limit_attempts = 0
|
||||
|
|
@ -232,23 +243,19 @@ class LLMService:
|
|||
if provider == "gemini" and self.google_client:
|
||||
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:
|
||||
err_str = str(e)
|
||||
# Rate-Limit Handling (429)
|
||||
if any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited"]):
|
||||
rate_limit_attempts += 1
|
||||
logger.warning(f"⏳ Rate Limit {provider}. Attempt {rate_limit_attempts}. Wait {wait_time}s.")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
# Andere Fehler werden an generate_raw_response für die Kaskade gereicht
|
||||
raise e
|
||||
|
||||
async def _execute_google(self, prompt, system, force_json, model_override, temperature):
|
||||
model = model_override or self.settings.GEMINI_MODEL
|
||||
clean_model = model.replace("models/", "")
|
||||
|
||||
model = (model_override or self.settings.GEMINI_MODEL).replace("models/", "")
|
||||
config_kwargs = {
|
||||
"system_instruction": system,
|
||||
"response_mime_type": "application/json" if force_json else "text/plain"
|
||||
|
|
@ -257,22 +264,13 @@ class LLMService:
|
|||
config_kwargs["temperature"] = temperature
|
||||
|
||||
config = types.GenerateContentConfig(**config_kwargs)
|
||||
|
||||
response = await asyncio.wait_for(
|
||||
asyncio.to_thread(
|
||||
self.google_client.models.generate_content,
|
||||
model=clean_model, contents=prompt, config=config
|
||||
),
|
||||
asyncio.to_thread(self.google_client.models.generate_content, model=model, contents=prompt, config=config),
|
||||
timeout=45.0
|
||||
)
|
||||
return response.text.strip()
|
||||
|
||||
async def _execute_openrouter(
|
||||
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:
|
||||
async def _execute_openrouter(self, prompt, system, force_json, model_override, json_schema, json_schema_name, strict_json_schema, temperature) -> str:
|
||||
model = model_override or self.settings.OPENROUTER_MODEL
|
||||
logger.info(f"🛰️ OpenRouter Call: Model='{model}' | Temp={temperature}")
|
||||
messages = []
|
||||
|
|
@ -280,35 +278,26 @@ class LLMService:
|
|||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
kwargs: Dict[str, Any] = {}
|
||||
if temperature is not None:
|
||||
kwargs["temperature"] = temperature
|
||||
if temperature is not None: kwargs["temperature"] = temperature
|
||||
|
||||
if force_json:
|
||||
if json_schema:
|
||||
kwargs["response_format"] = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}
|
||||
}
|
||||
kwargs["response_format"] = {"type": "json_schema", "json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}}
|
||||
else:
|
||||
kwargs["response_format"] = {"type": "json_object"}
|
||||
|
||||
response = await self.openrouter_client.chat.completions.create(
|
||||
model=model, messages=messages, **kwargs
|
||||
)
|
||||
|
||||
if not response.choices:
|
||||
return ""
|
||||
|
||||
response = await self.openrouter_client.chat.completions.create(model=model, messages=messages, **kwargs)
|
||||
if not response.choices: return ""
|
||||
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):
|
||||
# Nutzt Profil-Temperatur oder strikte Defaults für lokale Hardware-Schonung
|
||||
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None, model_override=None):
|
||||
# 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)
|
||||
|
||||
payload = {
|
||||
"model": self.settings.LLM_MODEL,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"model": effective_model,
|
||||
"prompt": prompt, "stream": False,
|
||||
"options": {"temperature": effective_temp, "num_ctx": 8192}
|
||||
}
|
||||
if force_json: payload["format"] = "json"
|
||||
|
|
@ -323,12 +312,11 @@ class LLMService:
|
|||
except Exception as e:
|
||||
attempt += 1
|
||||
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
|
||||
await asyncio.sleep(base_delay * (2 ** (attempt - 1)))
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
# 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).
|
||||
# - 100% Erhalt der Original-Prompts aus v3.1.2 für die Provider-Ebene (ollama, gemini, openrouter).
|
||||
# - Integration der Modell-spezifischen Overrides für Gemini 2.0, Llama 3.3 und Qwen 2.5.
|
||||
# - Hinzufügen des notwendigen 'compression_template' für die DecisionEngine v1.3.0.
|
||||
|
||||
system_prompt: |
|
||||
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||
|
|
@ -20,8 +20,19 @@ system_prompt: |
|
|||
# ---------------------------------------------------------
|
||||
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
||||
# ---------------------------------------------------------
|
||||
# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams.
|
||||
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: |
|
||||
WISSENS-STREAMS:
|
||||
=========================================
|
||||
|
|
@ -42,22 +53,32 @@ fact_synthesis_v1:
|
|||
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.
|
||||
|
||||
default: "Beantworte {query} basierend auf dem Kontext: {facts_stream} {biography_stream} {tech_stream}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||
# ---------------------------------------------------------
|
||||
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
|
||||
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: |
|
||||
ENTSCHEIDUNGS-STREAMS:
|
||||
=========================================
|
||||
|
|
@ -84,19 +105,24 @@ decision_synthesis_v1:
|
|||
- **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.
|
||||
|
||||
default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||
# ---------------------------------------------------------
|
||||
empathy_template:
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||
=========================================
|
||||
|
|
@ -118,13 +144,23 @@ empathy_template:
|
|||
|
||||
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}"
|
||||
|
||||
default: "Reflektiere empathisch über {query} basierend auf {biography_stream}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||
# ---------------------------------------------------------
|
||||
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: |
|
||||
KONTEXT (WISSEN & PROJEKTE):
|
||||
=========================================
|
||||
|
|
@ -148,13 +184,17 @@ technical_template:
|
|||
- 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}"
|
||||
|
||||
default: "Erstelle eine technische Lösung für {query}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
||||
# ---------------------------------------------------------
|
||||
interview_template:
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
TASK:
|
||||
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)
|
||||
(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}."
|
||||
|
||||
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:
|
||||
ollama: |
|
||||
|
|
@ -213,12 +272,14 @@ edge_allocation_template:
|
|||
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}
|
||||
|
|
@ -229,8 +290,10 @@ edge_allocation_template:
|
|||
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
||||
OUTPUT:
|
||||
|
||||
default: "[]"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||
# 8. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||
# ---------------------------------------------------------
|
||||
edge_extraction:
|
||||
ollama: |
|
||||
|
|
@ -254,11 +317,13 @@ edge_extraction:
|
|||
"""
|
||||
|
||||
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}
|
||||
|
|
@ -269,10 +334,20 @@ edge_extraction:
|
|||
Wenn keine Relationen existieren, antworte NUR mit: []
|
||||
OUTPUT:
|
||||
|
||||
default: "[]"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
|
||||
# 9. INGESTION: EDGE VALIDATION (Ingest/Validate)
|
||||
# ---------------------------------------------------------
|
||||
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: |
|
||||
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?
|
||||
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
||||
|
||||
openrouter: |
|
||||
Verify semantic relation for graph construction.
|
||||
Source Context: {chunk_text}
|
||||
|
|
@ -295,6 +371,7 @@ edge_validation:
|
|||
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}
|
||||
|
|
@ -302,10 +379,19 @@ edge_validation:
|
|||
BEZIEHUNG: {edge_kind}
|
||||
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
||||
|
||||
default: "YES"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
||||
# ---------------------------------------------------------
|
||||
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: |
|
||||
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
||||
Antworte NUR mit dem Namen der Strategie.
|
||||
|
|
@ -320,6 +406,7 @@ intent_router_v1:
|
|||
|
||||
NACHRICHT: "{query}"
|
||||
STRATEGIE:
|
||||
|
||||
gemini: |
|
||||
Classify intent:
|
||||
- FACT_WHEN: Exact dates/times only.
|
||||
|
|
@ -330,8 +417,11 @@ intent_router_v1:
|
|||
- 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:
|
||||
Response:
|
||||
|
||||
default: "FACT_WHAT"
|
||||
Loading…
Reference in New Issue
Block a user