bug fix Wp20
This commit is contained in:
parent
2c073c7d3c
commit
867a7a8b44
|
|
@ -5,7 +5,8 @@ DESCRIPTION: Haupt-Ingestion-Logik. Transformiert Markdown in den Graphen (Notes
|
|||
WP-22: Integration von Content Lifecycle (Status Gate) und Edge Registry Validation.
|
||||
WP-22: Kontextsensitive Kanten-Validierung mit Fundort-Reporting (Zeilennummern).
|
||||
WP-22: Multi-Hash Refresh für konsistente Change Detection.
|
||||
VERSION: 2.11.4
|
||||
FIX: Robuste Verarbeitung von LLM-Antworten (Dict vs String) zur Vermeidung von Item-Assignment-Errors.
|
||||
VERSION: 2.11.5
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.core.parser, app.core.note_payload, app.core.chunker, app.services.llm_service, app.services.edge_registry
|
||||
EXTERNAL_CONFIG: config/types.yaml, config/prompts.yaml
|
||||
|
|
@ -96,12 +97,11 @@ class IngestionService:
|
|||
self.cfg = QdrantConfig.from_env()
|
||||
self.cfg.prefix = self.prefix
|
||||
self.client = get_client(self.cfg)
|
||||
self.dim = self.settings.VECTOR_SIZE # Synchronisiert mit Settings v0.6.2
|
||||
self.dim = self.settings.VECTOR_SIZE
|
||||
self.registry = load_type_registry()
|
||||
self.embedder = EmbeddingsClient()
|
||||
self.llm = LLMService()
|
||||
|
||||
# WP-22: Change Detection Modus aus Settings
|
||||
self.active_hash_mode = self.settings.CHANGE_DETECTION_MODE
|
||||
|
||||
try:
|
||||
|
|
@ -111,7 +111,7 @@ class IngestionService:
|
|||
logger.warning(f"DB init warning: {e}")
|
||||
|
||||
def _get_chunk_config_by_profile(self, profile_name: str, note_type: str) -> Dict[str, Any]:
|
||||
"""Holt die Chunker-Parameter (max, target, overlap) für ein spezifisches Profil."""
|
||||
"""Holt die Chunker-Parameter für ein spezifisches Profil."""
|
||||
profiles = self.registry.get("chunking_profiles", {})
|
||||
if profile_name in profiles:
|
||||
cfg = profiles[profile_name].copy()
|
||||
|
|
@ -125,18 +125,25 @@ class IngestionService:
|
|||
WP-20: Nutzt den Hybrid LLM Service für die semantische Kanten-Extraktion.
|
||||
QUOTEN-SCHUTZ: Bevorzugt OpenRouter (Gemma 2), um Gemini-Tageslimits zu schonen.
|
||||
"""
|
||||
# Bestimme Provider: Nutze OpenRouter falls Key vorhanden
|
||||
provider = "openrouter" if self.settings.OPENROUTER_API_KEY else self.settings.MINDNET_LLM_PROVIDER
|
||||
model = self.settings.GEMMA_MODEL # Hochdurchsatz-Modell aus config.py
|
||||
model = self.settings.GEMMA_MODEL
|
||||
|
||||
logger.info(f"🚀 [Ingestion] Turbo-Mode: Extracting edges for '{note_id}' using {model} on {provider}")
|
||||
|
||||
# Hole das optimierte Prompt-Template (Kaskade: Provider -> gemini -> ollama)
|
||||
# WP-22: Hole valide Typen für das Prompt-Template
|
||||
edge_registry.ensure_latest()
|
||||
valid_types_str = ", ".join(sorted(list(edge_registry.valid_types)))
|
||||
|
||||
template = self.llm.get_prompt("edge_extraction", provider)
|
||||
prompt = template.format(text=text[:6000], note_id=note_id)
|
||||
|
||||
try:
|
||||
# Hintergrund-Task mit Semaphore via LLMService (WP-06)
|
||||
# Befülle das Template (v2.5.0 erwartet valid_types)
|
||||
prompt = template.format(
|
||||
text=text[:6000],
|
||||
note_id=note_id,
|
||||
valid_types=valid_types_str
|
||||
)
|
||||
|
||||
response_json = await self.llm.generate_raw_response(
|
||||
prompt=prompt,
|
||||
priority="background",
|
||||
|
|
@ -144,12 +151,44 @@ class IngestionService:
|
|||
provider=provider,
|
||||
model_override=model
|
||||
)
|
||||
data = json.loads(response_json)
|
||||
|
||||
for item in data:
|
||||
item["provenance"] = "semantic_ai"
|
||||
item["line"] = f"ai-{provider}"
|
||||
return data
|
||||
# Robustes Parsing (WP-20 Fix für 'str' object assignment error)
|
||||
raw_data = json.loads(response_json)
|
||||
processed_edges = []
|
||||
|
||||
# Das LLM liefert manchmal ein Dict mit einem Key statt einer Liste
|
||||
if isinstance(raw_data, dict):
|
||||
logger.debug(f"ℹ️ [Ingestion] LLM returned dict for {note_id}, attempting recovery.")
|
||||
for key in ["edges", "links", "results", "kanten"]:
|
||||
if key in raw_data and isinstance(raw_data[key], list):
|
||||
raw_data = raw_data[key]
|
||||
break
|
||||
|
||||
if not isinstance(raw_data, list):
|
||||
logger.warning(f"⚠️ [Ingestion] LLM output for {note_id} is not a list: {type(raw_data)}")
|
||||
return []
|
||||
|
||||
for item in raw_data:
|
||||
# Fall 1: Element ist bereits ein Dict (Idealfall)
|
||||
if isinstance(item, dict) and "to" in item:
|
||||
item["provenance"] = "semantic_ai"
|
||||
item["line"] = f"ai-{provider}"
|
||||
processed_edges.append(item)
|
||||
|
||||
# Fall 2: Element ist ein String (z.B. "kind:target") -> Umwandlung
|
||||
elif isinstance(item, str) and ":" in item:
|
||||
parts = item.split(":", 1)
|
||||
processed_edges.append({
|
||||
"to": parts[1].strip(),
|
||||
"kind": parts[0].strip(),
|
||||
"provenance": "semantic_ai",
|
||||
"line": f"ai-{provider}"
|
||||
})
|
||||
else:
|
||||
logger.debug(f"⏩ [Ingestion] Skipping unparseable AI edge: {item}")
|
||||
|
||||
return processed_edges
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ [Ingestion] Smart Edge Allocation failed for {note_id} on {provider}: {e}")
|
||||
return []
|
||||
|
|
@ -256,7 +295,9 @@ class IngestionService:
|
|||
# B. WP-20: Smart AI Edges (Hybrid Turbo Acceleration)
|
||||
ai_edges = await self._perform_smart_edge_allocation(body_text, note_id)
|
||||
for e in ai_edges:
|
||||
e["kind"] = edge_registry.resolve(edge_type=e.get("kind"), provenance="semantic_ai", context={**context, "line": e.get("line")})
|
||||
# Validierung gegen EdgeRegistry (Vermeidet 'Transition' etc.)
|
||||
valid_kind = edge_registry.resolve(edge_type=e.get("kind"), provenance="semantic_ai", context={**context, "line": e.get("line")})
|
||||
e["kind"] = valid_kind
|
||||
edges.append(e)
|
||||
|
||||
# C. System-Kanten (Struktur)
|
||||
|
|
|
|||
|
|
@ -31,12 +31,10 @@ rag_template:
|
|||
Beantworte die Frage präzise basierend auf den Quellen.
|
||||
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||
gemini: |
|
||||
Nutze das Wissen meines digitalen Zwillings aus folgendem Kontext: {context_str}
|
||||
Beantworte die Anfrage präzise, detailliert und strukturiert: {query}
|
||||
Kontext meines digitalen Zwillings: {context_str}
|
||||
Beantworte strukturiert: {query}
|
||||
openrouter: |
|
||||
Kontext-Analyse für Gemma/Llama:
|
||||
{context_str}
|
||||
|
||||
Kontext: {context_str}
|
||||
Anfrage: {query}
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
|
@ -63,13 +61,10 @@ decision_template:
|
|||
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||
gemini: |
|
||||
Agiere als Senior Strategy Consultant für meinen digitalen Zwilling.
|
||||
Wäge die Frage {query} multiperspektivisch gegen meine Werte und langfristigen Ziele ab.
|
||||
Kontext: {context_str}
|
||||
Agiere als strategischer Partner. Analysiere {query} basierend auf {context_str}.
|
||||
openrouter: |
|
||||
Strategischer Check via OpenRouter/Gemma:
|
||||
Analyse der Entscheidungsfrage: {query}
|
||||
Referenzdaten aus dem Graph: {context_str}
|
||||
Entscheidungsanalyse für: {query}
|
||||
Datenbasis: {context_str}
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||
|
|
@ -92,13 +87,8 @@ empathy_template:
|
|||
|
||||
TONFALL:
|
||||
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||
gemini: |
|
||||
Reflektiere meine aktuelle Situation {query} basierend auf meinen Werten {context_str}.
|
||||
Sei mein empathischer digitaler Zwilling. Antworte als 'Ich'.
|
||||
openrouter: |
|
||||
Empathische Reflexion (OpenRouter):
|
||||
Situation: {query}
|
||||
Persönlicher Kontext: {context_str}
|
||||
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {context_str}"
|
||||
openrouter: "Empathische Analyse: {query}. Kontext: {context_str}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||
|
|
@ -123,13 +113,8 @@ technical_template:
|
|||
- Kurze Erklärung des Ansatzes.
|
||||
- Markdown Code-Block (Copy-Paste fertig).
|
||||
- Wichtige Edge-Cases.
|
||||
gemini: |
|
||||
Du bist Senior Software Engineer. Löse die technische Aufgabe {query}
|
||||
unter Berücksichtigung meiner Dokumentation: {context_str}.
|
||||
openrouter: |
|
||||
Technischer Support via OpenRouter:
|
||||
Task: {query}
|
||||
Kontext-Snippets: {context_str}
|
||||
gemini: "Generiere Code für {query}. Kontext: {context_str}"
|
||||
openrouter: "Technischer Support: {query}. Kontext: {context_str}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode)
|
||||
|
|
@ -166,8 +151,8 @@ interview_template:
|
|||
|
||||
## (Zweiter Begriff aus STRUKTUR)
|
||||
(Text...)
|
||||
gemini: "Transformiere den Input {query} in das Schema {schema_fields} für Typ {target_type}."
|
||||
openrouter: "Extrahiere Daten für Typ {target_type} aus {query}. Schema: {schema_fields}."
|
||||
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
||||
openrouter: "Strukturiere {query} nach {schema_fields}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 6. EDGE_ALLOCATION: Kantenfilter (Intent: OFFLINE_FILTER)
|
||||
|
|
@ -194,16 +179,17 @@ edge_allocation_template:
|
|||
|
||||
DEIN OUTPUT (JSON):
|
||||
gemini: |
|
||||
Analysiere den Textabschnitt: {chunk_text}
|
||||
Wähle aus folgender Liste alle relevanten Kanten aus: {edge_list}
|
||||
Antworte STRIKT als JSON-Liste von Strings im Format ["typ:ziel"].
|
||||
Kein Text davor oder danach!
|
||||
TASK: Ordne Kanten einem Textabschnitt zu.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {chunk_text}
|
||||
KANDIDATEN: {edge_list}
|
||||
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Keine Objekte!
|
||||
openrouter: |
|
||||
Filtere die relevanten Kanten für den Graphen.
|
||||
Kandidaten: {edge_list}
|
||||
Text: {chunk_text}
|
||||
Output: JSON-Liste ["typ:ziel"].
|
||||
|
||||
Filtere relevante Kanten.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {chunk_text}
|
||||
KANDIDATEN: {edge_list}
|
||||
OUTPUT: STRIKT JSON-Liste von Strings ["typ:ziel"].
|
||||
# ---------------------------------------------------------
|
||||
# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST)
|
||||
# ---------------------------------------------------------
|
||||
|
|
@ -230,12 +216,12 @@ edge_extraction:
|
|||
|
||||
DEIN OUTPUT (JSON):
|
||||
gemini: |
|
||||
Führe eine semantische Analyse der Notiz '{note_id}' durch.
|
||||
Finde explizite und implizite Relationen.
|
||||
Antworte STRIKT als JSON: [[{{"to": "Ziel", "kind": "typ", "reason": "begründung"}}]]
|
||||
Keine Erklärungen, nur JSON.
|
||||
Text: {text}
|
||||
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {text}
|
||||
OUTPUT: STRIKT JSON-Liste von Objekten: [{"to": "Ziel", "kind": "typ"}]. Keine Erklärungen!
|
||||
openrouter: |
|
||||
Analysiere den Text für den Graphen. Identifiziere semantische Verbindungen.
|
||||
Output STRIKT als JSON-Liste: [[{{"to": "X", "kind": "Y"}}]].
|
||||
Text: {text}
|
||||
Wissensgraph-Extraktion für '{note_id}'.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {text}
|
||||
OUTPUT: STRIKT JSON-Liste von Objekten: [{"to": "Ziel", "kind": "typ"}]. Keine Dictionaries mit Schlüsseln wie 'edges'!
|
||||
Loading…
Reference in New Issue
Block a user