From 86464cec113ad9bf2b7172353a2ee300b2f9fd53 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 13:48:13 +0100 Subject: [PATCH 01/11] erster Entwurf WP07 --- app/routers/chat.py | 188 ++++++++++++++++++++++++--------- config/decision_engine.yaml | 60 ++++++++++- config/prompts.yaml | 45 +++++++- tests/test_interview_intent.py | 48 +++++++++ 4 files changed, 289 insertions(+), 52 deletions(-) create mode 100644 tests/test_interview_intent.py diff --git a/app/routers/chat.py b/app/routers/chat.py index 5ab2d3a..45cb679 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -1,10 +1,11 @@ """ -app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router + WP-04c Feedback) -Version: 2.3.2 (Merged Stability Patch) +app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router + WP-07 Interview Mode) +Version: 2.4.0 (Interview Support) Features: - Hybrid Intent Router (Keyword + LLM) - Strategic Retrieval (Late Binding via Config) +- Interview Loop (Schema-driven Data Collection) - Context Enrichment (Payload/Source Fallback) - Data Flywheel (Feedback Logging Integration) """ @@ -21,7 +22,6 @@ from app.config import get_settings from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit from app.services.llm_service import LLMService from app.core.retriever import Retriever -# [MERGE] Integration Feedback Service (WP-04c) from app.services.feedback_service import log_search router = APIRouter() @@ -62,6 +62,47 @@ def get_decision_strategy(intent: str) -> Dict[str, Any]: strategies = config.get("strategies", {}) return strategies.get(intent, strategies.get("FACT", {})) +# --- Helper: Target Type Detection (WP-07) --- + +def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str: + """ + Versucht zu erraten, welchen Notiz-Typ der User erstellen will. + Nutzt Keywords und Mappings. + """ + message_lower = message.lower() + + # 1. Direkter Match mit Schema-Keys (z.B. "projekt", "entscheidung") + # Ignoriere 'default' hier + for type_key in configured_schemas.keys(): + if type_key == "default": + continue + if type_key in message_lower: + return type_key + + # 2. Synonym-Mapping (Deutsch -> Schema Key) + # Dies verbessert die UX, falls User deutsche Begriffe nutzen + synonyms = { + "projekt": "project", + "vorhaben": "project", + "entscheidung": "decision", + "beschluss": "decision", + "ziel": "goal", + "erfahrung": "experience", + "lektion": "experience", + "wert": "value", + "prinzip": "principle", + "grundsatz": "principle", + "notiz": "default", + "idee": "default" + } + + for term, schema_key in synonyms.items(): + if term in message_lower: + # Prüfen, ob der gemappte Key auch konfiguriert ist + if schema_key in configured_schemas: + return schema_key + + return "default" # --- Dependencies --- @@ -167,67 +208,118 @@ async def chat_endpoint( # Strategy Load strategy = get_decision_strategy(intent) - inject_types = strategy.get("inject_types", []) prompt_key = strategy.get("prompt_template", "rag_template") - prepend_instr = strategy.get("prepend_instruction", "") - - # 2. Primary Retrieval - query_req = QueryRequest( - query=request.message, - mode="hybrid", - top_k=request.top_k, - explain=request.explain - ) - retrieve_result = await retriever.search(query_req) - hits = retrieve_result.results - # 3. Strategic Retrieval (WP-06 Kernfeature) - if inject_types: - logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...") - strategy_req = QueryRequest( - query=request.message, - mode="hybrid", - top_k=3, - filters={"type": inject_types}, - explain=False - ) - strategy_result = await retriever.search(strategy_req) + # --- SPLIT LOGIC: INTERVIEW vs. RAG --- + + sources_hits = [] + final_prompt = "" + + if intent == "INTERVIEW": + # --- WP-07: INTERVIEW MODE --- + # Kein Retrieval. Wir nutzen den Dialog-Kontext. + + # 1. Schema Loading (Late Binding) + schemas = strategy.get("schemas", {}) + target_type = _detect_target_type(request.message, schemas) + active_schema = schemas.get(target_type, schemas.get("default")) + + logger.info(f"[{query_id}] Starting Interview for Type: {target_type}") + + # Robustes Schema-Parsing (Dict vs List) + if isinstance(active_schema, dict): + fields_list = active_schema.get("fields", []) + hint_str = active_schema.get("hint", "") + else: + fields_list = active_schema # Fallback falls nur Liste definiert + hint_str = "" + + fields_str = "\n- " + "\n- ".join(fields_list) + + # 2. Context Logic + # Hinweis: In einer Stateless-API ist {context_str} idealerweise die History. + # Da ChatRequest (noch) kein History-Feld hat, nutzen wir einen Placeholder + # oder verlassen uns darauf, dass der Client die History im Prompt mitschickt + # (Streamlit Pattern: Appends history to prompt). + # Wir labeln es hier explizit. + context_str = "Bisheriger Verlauf (falls vorhanden): Siehe oben/unten." + + # 3. Prompt Assembly + template = llm.prompts.get(prompt_key, "") + final_prompt = template.replace("{context_str}", context_str) \ + .replace("{query}", request.message) \ + .replace("{target_type}", target_type) \ + .replace("{schema_fields}", fields_str) \ + .replace("{schema_hint}", hint_str) + + # Keine Hits im Interview + sources_hits = [] - existing_ids = {h.node_id for h in hits} - for strat_hit in strategy_result.results: - if strat_hit.node_id not in existing_ids: - hits.append(strat_hit) - - # 4. Context Building - if not hits: - context_str = "Keine relevanten Notizen gefunden." else: - context_str = _build_enriched_context(hits) + # --- WP-06: STANDARD RAG MODE --- + inject_types = strategy.get("inject_types", []) + prepend_instr = strategy.get("prepend_instruction", "") - # 5. Generation - template = llm.prompts.get(prompt_key, "{context_str}\n\n{query}") - system_prompt = llm.prompts.get("system_prompt", "") + # 2. Primary Retrieval + query_req = QueryRequest( + query=request.message, + mode="hybrid", + top_k=request.top_k, + explain=request.explain + ) + retrieve_result = await retriever.search(query_req) + hits = retrieve_result.results + + # 3. Strategic Retrieval (WP-06 Kernfeature) + if inject_types: + logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...") + strategy_req = QueryRequest( + query=request.message, + mode="hybrid", + top_k=3, + filters={"type": inject_types}, + explain=False + ) + strategy_result = await retriever.search(strategy_req) + + existing_ids = {h.node_id for h in hits} + for strat_hit in strategy_result.results: + if strat_hit.node_id not in existing_ids: + hits.append(strat_hit) + + # 4. Context Building + if not hits: + context_str = "Keine relevanten Notizen gefunden." + else: + context_str = _build_enriched_context(hits) + + # 5. Generation Setup + template = llm.prompts.get(prompt_key, "{context_str}\n\n{query}") + + if prepend_instr: + context_str = f"{prepend_instr}\n\n{context_str}" + + final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message) + sources_hits = hits - if prepend_instr: - context_str = f"{prepend_instr}\n\n{context_str}" - - final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message) + # --- COMMON GENERATION --- + + system_prompt = llm.prompts.get("system_prompt", "") logger.info(f"[{query_id}] Sending to LLM (Intent: {intent}, Template: {prompt_key})...") - # System-Prompt separat übergeben (WP-06a Fix) + # System-Prompt separat übergeben answer_text = await llm.generate_raw_response(prompt=final_prompt, system=system_prompt) duration_ms = int((time.time() - start_time) * 1000) - # 6. Logging (Fire & Forget) - [MERGE POINT] - # Wir loggen alles für das Data Flywheel (WP-08 Self-Tuning) + # 6. Logging (Fire & Forget) try: log_search( query_id=query_id, query_text=request.message, - results=hits, - mode="chat_rag", + results=sources_hits, + mode="interview" if intent == "INTERVIEW" else "chat_rag", metadata={ "intent": intent, "intent_source": intent_source, @@ -242,7 +334,7 @@ async def chat_endpoint( return ChatResponse( query_id=query_id, answer=answer_text, - sources=hits, + sources=sources_hits, latency_ms=duration_ms, intent=intent, intent_source=intent_source diff --git a/config/decision_engine.yaml b/config/decision_engine.yaml index f0f9e2d..406ec25 100644 --- a/config/decision_engine.yaml +++ b/config/decision_engine.yaml @@ -1,17 +1,19 @@ # config/decision_engine.yaml -# Steuerung der Decision Engine (WP-06) +# Steuerung der Decision Engine (WP-06 + WP-07) # Hybrid-Modus: Keywords (Fast) + LLM Router (Smart Fallback) -version: 1.2 +version: 1.3 settings: llm_fallback_enabled: true # Few-Shot Prompting für bessere SLM-Performance + # Erweitert um INTERVIEW Beispiele llm_router_prompt: | Du bist ein Klassifikator. Analysiere die Nachricht und wähle die passende Strategie. Antworte NUR mit dem Namen der Strategie. STRATEGIEN: + - INTERVIEW: User will Wissen strukturieren, Notizen anlegen, Projekte starten ("Neu", "Festhalten"). - DECISION: Rat, Strategie, Vor/Nachteile, "Soll ich". - EMPATHY: Gefühle, Frust, Freude, Probleme, "Alles ist sinnlos", "Ich bin traurig". - CODING: Code, Syntax, Programmierung, Python. @@ -20,6 +22,8 @@ settings: BEISPIELE: User: "Wie funktioniert Qdrant?" -> FACT User: "Soll ich Qdrant nutzen?" -> DECISION + User: "Ich möchte ein neues Projekt anlegen" -> INTERVIEW + User: "Lass uns eine Entscheidung festhalten" -> INTERVIEW User: "Schreibe ein Python Script" -> CODING User: "Alles ist grau und sinnlos" -> EMPATHY User: "Mir geht es heute gut" -> EMPATHY @@ -86,4 +90,54 @@ strategies: - "yaml" inject_types: ["snippet", "reference", "source"] prompt_template: "technical_template" - prepend_instruction: null \ No newline at end of file + prepend_instruction: null + + # 5. Interview / Datenerfassung (WP-07) + INTERVIEW: + description: "Der User möchte strukturiertes Wissen erfassen (Projekt, Notiz, Idee)." + trigger_keywords: + - "neue notiz" + - "neues projekt" + - "neue entscheidung" + - "neues ziel" + - "festhalten" + - "entwurf erstellen" + - "interview" + - "dokumentieren" + - "erfassen" + - "idee speichern" + inject_types: [] # Keine RAG-Suche, reiner Kontext-Dialog + prompt_template: "interview_template" + prepend_instruction: null + + # LATE BINDING SCHEMAS: + # Definition der Pflichtfelder pro Typ (korrespondiert mit types.yaml) + # Wenn ein Typ hier fehlt, wird 'default' genutzt. + schemas: + default: + fields: ["Titel", "Thema/Inhalt", "Tags"] + hint: "Halte es einfach und übersichtlich." + + project: + fields: ["Titel", "Zielsetzung (Goal)", "Status (draft/active)", "Wichtige Stakeholder", "Nächste Schritte"] + hint: "Achte darauf, Abhängigkeiten zu anderen Projekten mit [[rel:depends_on]] zu erfragen." + + decision: + fields: ["Titel", "Kontext (Warum entscheiden wir?)", "Getroffene Entscheidung", "Betrachtete Alternativen", "Status (proposed/final)"] + hint: "Wichtig: Frage explizit nach den Gründen gegen die Alternativen." + + goal: + fields: ["Titel", "Zeitrahmen (Deadline)", "Messkriterien (KPIs)", "Verbundene Werte"] + hint: "Ziele sollten SMART formuliert sein." + + experience: + fields: ["Titel", "Situation (Kontext)", "Erkenntnis (Learning)", "Emotionale Keywords (für Empathie-Suche)"] + hint: "Fokussiere dich auf die persönliche Lektion." + + value: + fields: ["Titel (Name des Werts)", "Definition (Was bedeutet das für uns?)", "Anti-Beispiel (Was ist es nicht?)"] + hint: "Werte dienen als Entscheidungsgrundlage." + + principle: + fields: ["Titel", "Handlungsanweisung", "Begründung"] + hint: "Prinzipien sind härter als Werte." \ No newline at end of file diff --git a/config/prompts.yaml b/config/prompts.yaml index b36b12c..c913f1f 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -93,4 +93,47 @@ technical_template: | FORMAT: - Kurze Erklärung des Ansatzes. - Markdown Code-Block (Copy-Paste fertig). - - Wichtige Edge-Cases. \ No newline at end of file + - Wichtige Edge-Cases. + +# --------------------------------------------------------- +# 5. INTERVIEW: Der Analyst (Intent: INTERVIEW) +# --------------------------------------------------------- +interview_template: | + CHAT_HISTORIE (BISHERIGER KONTEXT): + ========================================= + {context_str} + ========================================= + + AKTUELLE USER-EINGABE: + {query} + + DEINE ROLLE: + Du bist 'Mindnet Analyst'. Dein Ziel ist es, einen strukturierten Entwurf für eine Notiz vom Typ '{target_type}' zu erstellen. + + PFLICHTFELDER (SCHEMA): + {schema_fields} + + HINWEIS ZUM TYP: + {schema_hint} + + ANWEISUNG: + 1. Analysiere den bisherigen Verlauf und die Eingabe. Welche der Pflichtfelder sind bereits bekannt? + 2. STATUS CHECK: + - Fehlen Pflichtfelder? -> Stelle GENAU EINE gezielte Frage, um das nächste fehlende Feld zu klären. Warte auf die Antwort. + - Sind alle Felder grob geklärt? -> Generiere den finalen Entwurf. + + OUTPUT FORMAT (Nur wenn alle Infos da sind): + Erstelle einen Markdown-Codeblock. Nutze Frontmatter. + Verlinke erkannte Entitäten aggressiv mit [[Wikilinks]] oder [[rel:relation Ziel]]. + + Beispiel Output: + "Danke, ich habe alle Infos. Hier ist dein Entwurf:" + + ```markdown + --- + type: {target_type} + status: draft + tags: [...] + --- + # Titel + ... \ No newline at end of file diff --git a/tests/test_interview_intent.py b/tests/test_interview_intent.py new file mode 100644 index 0000000..f61b611 --- /dev/null +++ b/tests/test_interview_intent.py @@ -0,0 +1,48 @@ +import requests +import json + +# URL anpassen, falls du auf Port 8001 (Prod) oder 8002 (Dev) bist +API_URL = "http://localhost:8002/chat/" + +def test_intent(message, expected_intent, expected_type_hint): + payload = { + "message": message, + "top_k": 0, # Für Interview irrelevant + "explain": False + } + + try: + response = requests.post(API_URL, json=payload) + response.raise_for_status() + data = response.json() + + intent = data.get("intent") + answer = data.get("answer") + + print(f"--- TEST: '{message}' ---") + print(f"Erkannter Intent: {intent}") + print(f"Antwort-Snippet: {answer[:100]}...") + + if intent == expected_intent: + print("✅ Intent SUCCESS") + else: + print(f"❌ Intent FAILED (Erwartet: {expected_intent})") + + if expected_type_hint.lower() in answer.lower(): + print(f"✅ Context Check SUCCESS (Typ '{expected_type_hint}' erkannt)") + else: + print(f"⚠️ Context Check WARNING (Typ '{expected_type_hint}' nicht explizit im Start-Prompt gefunden)") + print("\n") + + except Exception as e: + print(f"❌ Error: {e}") + +if __name__ == "__main__": + # Test 1: Projekt-Start + test_intent("Ich möchte ein neues Projekt anlegen", "INTERVIEW", "project") + + # Test 2: Entscheidungs-Doku + test_intent("Lass uns eine Entscheidung festhalten", "INTERVIEW", "decision") + + # Test 3: Standard Chat (Gegenprobe) + test_intent("Was ist ein Vektor?", "FACT", "") \ No newline at end of file From ff23ce6f00cab5402e4308d5d1ad9d7477bbc373 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 14:03:07 +0100 Subject: [PATCH 02/11] testscript --- tests/test_interview_intent.py | 54 +++++++++++++++------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/tests/test_interview_intent.py b/tests/test_interview_intent.py index f61b611..b847984 100644 --- a/tests/test_interview_intent.py +++ b/tests/test_interview_intent.py @@ -1,48 +1,40 @@ import requests -import json +import sys -# URL anpassen, falls du auf Port 8001 (Prod) oder 8002 (Dev) bist API_URL = "http://localhost:8002/chat/" -def test_intent(message, expected_intent, expected_type_hint): +def test_intent(message): + print(f"📡 Sende: '{message}' ...") payload = { "message": message, - "top_k": 0, # Für Interview irrelevant + "top_k": 0, "explain": False } try: - response = requests.post(API_URL, json=payload) - response.raise_for_status() - data = response.json() + # Timeout nach 30 Sekunden erzwingen + response = requests.post(API_URL, json=payload, timeout=30) + if response.status_code != 200: + print(f"❌ Server Error {response.status_code}: {response.text}") + return + + data = response.json() intent = data.get("intent") + source = data.get("intent_source") answer = data.get("answer") - print(f"--- TEST: '{message}' ---") - print(f"Erkannter Intent: {intent}") - print(f"Antwort-Snippet: {answer[:100]}...") - - if intent == expected_intent: - print("✅ Intent SUCCESS") - else: - print(f"❌ Intent FAILED (Erwartet: {expected_intent})") - - if expected_type_hint.lower() in answer.lower(): - print(f"✅ Context Check SUCCESS (Typ '{expected_type_hint}' erkannt)") - else: - print(f"⚠️ Context Check WARNING (Typ '{expected_type_hint}' nicht explizit im Start-Prompt gefunden)") - print("\n") - + print(f"✅ Ergebnis erhalten:") + print(f" Intent: {intent} (Quelle: {source})") + print(f" Antwort: {answer[:100]}...") # Nur die ersten 100 Zeichen + print("-" * 40) + + except requests.exceptions.Timeout: + print("❌ TIMEOUT: Das Backend antwortet nicht innerhalb von 30 Sekunden.") + print(" -> Prüfe das Terminal, wo uvicorn läuft. Gibt es dort Fehlermeldungen?") except Exception as e: - print(f"❌ Error: {e}") + print(f"❌ Fehler: {e}") if __name__ == "__main__": - # Test 1: Projekt-Start - test_intent("Ich möchte ein neues Projekt anlegen", "INTERVIEW", "project") - - # Test 2: Entscheidungs-Doku - test_intent("Lass uns eine Entscheidung festhalten", "INTERVIEW", "decision") - - # Test 3: Standard Chat (Gegenprobe) - test_intent("Was ist ein Vektor?", "FACT", "") \ No newline at end of file + # Testfall: Muss INTERVIEW auslösen + test_intent("Ich möchte ein neues Projekt anlegen") \ No newline at end of file From fd39211801cd95c0830501befcd68a15263bda68 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 14:12:53 +0100 Subject: [PATCH 03/11] neuer prompt --- config/prompts.yaml | 54 ++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/config/prompts.yaml b/config/prompts.yaml index c913f1f..96dc310 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -95,45 +95,33 @@ technical_template: | - Markdown Code-Block (Copy-Paste fertig). - Wichtige Edge-Cases. +# config/prompts.yaml + # --------------------------------------------------------- -# 5. INTERVIEW: Der Analyst (Intent: INTERVIEW) +# 5. INTERVIEW: Der "Checklist Operator" (Strict Mode) # --------------------------------------------------------- interview_template: | - CHAT_HISTORIE (BISHERIGER KONTEXT): - ========================================= - {context_str} - ========================================= - - AKTUELLE USER-EINGABE: - {query} - - DEINE ROLLE: - Du bist 'Mindnet Analyst'. Dein Ziel ist es, einen strukturierten Entwurf für eine Notiz vom Typ '{target_type}' zu erstellen. + TASK: Daten-Erfassung für Typ '{target_type}'. - PFLICHTFELDER (SCHEMA): + SCHEMA (Checkliste): {schema_fields} - HINWEIS ZUM TYP: - {schema_hint} - + CHAT VERLAUF: + {context_str} + + USER INPUT: + "{query}" + + LOGIK: + 1. Lies den VERLAUF und den INPUT. + 2. Prüfe die SCHEMA-Liste von oben nach unten. + 3. Welches ist das ERSTE Feld, das noch keine klare Antwort hat? + ANWEISUNG: - 1. Analysiere den bisherigen Verlauf und die Eingabe. Welche der Pflichtfelder sind bereits bekannt? - 2. STATUS CHECK: - - Fehlen Pflichtfelder? -> Stelle GENAU EINE gezielte Frage, um das nächste fehlende Feld zu klären. Warte auf die Antwort. - - Sind alle Felder grob geklärt? -> Generiere den finalen Entwurf. - - OUTPUT FORMAT (Nur wenn alle Infos da sind): - Erstelle einen Markdown-Codeblock. Nutze Frontmatter. - Verlinke erkannte Entitäten aggressiv mit [[Wikilinks]] oder [[rel:relation Ziel]]. + - Wenn ein Feld fehlt: Stelle NUR eine präzise Frage nach diesem Feld. (Kein Hallo, keine Erklärung). + - Wenn ALLE Felder da sind: Gib den Codeblock aus. - Beispiel Output: - "Danke, ich habe alle Infos. Hier ist dein Entwurf:" + HINWEIS: + {schema_hint} - ```markdown - --- - type: {target_type} - status: draft - tags: [...] - --- - # Titel - ... \ No newline at end of file + ANTWORT (Entweder Frage oder Markdown-Code): \ No newline at end of file From 954e21ca81b223ddac5c98b6393978c0471ec8c5 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 16:16:51 +0100 Subject: [PATCH 04/11] anpassung one shot --- config/prompts.yaml | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/config/prompts.yaml b/config/prompts.yaml index 96dc310..2570c54 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -97,31 +97,38 @@ technical_template: | # config/prompts.yaml +# config/prompts.yaml + # --------------------------------------------------------- -# 5. INTERVIEW: Der "Checklist Operator" (Strict Mode) +# 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode) # --------------------------------------------------------- interview_template: | - TASK: Daten-Erfassung für Typ '{target_type}'. + TASK: + Erstelle einen Markdown-Entwurf für eine Notiz vom Typ '{target_type}'. - SCHEMA (Checkliste): + SCHEMA (Pflichtfelder): {schema_fields} - CHAT VERLAUF: - {context_str} - USER INPUT: "{query}" - LOGIK: - 1. Lies den VERLAUF und den INPUT. - 2. Prüfe die SCHEMA-Liste von oben nach unten. - 3. Welches ist das ERSTE Feld, das noch keine klare Antwort hat? - ANWEISUNG: - - Wenn ein Feld fehlt: Stelle NUR eine präzise Frage nach diesem Feld. (Kein Hallo, keine Erklärung). - - Wenn ALLE Felder da sind: Gib den Codeblock aus. + 1. Extrahiere ALLE Informationen aus dem User Input, die du finden kannst. + 2. Mappe sie auf die Schema-Felder. + 3. Für Felder, die im Input FEHLEN, schreibe den Platzhalter "[TODO: Bitte ergänzen]". + 4. Generiere SOFORT den Markdown-Codeblock. Stelle KEINE Rückfragen. - HINWEIS: + HINWEIS ZUM TYP: {schema_hint} - ANTWORT (Entweder Frage oder Markdown-Code): \ No newline at end of file + OUTPUT FORMAT: + ```markdown + --- + type: {target_type} + status: draft + ... + --- + # Titel (oder [TODO]) + + ## Feldname + Inhalt... \ No newline at end of file From 1063c94f5dbf38bd6b18dc5e0ba5a030b1558406 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 16:41:21 +0100 Subject: [PATCH 05/11] =?UTF-8?q?UI=20Anpasung=20f=C3=BCr=20WP07?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/frontend/ui.py | 92 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 418e82c..f22ea22 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -3,6 +3,7 @@ import requests import uuid import os import json +import re from pathlib import Path from dotenv import load_dotenv @@ -18,7 +19,7 @@ timeout_setting = os.getenv("MINDNET_API_TIMEOUT") or os.getenv("MINDNET_LLM_TIM API_TIMEOUT = float(timeout_setting) if timeout_setting else 300.0 # --- PAGE SETUP --- -st.set_page_config(page_title="mindnet v2.3.1", page_icon="🧠", layout="wide") +st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -40,6 +41,9 @@ st.markdown(""" /* Expander Cleaner */ .streamlit-expanderHeader { font-size: 0.9rem; font-weight: 600; color: #444; } + + /* Editor Label */ + .editor-label { font-weight: bold; margin-bottom: 5px; display: block; color: #333; } """, unsafe_allow_html=True) @@ -50,6 +54,14 @@ if "draft_note" not in st.session_state: st.session_state.draft_note = {"title": # --- HELPER FUNCTIONS --- +def extract_markdown_content(text): + """Extrahiert den Inhalt aus einem Markdown-Codeblock.""" + pattern = r"```markdown\s*(.*?)\s*```" + match = re.search(pattern, text, re.DOTALL) + if match: + return match.group(1).strip() + return text # Fallback: Ganzen Text zurückgeben, wenn kein Block gefunden + def load_history_from_logs(limit=10): """Liest die letzten N Queries aus dem Logfile.""" queries = [] @@ -93,9 +105,9 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("v2.3.1 | WP-10 UI") + st.caption("v2.3.2 | WP-10 UI") - mode = st.radio("Modus", ["💬 Chat", "📝 Neuer Eintrag (WP-07)"], index=0) + mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Eintrag"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -119,17 +131,59 @@ def render_chat_interface(top_k, explain): for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg["role"] == "assistant": - # Intent Badge MIT SOURCE (Fix für Debugging) + # 1. INTENT BADGE (mit Source) if "intent" in msg: - icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚"}.get(msg["intent"], "🧠") + intent = msg["intent"] + icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚", "INTERVIEW": "📝"}.get(intent, "🧠") source_info = msg.get("intent_source", "Unknown") - # Hier wird die Quelle wieder angezeigt: - st.markdown(f'
{icon} Intent: {msg["intent"]} via {source_info}
', unsafe_allow_html=True) + st.markdown(f'
{icon} Intent: {intent} via {source_info}
', unsafe_allow_html=True) - st.markdown(msg["content"]) + # 2. CONTENT RENDERING (Weiche für INTERVIEW vs. NORMAL) + if msg.get("intent") == "INTERVIEW": + # --- INTERVIEW EDITOR MODUS --- + + # Markdown extrahieren (nur beim ersten Mal, dann aus State) + raw_content = msg["content"] + draft_content = extract_markdown_content(raw_content) + + # Eindeutiger Key für diesen Editor (basierend auf query_id) + editor_key = f"editor_{msg.get('query_id', uuid.uuid4())}" + + # Init State falls noch nicht vorhanden + if editor_key not in st.session_state: + st.session_state[editor_key] = draft_content + + st.markdown('Entwurf bearbeiten (Draft):', unsafe_allow_html=True) + + # Der Editor + edited_text = st.text_area( + label="Editor", + value=st.session_state[editor_key], + height=350, + key=editor_key, + label_visibility="collapsed" + ) + + # Action Buttons + c1, c2 = st.columns([1, 1]) + with c1: + st.download_button( + label="💾 Als .md herunterladen", + data=edited_text, + file_name=f"draft_{msg.get('query_id', 'unknown')[:8]}.md", + mime="text/markdown" + ) + with c2: + # Copy Mockup (Browser-Security verhindert oft direkten Zugriff) + if st.button("📋 In Zwischenablage kopieren", key=f"copy_{editor_key}"): + st.toast("Tipp: Klicke in das Textfeld, drücke Strg+A dann Strg+C.") + + else: + # --- STANDARD CHAT MODUS --- + st.markdown(msg["content"]) - # Sources - if "sources" in msg: + # 3. SOURCES (Nur anzeigen, wenn vorhanden) + if "sources" in msg and msg["sources"]: for hit in msg["sources"]: score = hit.get('total_score', 0) icon = "🟢" if score > 0.8 else "🟡" if score > 0.5 else "⚪" @@ -138,24 +192,26 @@ def render_chat_interface(top_k, explain): if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") - # Granular Feedback (Faces) + # Granular Feedback def _cb(qid=msg["query_id"], nid=hit['node_id']): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1, "Faces UI") st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb) - # Global Feedback (Stars) - qid = msg["query_id"] - st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) + # 4. GLOBAL FEEDBACK + if "query_id" in msg: + qid = msg["query_id"] + st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) else: + # User Message st.markdown(msg["content"]) # Input Logic last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" - if prompt := st.chat_input("Frage Mindnet..."): + if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() @@ -172,7 +228,7 @@ def render_chat_interface(top_k, explain): "role": "assistant", "content": resp.get("answer"), "intent": resp.get("intent", "FACT"), - "intent_source": resp.get("intent_source", "Unknown"), # Wichtig für Anzeige + "intent_source": resp.get("intent_source", "Unknown"), "sources": resp.get("sources", []), "query_id": resp.get("query_id") } @@ -180,8 +236,8 @@ def render_chat_interface(top_k, explain): st.rerun() def render_creation_interface(): - st.header("📝 Neuer Wissens-Eintrag (WP-07/11)") - st.info("Hier kannst du strukturierte Notizen erstellen, die direkt in den Obsidian Vault gespeichert werden.") + st.header("📝 Manueller Eintrag (Legacy)") + st.info("Nutze lieber den Chat ('Neues Projekt anlegen') für den Interview-Modus.") with st.form("new_entry"): col1, col2 = st.columns([3, 1]) From a520b62d085535a15aacca96a5af5efcecea24fd Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 16:55:56 +0100 Subject: [PATCH 06/11] =?UTF-8?q?spezial=20UI=20f=C3=BCr=20das=20Bearbeite?= =?UTF-8?q?n=20von=20MD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/frontend/ui.py | 193 ++++++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 65 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index f22ea22..882a674 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -4,6 +4,7 @@ import uuid import os import json import re +import yaml from pathlib import Path from dotenv import load_dotenv @@ -25,7 +26,7 @@ st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") st.markdown(""" """, unsafe_allow_html=True) # --- SESSION STATE --- if "messages" not in st.session_state: st.session_state.messages = [] if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4()) -if "draft_note" not in st.session_state: st.session_state.draft_note = {"title": "", "content": "", "type": "concept"} # --- HELPER FUNCTIONS --- -def extract_markdown_content(text): - """Extrahiert den Inhalt aus einem Markdown-Codeblock.""" - pattern = r"```markdown\s*(.*?)\s*```" - match = re.search(pattern, text, re.DOTALL) - if match: - return match.group(1).strip() - return text # Fallback: Ganzen Text zurückgeben, wenn kein Block gefunden +def parse_markdown_draft(full_text): + """ + Zerlegt einen Markdown-Text in Frontmatter (Dict) und Body (String). + """ + # 1. Versuch: Markdown Codeblock entfernen + pattern_block = r"```markdown\s*(.*?)\s*```" + match_block = re.search(pattern_block, full_text, re.DOTALL) + clean_text = match_block.group(1).strip() if match_block else full_text + + # 2. Frontmatter parsen (YAML zwischen ---) + pattern_fm = r"^---\s+(.*?)\s+---\s+(.*)$" + match_fm = re.search(pattern_fm, clean_text, re.DOTALL) + + if match_fm: + yaml_str = match_fm.group(1) + body = match_fm.group(2) + try: + meta = yaml.safe_load(yaml_str) or {} + except: + meta = {} + return meta, body + else: + return {}, clean_text + +def build_markdown_draft(meta, body): + """Baut das Dokument aus Metadaten und Text wieder zusammen.""" + yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False).strip() + return f"---\n{yaml_str}\n---\n\n{body}" def load_history_from_logs(limit=10): - """Liest die letzten N Queries aus dem Logfile.""" queries = [] if HISTORY_FILE.exists(): try: @@ -126,63 +152,103 @@ def render_sidebar(): return mode, top_k, explain +def render_draft_editor(msg): + """ + Spezial-Widget für den INTERVIEW Intent. + Zeigt Tabs für Edit/Preview und separiert Metadaten. + """ + qid = msg.get('query_id', str(uuid.uuid4())) + key_base = f"draft_{qid}" + + # 1. Parsing (Initialisierung) + if f"{key_base}_initialized" not in st.session_state: + meta, body = parse_markdown_draft(msg["content"]) + st.session_state[f"{key_base}_type"] = meta.get("type", "concept") + st.session_state[f"{key_base}_tags"] = meta.get("tags", []) + st.session_state[f"{key_base}_body"] = body.strip() + st.session_state[f"{key_base}_initialized"] = True + + # 2. Container Style + st.markdown(f'
', unsafe_allow_html=True) + st.markdown("### 📝 Entwurf bearbeiten") + + # 3. Metadaten-Controls (Oberer Bereich) + c1, c2 = st.columns([1, 2]) + with c1: + # Typ-Auswahl (Liste synchron mit types.yaml) + valid_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal"] + # Fallback falls LLM etwas Exotisches erfunden hat + current_type = st.session_state[f"{key_base}_type"] + if current_type not in valid_types: valid_types.append(current_type) + + new_type = st.selectbox("Typ", valid_types, key=f"{key_base}_sel_type", index=valid_types.index(current_type)) + + with c2: + # Tag-Editor (als Chips) + current_tags = st.session_state[f"{key_base}_tags"] + if isinstance(current_tags, str): current_tags = [current_tags] # Safety catch + new_tags = st.text_input("Tags (Kommagetrennt)", value=", ".join(current_tags), key=f"{key_base}_inp_tags") + + # 4. Tabs: Edit vs Preview + tab_edit, tab_view = st.tabs(["✏️ Bearbeiten", "👁️ Vorschau"]) + + with tab_edit: + new_body = st.text_area( + "Inhalt", + value=st.session_state[f"{key_base}_body"], + height=400, + key=f"{key_base}_txt_body", + label_visibility="collapsed" + ) + + # 5. Rekonstruktion des Dokuments + final_tags = [t.strip() for t in new_tags.split(",") if t.strip()] + final_meta = {"type": new_type, "tags": final_tags, "status": "draft"} # Basic meta + final_doc = build_markdown_draft(final_meta, new_body) + + with tab_view: + st.markdown('
', unsafe_allow_html=True) + st.markdown(final_doc) # Rendered Markdown + st.markdown('
', unsafe_allow_html=True) + + st.markdown("---") + + # 6. Actions + b1, b2, b3 = st.columns([1, 1, 2]) + with b1: + st.download_button( + "💾 Download .md", + data=final_doc, + file_name=f"draft_{new_type}_{qid[:6]}.md", + mime="text/markdown" + ) + with b2: + if st.button("📋 Copy Code", key=f"{key_base}_btn_copy"): + st.code(final_doc, language="markdown") + st.toast("Code unten ausgeklappt zum Kopieren!") + + st.markdown("
", unsafe_allow_html=True) + + def render_chat_interface(top_k, explain): # Render History for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg["role"] == "assistant": - # 1. INTENT BADGE (mit Source) + # Intent Badge if "intent" in msg: intent = msg["intent"] icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚", "INTERVIEW": "📝"}.get(intent, "🧠") source_info = msg.get("intent_source", "Unknown") st.markdown(f'
{icon} Intent: {intent} via {source_info}
', unsafe_allow_html=True) - # 2. CONTENT RENDERING (Weiche für INTERVIEW vs. NORMAL) + # WEICHE: Editor vs. Text if msg.get("intent") == "INTERVIEW": - # --- INTERVIEW EDITOR MODUS --- - - # Markdown extrahieren (nur beim ersten Mal, dann aus State) - raw_content = msg["content"] - draft_content = extract_markdown_content(raw_content) - - # Eindeutiger Key für diesen Editor (basierend auf query_id) - editor_key = f"editor_{msg.get('query_id', uuid.uuid4())}" - - # Init State falls noch nicht vorhanden - if editor_key not in st.session_state: - st.session_state[editor_key] = draft_content - - st.markdown('Entwurf bearbeiten (Draft):', unsafe_allow_html=True) - - # Der Editor - edited_text = st.text_area( - label="Editor", - value=st.session_state[editor_key], - height=350, - key=editor_key, - label_visibility="collapsed" - ) - - # Action Buttons - c1, c2 = st.columns([1, 1]) - with c1: - st.download_button( - label="💾 Als .md herunterladen", - data=edited_text, - file_name=f"draft_{msg.get('query_id', 'unknown')[:8]}.md", - mime="text/markdown" - ) - with c2: - # Copy Mockup (Browser-Security verhindert oft direkten Zugriff) - if st.button("📋 In Zwischenablage kopieren", key=f"copy_{editor_key}"): - st.toast("Tipp: Klicke in das Textfeld, drücke Strg+A dann Strg+C.") - + render_draft_editor(msg) else: - # --- STANDARD CHAT MODUS --- st.markdown(msg["content"]) - # 3. SOURCES (Nur anzeigen, wenn vorhanden) + # Sources & Feedback (nur bei non-interview meist sinnvoll, oder immer?) if "sources" in msg and msg["sources"]: for hit in msg["sources"]: score = hit.get('total_score', 0) @@ -192,26 +258,23 @@ def render_chat_interface(top_k, explain): if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") - # Granular Feedback def _cb(qid=msg["query_id"], nid=hit['node_id']): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1, "Faces UI") st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb) - # 4. GLOBAL FEEDBACK if "query_id" in msg: qid = msg["query_id"] st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) else: - # User Message st.markdown(msg["content"]) # Input Logic last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" - if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): + if prompt := st.chat_input("Frage Mindnet..."): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() From 1fefc538ac2a38a41a7bd13b540ead4a869b69b9 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 17:14:51 +0100 Subject: [PATCH 07/11] UI Work --- app/frontend/ui.py | 218 ++++++++++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 101 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 882a674..b3f52a8 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -5,6 +5,7 @@ import os import json import re import yaml +from datetime import datetime from pathlib import Path from dotenv import load_dotenv @@ -25,10 +26,8 @@ st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" """, unsafe_allow_html=True) @@ -62,15 +64,19 @@ if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4( def parse_markdown_draft(full_text): """ - Zerlegt einen Markdown-Text in Frontmatter (Dict) und Body (String). + Zerlegt die LLM-Antwort in Frontmatter (Dict) und Body (String). + Robust gegen Einleitungstexte vor dem Codeblock. """ - # 1. Versuch: Markdown Codeblock entfernen + # 1. Extrahiere Codeblock pattern_block = r"```markdown\s*(.*?)\s*```" match_block = re.search(pattern_block, full_text, re.DOTALL) + + # Fallback: Wenn kein Codeblock, nimm ganzen Text clean_text = match_block.group(1).strip() if match_block else full_text - # 2. Frontmatter parsen (YAML zwischen ---) - pattern_fm = r"^---\s+(.*?)\s+---\s+(.*)$" + # 2. Trenne Frontmatter (YAML) vom Body + # Sucht nach --- am Anfang, gefolgt von YAML, gefolgt von --- + pattern_fm = r"^---\s+(.*?)\s+---\s*(.*)$" match_fm = re.search(pattern_fm, clean_text, re.DOTALL) if match_fm: @@ -78,15 +84,30 @@ def parse_markdown_draft(full_text): body = match_fm.group(2) try: meta = yaml.safe_load(yaml_str) or {} - except: - meta = {} + except Exception: + meta = {} # Fallback bei kaputtem YAML return meta, body else: + # Kein Frontmatter gefunden -> Alles ist Body return {}, clean_text -def build_markdown_draft(meta, body): - """Baut das Dokument aus Metadaten und Text wieder zusammen.""" - yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False).strip() +def generate_filename(meta): + """Generiert einen Dateinamen basierend auf Typ und Datum.""" + date_str = datetime.now().strftime("%Y%m%d") + n_type = meta.get("type", "note") + # Versuche Titel aus Body zu raten wäre zu komplex, wir nehmen Generic + return f"{date_str}-{n_type}-draft.md" + +def build_markdown_doc(meta, body): + """Baut das finale Dokument zusammen.""" + # ID Generierung beim 'Speichern' (hier simuliert für Download) + if "id" not in meta or meta["id"] == "generated_on_save": + meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{meta.get('type', 'note')}-{uuid.uuid4().hex[:6]}" + + # Updated timestamp + meta["updated"] = datetime.now().strftime("%Y-%m-%d") + + yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip() return f"---\n{yaml_str}\n---\n\n{body}" def load_history_from_logs(limit=10): @@ -103,8 +124,7 @@ def load_history_from_logs(limit=10): queries.append(q) if len(queries) >= limit: break except: continue - except Exception as e: - st.sidebar.warning(f"Log-Fehler: {e}") + except: pass return queries def send_chat_message(message: str, top_k: int, explain: bool): @@ -123,7 +143,7 @@ def submit_feedback(query_id, node_id, score, comment=None): try: requests.post(FEEDBACK_ENDPOINT, json={"query_id": query_id, "node_id": node_id, "score": score, "comment": comment}, timeout=2) target = "Antwort" if node_id == "generated_answer" else "Quelle" - st.toast(f"Feedback für {target} gespeichert! (Score: {score})") + st.toast(f"Feedback ({score}) gesendet!") except: pass # --- UI COMPONENTS --- @@ -133,7 +153,7 @@ def render_sidebar(): st.title("🧠 mindnet") st.caption("v2.3.2 | WP-10 UI") - mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Eintrag"], index=0) + mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -142,69 +162,75 @@ def render_sidebar(): st.divider() st.subheader("🕒 Verlauf") - history = load_history_from_logs(8) - if not history: - st.caption("Noch keine Einträge.") - for q in history: - if st.button(f"🔎 {q[:30]}...", key=f"hist_{q}", help=q, use_container_width=True): + for q in load_history_from_logs(8): + if st.button(f"🔎 {q[:25]}...", key=f"hist_{q}", use_container_width=True): st.session_state.messages.append({"role": "user", "content": q}) st.rerun() - return mode, top_k, explain def render_draft_editor(msg): """ - Spezial-Widget für den INTERVIEW Intent. - Zeigt Tabs für Edit/Preview und separiert Metadaten. + Rendert den Split-Screen Editor für INTERVIEW Drafts. """ qid = msg.get('query_id', str(uuid.uuid4())) key_base = f"draft_{qid}" - # 1. Parsing (Initialisierung) - if f"{key_base}_initialized" not in st.session_state: + # 1. State Initialisierung (Nur einmal pro Nachricht) + if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) - st.session_state[f"{key_base}_type"] = meta.get("type", "concept") - st.session_state[f"{key_base}_tags"] = meta.get("tags", []) + st.session_state[f"{key_base}_type"] = meta.get("type", "default") + + # Tags Behandlung (Liste oder String) + tags_raw = meta.get("tags", []) + if isinstance(tags_raw, list): + st.session_state[f"{key_base}_tags"] = ", ".join(tags_raw) + else: + st.session_state[f"{key_base}_tags"] = str(tags_raw) + st.session_state[f"{key_base}_body"] = body.strip() - st.session_state[f"{key_base}_initialized"] = True + st.session_state[f"{key_base}_init"] = True - # 2. Container Style + # 2. Editor Container st.markdown(f'
', unsafe_allow_html=True) st.markdown("### 📝 Entwurf bearbeiten") - - # 3. Metadaten-Controls (Oberer Bereich) + st.caption("Das System hat diesen Entwurf vorbereitet. Ergänze die fehlenden [TODO] Bereiche.") + + # 3. Metadaten (Grid Layout) c1, c2 = st.columns([1, 2]) with c1: - # Typ-Auswahl (Liste synchron mit types.yaml) - valid_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal"] - # Fallback falls LLM etwas Exotisches erfunden hat - current_type = st.session_state[f"{key_base}_type"] - if current_type not in valid_types: valid_types.append(current_type) + # Typen müssen mit types.yaml synchron sein + known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle"] + curr_type = st.session_state[f"{key_base}_type"] + if curr_type not in known_types: known_types.append(curr_type) - new_type = st.selectbox("Typ", valid_types, key=f"{key_base}_sel_type", index=valid_types.index(current_type)) + new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type") with c2: - # Tag-Editor (als Chips) - current_tags = st.session_state[f"{key_base}_tags"] - if isinstance(current_tags, str): current_tags = [current_tags] # Safety catch - new_tags = st.text_input("Tags (Kommagetrennt)", value=", ".join(current_tags), key=f"{key_base}_inp_tags") + new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state[f"{key_base}_tags"], key=f"{key_base}_inp_tags") - # 4. Tabs: Edit vs Preview - tab_edit, tab_view = st.tabs(["✏️ Bearbeiten", "👁️ Vorschau"]) + # 4. Inhalt (Tabs) + tab_edit, tab_view = st.tabs(["✏️ Editor", "👁️ Vorschau"]) with tab_edit: + # Hier landet der Body mit den ## Headings und [TODO]s new_body = st.text_area( "Inhalt", value=st.session_state[f"{key_base}_body"], - height=400, + height=500, key=f"{key_base}_txt_body", - label_visibility="collapsed" + label_visibility="collapsed", + help="Hier kannst du Markdown schreiben." ) - # 5. Rekonstruktion des Dokuments - final_tags = [t.strip() for t in new_tags.split(",") if t.strip()] - final_meta = {"type": new_type, "tags": final_tags, "status": "draft"} # Basic meta - final_doc = build_markdown_draft(final_meta, new_body) + # Live-Zusammenbau für Vorschau & Download + final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()] + final_meta = { + "id": "generated_on_save", # Placeholder, wird beim Download ersetzt + "type": new_type, + "status": "draft", + "tags": final_tags_list + } + final_doc = build_markdown_doc(final_meta, new_body) with tab_view: st.markdown('
', unsafe_allow_html=True) @@ -213,25 +239,25 @@ def render_draft_editor(msg): st.markdown("---") - # 6. Actions - b1, b2, b3 = st.columns([1, 1, 2]) + # 5. Actions (Writeback Simulation) + b1, b2 = st.columns([1, 1]) with b1: + # Download Button (Client-Side) st.download_button( - "💾 Download .md", - data=final_doc, - file_name=f"draft_{new_type}_{qid[:6]}.md", + label="💾 Als .md Datei speichern", + data=final_doc, + file_name=generate_filename(final_meta), mime="text/markdown" ) with b2: - if st.button("📋 Copy Code", key=f"{key_base}_btn_copy"): + # Copy Button Logic + if st.button("📋 Code anzeigen (Copy)", key=f"{key_base}_btn_copy"): st.code(final_doc, language="markdown") - st.toast("Code unten ausgeklappt zum Kopieren!") st.markdown("
", unsafe_allow_html=True) def render_chat_interface(top_k, explain): - # Render History for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg["role"] == "assistant": @@ -239,16 +265,16 @@ def render_chat_interface(top_k, explain): if "intent" in msg: intent = msg["intent"] icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚", "INTERVIEW": "📝"}.get(intent, "🧠") - source_info = msg.get("intent_source", "Unknown") - st.markdown(f'
{icon} Intent: {intent} via {source_info}
', unsafe_allow_html=True) + src = msg.get("intent_source", "?") + st.markdown(f'
{icon} Intent: {intent} ({src})
', unsafe_allow_html=True) - # WEICHE: Editor vs. Text + # INTERVIEW Logic vs NORMAL Chat if msg.get("intent") == "INTERVIEW": render_draft_editor(msg) else: st.markdown(msg["content"]) - # Sources & Feedback (nur bei non-interview meist sinnvoll, oder immer?) + # Sources & Feedback (nur bei RAG sinnvoll) if "sources" in msg and msg["sources"]: for hit in msg["sources"]: score = hit.get('total_score', 0) @@ -260,67 +286,57 @@ def render_chat_interface(top_k, explain): def _cb(qid=msg["query_id"], nid=hit['node_id']): val = st.session_state.get(f"fb_src_{qid}_{nid}") - if val is not None: submit_feedback(qid, nid, val+1, "Faces UI") - + if val is not None: submit_feedback(qid, nid, val+1) st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb) if "query_id" in msg: qid = msg["query_id"] st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) - else: st.markdown(msg["content"]) - # Input Logic - last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" - - if prompt := st.chat_input("Frage Mindnet..."): + # Input Loop + if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() + last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" if last_msg_is_user: - last_prompt = st.session_state.messages[-1]["content"] with st.chat_message("assistant"): with st.spinner("Thinking..."): - resp = send_chat_message(last_prompt, top_k, explain) + resp = send_chat_message(st.session_state.messages[-1]["content"], top_k, explain) if "error" in resp: st.error(resp["error"]) else: - msg_data = { + st.session_state.messages.append({ "role": "assistant", "content": resp.get("answer"), "intent": resp.get("intent", "FACT"), "intent_source": resp.get("intent_source", "Unknown"), "sources": resp.get("sources", []), "query_id": resp.get("query_id") - } - st.session_state.messages.append(msg_data) + }) st.rerun() -def render_creation_interface(): - st.header("📝 Manueller Eintrag (Legacy)") - st.info("Nutze lieber den Chat ('Neues Projekt anlegen') für den Interview-Modus.") +def render_manual_editor(): + st.header("📝 Manueller Editor") + st.info("Hier kannst du eine Notiz komplett von Null erstellen.") - with st.form("new_entry"): - col1, col2 = st.columns([3, 1]) - title = col1.text_input("Titel der Notiz", placeholder="z.B. Projekt Gamma Meeting") - n_type = col2.selectbox("Typ", ["concept", "meeting", "person", "project", "decision"]) - - content = st.text_area("Inhalt (Markdown)", height=300, placeholder="# Protokoll\n\n- Punkt 1...") - - st.markdown("**Automatische Vernetzung:**") - st.caption("Verwende `[[Link]]` für Referenzen und `[[rel:depends_on X]]` für logische Kanten.") - - submitted = st.form_submit_button("💾 Speichern & Indizieren") - if submitted: - st.success(f"Mockup: Notiz '{title}' ({n_type}) wäre jetzt gespeichert worden!") - st.balloons() + # Wiederverwendung der Editor-Logik wäre ideal, hier vereinfacht: + c1, c2 = st.columns([1, 2]) + n_type = c1.selectbox("Typ", ["concept", "project", "decision", "experience", "value", "goal"]) + tags = c2.text_input("Tags") + body = st.text_area("Inhalt", height=400, placeholder="# Titel\n\nText...") + + if st.button("Generieren & Download"): + meta = {"type": n_type, "status": "draft", "tags": [t.strip() for t in tags.split(",")]} + doc = build_markdown_doc(meta, body) + st.code(doc, language="markdown") -# --- MAIN LOOP --- +# --- MAIN --- mode, top_k, explain = render_sidebar() - if mode == "💬 Chat": render_chat_interface(top_k, explain) else: - render_creation_interface() \ No newline at end of file + render_manual_editor() \ No newline at end of file From f50ae4c934dfb6de0bdd5012994b06b8e0c00b67 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 17:35:21 +0100 Subject: [PATCH 08/11] debug mode gui --- app/frontend/ui.py | 138 +++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index b3f52a8..0fa5783 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -21,7 +21,7 @@ timeout_setting = os.getenv("MINDNET_API_TIMEOUT") or os.getenv("MINDNET_LLM_TIM API_TIMEOUT = float(timeout_setting) if timeout_setting else 300.0 # --- PAGE SETUP --- -st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") +st.set_page_config(page_title="mindnet v2.3.2 (Debug Mode)", page_icon="🐞", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -35,7 +35,6 @@ st.markdown(""" border: 1px solid #d2e3fc; display: inline-block; margin-bottom: 0.5rem; } - /* Editor Box Styling */ .draft-box { border: 1px solid #d0d7de; border-radius: 6px; @@ -45,7 +44,6 @@ st.markdown(""" margin-bottom: 10px; } - /* Preview Styling mimics GitHub Markdown */ .preview-box { border: 1px solid #e0e0e0; border-radius: 6px; @@ -60,54 +58,57 @@ st.markdown(""" if "messages" not in st.session_state: st.session_state.messages = [] if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4()) -# --- HELPER FUNCTIONS --- +# --- HELPER FUNCTIONS (ROBUST PARSING) --- def parse_markdown_draft(full_text): """ - Zerlegt die LLM-Antwort in Frontmatter (Dict) und Body (String). - Robust gegen Einleitungstexte vor dem Codeblock. + Versucht extrem tolerant, Frontmatter und Body zu trennen. """ - # 1. Extrahiere Codeblock - pattern_block = r"```markdown\s*(.*?)\s*```" - match_block = re.search(pattern_block, full_text, re.DOTALL) + clean_text = full_text - # Fallback: Wenn kein Codeblock, nimm ganzen Text - clean_text = match_block.group(1).strip() if match_block else full_text + # 1. Versuch: Markdown Fences entfernen (egal ob ```markdown, ```md oder nur ```) + # re.IGNORECASE und re.DOTALL sind wichtig! + pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" + match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) + + if match_block: + clean_text = match_block.group(1).strip() + # Debugging Info im UI anzeigen ist schwer hier, wir verlassen uns auf den Return - # 2. Trenne Frontmatter (YAML) vom Body - # Sucht nach --- am Anfang, gefolgt von YAML, gefolgt von --- - pattern_fm = r"^---\s+(.*?)\s+---\s*(.*)$" + # 2. Versuch: YAML Frontmatter finden + # Suche nach --- am Anfang (oder nach Whitespace), gefolgt von Inhalt, gefolgt von --- + pattern_fm = r"^\s*---\s+(.*?)\s+---\s*(.*)$" match_fm = re.search(pattern_fm, clean_text, re.DOTALL) + meta = {} + body = clean_text + if match_fm: yaml_str = match_fm.group(1) body = match_fm.group(2) try: - meta = yaml.safe_load(yaml_str) or {} - except Exception: - meta = {} # Fallback bei kaputtem YAML - return meta, body - else: - # Kein Frontmatter gefunden -> Alles ist Body - return {}, clean_text - -def generate_filename(meta): - """Generiert einen Dateinamen basierend auf Typ und Datum.""" - date_str = datetime.now().strftime("%Y%m%d") - n_type = meta.get("type", "note") - # Versuche Titel aus Body zu raten wäre zu komplex, wir nehmen Generic - return f"{date_str}-{n_type}-draft.md" + # YAML laden, aber Fehler abfangen + parsed = yaml.safe_load(yaml_str) + if isinstance(parsed, dict): + meta = parsed + except Exception as e: + print(f"YAML Parsing Error: {e}") # Geht in Server Log + # Wir behalten body, meta bleibt leer + + return meta, body def build_markdown_doc(meta, body): """Baut das finale Dokument zusammen.""" - # ID Generierung beim 'Speichern' (hier simuliert für Download) if "id" not in meta or meta["id"] == "generated_on_save": meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{meta.get('type', 'note')}-{uuid.uuid4().hex[:6]}" - # Updated timestamp meta["updated"] = datetime.now().strftime("%Y-%m-%d") - yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip() + try: + yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip() + except: + yaml_str = "error: generating_yaml" + return f"---\n{yaml_str}\n---\n\n{body}" def load_history_from_logs(limit=10): @@ -151,7 +152,7 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("v2.3.2 | WP-10 UI") + st.caption("DEBUG MODE | WP-10 UI") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) @@ -176,8 +177,11 @@ def render_draft_editor(msg): key_base = f"draft_{qid}" # 1. State Initialisierung (Nur einmal pro Nachricht) + # Wir parsen IMMER neu, wenn der Key noch nicht im State ist if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) + + # Fallback Defaults st.session_state[f"{key_base}_type"] = meta.get("type", "default") # Tags Behandlung (Liste oder String) @@ -193,39 +197,36 @@ def render_draft_editor(msg): # 2. Editor Container st.markdown(f'
', unsafe_allow_html=True) st.markdown("### 📝 Entwurf bearbeiten") - st.caption("Das System hat diesen Entwurf vorbereitet. Ergänze die fehlenden [TODO] Bereiche.") - + # 3. Metadaten (Grid Layout) c1, c2 = st.columns([1, 2]) with c1: - # Typen müssen mit types.yaml synchron sein - known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle"] - curr_type = st.session_state[f"{key_base}_type"] + known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"] + curr_type = st.session_state.get(f"{key_base}_type", "default") if curr_type not in known_types: known_types.append(curr_type) + # Selectbox mit State-Sync new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type") with c2: - new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state[f"{key_base}_tags"], key=f"{key_base}_inp_tags") + new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags") # 4. Inhalt (Tabs) tab_edit, tab_view = st.tabs(["✏️ Editor", "👁️ Vorschau"]) with tab_edit: - # Hier landet der Body mit den ## Headings und [TODO]s new_body = st.text_area( - "Inhalt", - value=st.session_state[f"{key_base}_body"], + "Inhalt (Markdown Body)", + value=st.session_state.get(f"{key_base}_body", ""), height=500, key=f"{key_base}_txt_body", - label_visibility="collapsed", - help="Hier kannst du Markdown schreiben." + label_visibility="collapsed" ) - # Live-Zusammenbau für Vorschau & Download + # Live-Zusammenbau final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()] final_meta = { - "id": "generated_on_save", # Placeholder, wird beim Download ersetzt + "id": "generated_on_save", "type": new_type, "status": "draft", "tags": final_tags_list @@ -234,47 +235,48 @@ def render_draft_editor(msg): with tab_view: st.markdown('
', unsafe_allow_html=True) - st.markdown(final_doc) # Rendered Markdown + st.markdown(final_doc) st.markdown('
', unsafe_allow_html=True) st.markdown("---") - # 5. Actions (Writeback Simulation) + # 5. Actions b1, b2 = st.columns([1, 1]) with b1: - # Download Button (Client-Side) st.download_button( - label="💾 Als .md Datei speichern", + label="💾 Download .md", data=final_doc, file_name=generate_filename(final_meta), mime="text/markdown" ) with b2: - # Copy Button Logic - if st.button("📋 Code anzeigen (Copy)", key=f"{key_base}_btn_copy"): + if st.button("📋 Code Copy", key=f"{key_base}_btn_copy"): st.code(final_doc, language="markdown") st.markdown("
", unsafe_allow_html=True) def render_chat_interface(top_k, explain): - for msg in st.session_state.messages: + for idx, msg in enumerate(st.session_state.messages): with st.chat_message(msg["role"]): if msg["role"] == "assistant": # Intent Badge - if "intent" in msg: - intent = msg["intent"] - icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚", "INTERVIEW": "📝"}.get(intent, "🧠") - src = msg.get("intent_source", "?") - st.markdown(f'
{icon} Intent: {intent} ({src})
', unsafe_allow_html=True) + intent = msg.get("intent", "UNKNOWN") + src = msg.get("intent_source", "?") - # INTERVIEW Logic vs NORMAL Chat - if msg.get("intent") == "INTERVIEW": + # Icon Mapping + icon_map = {"EMPATHY":"❤️", "DECISION":"⚖️", "CODING":"💻", "FACT":"📚", "INTERVIEW":"📝"} + icon = icon_map.get(intent, "🧠") + + st.markdown(f'
{icon} Intent: {intent} ({src})
', unsafe_allow_html=True) + + # --- LOGIC SWITCH --- + if intent == "INTERVIEW": render_draft_editor(msg) else: st.markdown(msg["content"]) - # Sources & Feedback (nur bei RAG sinnvoll) + # --- SOURCES --- if "sources" in msg and msg["sources"]: for hit in msg["sources"]: score = hit.get('total_score', 0) @@ -284,18 +286,20 @@ def render_chat_interface(top_k, explain): if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") - def _cb(qid=msg["query_id"], nid=hit['node_id']): + def _cb(qid=msg.get("query_id"), nid=hit.get('node_id')): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1) - st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb) + + st.feedback("faces", key=f"fb_src_{msg.get('query_id')}_{hit.get('node_id')}", on_change=_cb) - if "query_id" in msg: - qid = msg["query_id"] - st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) + # --- DEBUGGING (NEU) --- + with st.expander("🐞 Debug Raw Payload"): + st.json(msg) # Zeigt exakt, was das Backend geschickt hat + else: st.markdown(msg["content"]) - # Input Loop + # Input if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() @@ -322,8 +326,6 @@ def render_chat_interface(top_k, explain): def render_manual_editor(): st.header("📝 Manueller Editor") st.info("Hier kannst du eine Notiz komplett von Null erstellen.") - - # Wiederverwendung der Editor-Logik wäre ideal, hier vereinfacht: c1, c2 = st.columns([1, 2]) n_type = c1.selectbox("Typ", ["concept", "project", "decision", "experience", "value", "goal"]) tags = c2.text_input("Tags") From 5c0a36c9ea55d8ca0fbd311de7c9f7cff29c481e Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 17:43:03 +0100 Subject: [PATCH 09/11] Debug 2 --- app/frontend/ui.py | 132 ++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 86 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 0fa5783..4b9d778 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -21,7 +21,7 @@ timeout_setting = os.getenv("MINDNET_API_TIMEOUT") or os.getenv("MINDNET_LLM_TIM API_TIMEOUT = float(timeout_setting) if timeout_setting else 300.0 # --- PAGE SETUP --- -st.set_page_config(page_title="mindnet v2.3.2 (Debug Mode)", page_icon="🐞", layout="wide") +st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -51,6 +51,12 @@ st.markdown(""" background-color: white; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; } + + .debug-info { + font-size: 0.7rem; + color: #888; + margin-bottom: 5px; + } """, unsafe_allow_html=True) @@ -58,7 +64,7 @@ st.markdown(""" if "messages" not in st.session_state: st.session_state.messages = [] if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4()) -# --- HELPER FUNCTIONS (ROBUST PARSING) --- +# --- HELPER FUNCTIONS --- def parse_markdown_draft(full_text): """ @@ -66,42 +72,36 @@ def parse_markdown_draft(full_text): """ clean_text = full_text - # 1. Versuch: Markdown Fences entfernen (egal ob ```markdown, ```md oder nur ```) - # re.IGNORECASE und re.DOTALL sind wichtig! + # 1. Versuch: Codeblock isolieren pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) - if match_block: clean_text = match_block.group(1).strip() - # Debugging Info im UI anzeigen ist schwer hier, wir verlassen uns auf den Return - # 2. Versuch: YAML Frontmatter finden - # Suche nach --- am Anfang (oder nach Whitespace), gefolgt von Inhalt, gefolgt von --- - pattern_fm = r"^\s*---\s+(.*?)\s+---\s*(.*)$" + # 2. Versuch: Frontmatter finden (--- YAML ---) + # Verbesserter Regex: Sucht nach dem ersten Vorkommen von --- am Zeilenanfang + pattern_fm = r"(-{3,})\s*(.*?)\s*\1\s*(.*)" match_fm = re.search(pattern_fm, clean_text, re.DOTALL) meta = {} body = clean_text if match_fm: - yaml_str = match_fm.group(1) - body = match_fm.group(2) + yaml_str = match_fm.group(2) + body_content = match_fm.group(3) try: - # YAML laden, aber Fehler abfangen parsed = yaml.safe_load(yaml_str) if isinstance(parsed, dict): meta = parsed - except Exception as e: - print(f"YAML Parsing Error: {e}") # Geht in Server Log - # Wir behalten body, meta bleibt leer + body = body_content.strip() + except Exception: + pass # YAML kaputt -> alles als Body behandeln return meta, body def build_markdown_doc(meta, body): - """Baut das finale Dokument zusammen.""" if "id" not in meta or meta["id"] == "generated_on_save": meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{meta.get('type', 'note')}-{uuid.uuid4().hex[:6]}" - meta["updated"] = datetime.now().strftime("%Y-%m-%d") try: @@ -143,7 +143,6 @@ def send_chat_message(message: str, top_k: int, explain: bool): def submit_feedback(query_id, node_id, score, comment=None): try: requests.post(FEEDBACK_ENDPOINT, json={"query_id": query_id, "node_id": node_id, "score": score, "comment": comment}, timeout=2) - target = "Antwort" if node_id == "generated_answer" else "Quelle" st.toast(f"Feedback ({score}) gesendet!") except: pass @@ -152,15 +151,12 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("DEBUG MODE | WP-10 UI") - + st.caption("DEBUG MODE ACTIVATED") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) - st.divider() st.subheader("⚙️ Settings") top_k = st.slider("Quellen (Top-K)", 1, 10, 5) explain = st.toggle("Explanation Layer", True) - st.divider() st.subheader("🕒 Verlauf") for q in load_history_from_logs(8): @@ -170,67 +166,46 @@ def render_sidebar(): return mode, top_k, explain def render_draft_editor(msg): - """ - Rendert den Split-Screen Editor für INTERVIEW Drafts. - """ qid = msg.get('query_id', str(uuid.uuid4())) key_base = f"draft_{qid}" - # 1. State Initialisierung (Nur einmal pro Nachricht) - # Wir parsen IMMER neu, wenn der Key noch nicht im State ist if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) - - # Fallback Defaults st.session_state[f"{key_base}_type"] = meta.get("type", "default") - - # Tags Behandlung (Liste oder String) tags_raw = meta.get("tags", []) - if isinstance(tags_raw, list): - st.session_state[f"{key_base}_tags"] = ", ".join(tags_raw) - else: - st.session_state[f"{key_base}_tags"] = str(tags_raw) - + st.session_state[f"{key_base}_tags"] = ", ".join(tags_raw) if isinstance(tags_raw, list) else str(tags_raw) st.session_state[f"{key_base}_body"] = body.strip() st.session_state[f"{key_base}_init"] = True - # 2. Editor Container st.markdown(f'
', unsafe_allow_html=True) st.markdown("### 📝 Entwurf bearbeiten") - # 3. Metadaten (Grid Layout) + # Metadata Controls c1, c2 = st.columns([1, 2]) with c1: known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"] curr_type = st.session_state.get(f"{key_base}_type", "default") if curr_type not in known_types: known_types.append(curr_type) - - # Selectbox mit State-Sync new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type") with c2: - new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags") + new_tags = st.text_input("Tags", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags") - # 4. Inhalt (Tabs) + # Editor / Preview Tabs tab_edit, tab_view = st.tabs(["✏️ Editor", "👁️ Vorschau"]) with tab_edit: new_body = st.text_area( - "Inhalt (Markdown Body)", + "Inhalt", value=st.session_state.get(f"{key_base}_body", ""), height=500, key=f"{key_base}_txt_body", label_visibility="collapsed" ) - # Live-Zusammenbau + # Live Reassembly final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()] - final_meta = { - "id": "generated_on_save", - "type": new_type, - "status": "draft", - "tags": final_tags_list - } + final_meta = {"id": "generated_on_save", "type": new_type, "status": "draft", "tags": final_tags_list} final_doc = build_markdown_doc(final_meta, new_body) with tab_view: @@ -240,48 +215,41 @@ def render_draft_editor(msg): st.markdown("---") - # 5. Actions + # Actions b1, b2 = st.columns([1, 1]) with b1: - st.download_button( - label="💾 Download .md", - data=final_doc, - file_name=generate_filename(final_meta), - mime="text/markdown" - ) + st.download_button("💾 Download .md", data=final_doc, file_name=f"draft_{new_type}.md", mime="text/markdown") with b2: if st.button("📋 Code Copy", key=f"{key_base}_btn_copy"): st.code(final_doc, language="markdown") - + st.markdown("
", unsafe_allow_html=True) - def render_chat_interface(top_k, explain): for idx, msg in enumerate(st.session_state.messages): with st.chat_message(msg["role"]): if msg["role"] == "assistant": - # Intent Badge + # Meta Info intent = msg.get("intent", "UNKNOWN") src = msg.get("intent_source", "?") - - # Icon Mapping - icon_map = {"EMPATHY":"❤️", "DECISION":"⚖️", "CODING":"💻", "FACT":"📚", "INTERVIEW":"📝"} - icon = icon_map.get(intent, "🧠") - + icon = {"EMPATHY":"❤️", "DECISION":"⚖️", "CODING":"💻", "FACT":"📚", "INTERVIEW":"📝"}.get(intent, "🧠") st.markdown(f'
{icon} Intent: {intent} ({src})
', unsafe_allow_html=True) - # --- LOGIC SWITCH --- + # --- WICHTIG: DEBUGGING JETZT GANZ OBEN --- + with st.expander("🐞 Debug Raw Payload", expanded=False): + st.text("Hier siehst du, was das Backend wirklich geschickt hat:") + st.json(msg) + + # --- CONTENT LOGIC --- if intent == "INTERVIEW": render_draft_editor(msg) else: st.markdown(msg["content"]) - # --- SOURCES --- + # Sources & Feedback if "sources" in msg and msg["sources"]: for hit in msg["sources"]: - score = hit.get('total_score', 0) - icon = "🟢" if score > 0.8 else "🟡" if score > 0.5 else "⚪" - with st.expander(f"{icon} {hit.get('note_id', '?')} ({score:.2f})"): + with st.expander(f"📄 {hit.get('note_id', '?')} ({hit.get('total_score', 0):.2f})"): st.markdown(f"_{hit.get('source', {}).get('text', '')[:300]}..._") if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") @@ -289,27 +257,23 @@ def render_chat_interface(top_k, explain): def _cb(qid=msg.get("query_id"), nid=hit.get('node_id')): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1) - st.feedback("faces", key=f"fb_src_{msg.get('query_id')}_{hit.get('node_id')}", on_change=_cb) - # --- DEBUGGING (NEU) --- - with st.expander("🐞 Debug Raw Payload"): - st.json(msg) # Zeigt exakt, was das Backend geschickt hat - + if "query_id" in msg: + qid = msg["query_id"] + st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1)) else: st.markdown(msg["content"]) - # Input - if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): + # Input Logic + if prompt := st.chat_input("Frage Mindnet..."): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() - last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" - if last_msg_is_user: + if len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user": with st.chat_message("assistant"): with st.spinner("Thinking..."): resp = send_chat_message(st.session_state.messages[-1]["content"], top_k, explain) - if "error" in resp: st.error(resp["error"]) else: @@ -325,18 +289,14 @@ def render_chat_interface(top_k, explain): def render_manual_editor(): st.header("📝 Manueller Editor") - st.info("Hier kannst du eine Notiz komplett von Null erstellen.") c1, c2 = st.columns([1, 2]) n_type = c1.selectbox("Typ", ["concept", "project", "decision", "experience", "value", "goal"]) tags = c2.text_input("Tags") body = st.text_area("Inhalt", height=400, placeholder="# Titel\n\nText...") - - if st.button("Generieren & Download"): + if st.button("Code anzeigen"): meta = {"type": n_type, "status": "draft", "tags": [t.strip() for t in tags.split(",")]} - doc = build_markdown_doc(meta, body) - st.code(doc, language="markdown") + st.code(build_markdown_doc(meta, body), language="markdown") -# --- MAIN --- mode, top_k, explain = render_sidebar() if mode == "💬 Chat": render_chat_interface(top_k, explain) From 67d865d3731984fa6db0e11ccb63cf8a28b3f2c3 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 18:13:35 +0100 Subject: [PATCH 10/11] ui und prompt --- app/frontend/ui.py | 141 +++++++++++++++++++++++++++++++++----------- config/prompts.yaml | 36 ++++++----- 2 files changed, 127 insertions(+), 50 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 4b9d778..d3e714b 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -66,46 +66,101 @@ if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4( # --- HELPER FUNCTIONS --- +def normalize_meta_and_body(meta, body): + """ + Sanitizer: Stellt sicher, dass nur erlaubte Felder im Frontmatter bleiben. + Alles andere wird in den Body verschoben (Repair-Strategie). + """ + ALLOWED_KEYS = {"title", "type", "status", "tags", "id", "created", "updated", "aliases", "lang"} + + clean_meta = {} + extra_content = [] + + # 1. Title/Titel Normalisierung + if "titel" in meta and "title" not in meta: + meta["title"] = meta.pop("titel") + + # 2. Tags Normalisierung (Synonyme) + tag_candidates = ["tags", "emotionale_keywords", "keywords", "schluesselwoerter"] + all_tags = [] + for key in tag_candidates: + if key in meta: + val = meta[key] + if isinstance(val, list): all_tags.extend(val) + elif isinstance(val, str): all_tags.extend([t.strip() for t in val.split(",")]) + + # 3. Filterung und Verschiebung + for key, val in meta.items(): + if key in ALLOWED_KEYS: + clean_meta[key] = val + elif key in tag_candidates: + pass # Schon oben behandelt + else: + # Unerlaubtes Feld (z.B. 'situation') -> Ab in den Body! + if val and isinstance(val, str): + header = key.replace("_", " ").title() + extra_content.append(f"## {header}\n{val}\n") + + if all_tags: + clean_meta["tags"] = list(set(all_tags)) + + # 4. Body Zusammenbau + if extra_content: + new_section = "\n".join(extra_content) + final_body = f"{new_section}\n{body}" + else: + final_body = body + + return clean_meta, final_body + def parse_markdown_draft(full_text): """ - Versucht extrem tolerant, Frontmatter und Body zu trennen. + Robustes Parsing + Sanitization. """ clean_text = full_text - # 1. Versuch: Codeblock isolieren + # Codeblock entfernen pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) if match_block: clean_text = match_block.group(1).strip() - # 2. Versuch: Frontmatter finden (--- YAML ---) - # Verbesserter Regex: Sucht nach dem ersten Vorkommen von --- am Zeilenanfang - pattern_fm = r"(-{3,})\s*(.*?)\s*\1\s*(.*)" - match_fm = re.search(pattern_fm, clean_text, re.DOTALL) + # Frontmatter splitten + parts = re.split(r"^---+\s*$", clean_text, maxsplit=2, flags=re.MULTILINE) meta = {} body = clean_text - if match_fm: - yaml_str = match_fm.group(2) - body_content = match_fm.group(3) + if len(parts) >= 3: + yaml_str = parts[1] + body_candidate = parts[2] try: parsed = yaml.safe_load(yaml_str) if isinstance(parsed, dict): meta = parsed - body = body_content.strip() + body = body_candidate.strip() except Exception: - pass # YAML kaputt -> alles als Body behandeln + pass - return meta, body + return normalize_meta_and_body(meta, body) def build_markdown_doc(meta, body): + """Baut das finale Dokument zusammen.""" if "id" not in meta or meta["id"] == "generated_on_save": - meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{meta.get('type', 'note')}-{uuid.uuid4().hex[:6]}" + safe_title = re.sub(r'[^a-zA-Z0-9]', '-', meta.get('title', 'note')).lower()[:30] + meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}-{uuid.uuid4().hex[:4]}" + meta["updated"] = datetime.now().strftime("%Y-%m-%d") + # Sortierung für UX + ordered_meta = {} + prio_keys = ["id", "type", "title", "status", "tags"] + for k in prio_keys: + if k in meta: ordered_meta[k] = meta.pop(k) + ordered_meta.update(meta) + try: - yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip() + yaml_str = yaml.dump(ordered_meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip() except: yaml_str = "error: generating_yaml" @@ -151,7 +206,7 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("DEBUG MODE ACTIVATED") + st.caption("v2.3.2 | WP-10 UI") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -169,43 +224,60 @@ def render_draft_editor(msg): qid = msg.get('query_id', str(uuid.uuid4())) key_base = f"draft_{qid}" + # 1. Init if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) + st.session_state[f"{key_base}_type"] = meta.get("type", "default") + st.session_state[f"{key_base}_title"] = meta.get("title", "") + tags_raw = meta.get("tags", []) st.session_state[f"{key_base}_tags"] = ", ".join(tags_raw) if isinstance(tags_raw, list) else str(tags_raw) + st.session_state[f"{key_base}_body"] = body.strip() + st.session_state[f"{key_base}_meta"] = meta st.session_state[f"{key_base}_init"] = True + # 2. UI st.markdown(f'
', unsafe_allow_html=True) st.markdown("### 📝 Entwurf bearbeiten") - # Metadata Controls - c1, c2 = st.columns([1, 2]) + # Metadata + c1, c2 = st.columns([2, 1]) with c1: + new_title = st.text_input("Titel", value=st.session_state.get(f"{key_base}_title", ""), key=f"{key_base}_inp_title") + with c2: known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"] curr_type = st.session_state.get(f"{key_base}_type", "default") if curr_type not in known_types: known_types.append(curr_type) new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type") - - with c2: - new_tags = st.text_input("Tags", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags") - # Editor / Preview Tabs - tab_edit, tab_view = st.tabs(["✏️ Editor", "👁️ Vorschau"]) + new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags") + + # Tabs + tab_edit, tab_view = st.tabs(["✏️ Inhalt", "👁️ Vorschau"]) with tab_edit: + st.caption("Bearbeite hier den Inhalt. Metadaten (oben) werden automatisch hinzugefügt.") new_body = st.text_area( - "Inhalt", + "Body", value=st.session_state.get(f"{key_base}_body", ""), height=500, key=f"{key_base}_txt_body", label_visibility="collapsed" ) - # Live Reassembly + # Reassembly final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()] - final_meta = {"id": "generated_on_save", "type": new_type, "status": "draft", "tags": final_tags_list} + final_meta = st.session_state.get(f"{key_base}_meta", {}).copy() + final_meta.update({ + "id": "generated_on_save", + "type": new_type, + "title": new_title, + "status": "draft", + "tags": final_tags_list + }) + final_doc = build_markdown_doc(final_meta, new_body) with tab_view: @@ -218,42 +290,42 @@ def render_draft_editor(msg): # Actions b1, b2 = st.columns([1, 1]) with b1: - st.download_button("💾 Download .md", data=final_doc, file_name=f"draft_{new_type}.md", mime="text/markdown") + fname = f"{datetime.now().strftime('%Y%m%d')}-{new_type}.md" + st.download_button("💾 Download .md", data=final_doc, file_name=fname, mime="text/markdown") with b2: - if st.button("📋 Code Copy", key=f"{key_base}_btn_copy"): + if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"): st.code(final_doc, language="markdown") - + st.markdown("
", unsafe_allow_html=True) + def render_chat_interface(top_k, explain): for idx, msg in enumerate(st.session_state.messages): with st.chat_message(msg["role"]): if msg["role"] == "assistant": - # Meta Info + # Meta intent = msg.get("intent", "UNKNOWN") src = msg.get("intent_source", "?") icon = {"EMPATHY":"❤️", "DECISION":"⚖️", "CODING":"💻", "FACT":"📚", "INTERVIEW":"📝"}.get(intent, "🧠") st.markdown(f'
{icon} Intent: {intent} ({src})
', unsafe_allow_html=True) - # --- WICHTIG: DEBUGGING JETZT GANZ OBEN --- + # Debugging (Always visible for safety) with st.expander("🐞 Debug Raw Payload", expanded=False): - st.text("Hier siehst du, was das Backend wirklich geschickt hat:") st.json(msg) - # --- CONTENT LOGIC --- + # Logic if intent == "INTERVIEW": render_draft_editor(msg) else: st.markdown(msg["content"]) - # Sources & Feedback + # Sources if "sources" in msg and msg["sources"]: for hit in msg["sources"]: with st.expander(f"📄 {hit.get('note_id', '?')} ({hit.get('total_score', 0):.2f})"): st.markdown(f"_{hit.get('source', {}).get('text', '')[:300]}..._") if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") - def _cb(qid=msg.get("query_id"), nid=hit.get('node_id')): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1) @@ -265,7 +337,6 @@ def render_chat_interface(top_k, explain): else: st.markdown(msg["content"]) - # Input Logic if prompt := st.chat_input("Frage Mindnet..."): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() diff --git a/config/prompts.yaml b/config/prompts.yaml index 2570c54..f192109 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -94,41 +94,47 @@ technical_template: | - Kurze Erklärung des Ansatzes. - Markdown Code-Block (Copy-Paste fertig). - Wichtige Edge-Cases. - -# config/prompts.yaml - -# config/prompts.yaml - # --------------------------------------------------------- # 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode) # --------------------------------------------------------- + interview_template: | TASK: Erstelle einen Markdown-Entwurf für eine Notiz vom Typ '{target_type}'. - SCHEMA (Pflichtfelder): + SCHEMA (Inhaltliche Pflichtfelder für den Body): {schema_fields} USER INPUT: "{query}" ANWEISUNG: - 1. Extrahiere ALLE Informationen aus dem User Input, die du finden kannst. - 2. Mappe sie auf die Schema-Felder. - 3. Für Felder, die im Input FEHLEN, schreibe den Platzhalter "[TODO: Bitte ergänzen]". - 4. Generiere SOFORT den Markdown-Codeblock. Stelle KEINE Rückfragen. + 1. Extrahiere Informationen aus dem Input. + 2. Generiere validen Markdown. + + OUTPUT REGELN (STRIKT BEACHTEN): + A. FRONTMATTER (YAML): + - Darf NUR folgende Felder enthalten: [type, status, title, tags]. + - Schreibe KEINE inhaltlichen Sätze (wie 'Situation', 'Ziel') in das YAML! + - Setze 'status: draft'. + + B. BODY (Markdown): + - Nutze für jedes Schema-Feld eine Markdown-Überschrift (## Feldname). + - Schreibe den Inhalt DARUNTER. + - Nutze "[TODO: Ergänzen]", wenn Infos fehlen. HINWEIS ZUM TYP: {schema_hint} - OUTPUT FORMAT: + OUTPUT FORMAT BEISPIEL: ```markdown --- type: {target_type} status: draft - ... + title: ... + tags: [...] --- - # Titel (oder [TODO]) + # Titel der Notiz - ## Feldname - Inhalt... \ No newline at end of file + ## Erstes Schema Feld + Der Inhalt hier... \ No newline at end of file From f2de556c16573b84777580478f2f8bbdf6227e19 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 18:55:38 +0100 Subject: [PATCH 11/11] dokumentation --- Programmmanagement/Programmplan_V2.2.md | 63 ++++++++++--------- docs/Knowledge_Design_Manual.md | 41 ++++++++----- docs/Overview.md | 58 +++++++++--------- docs/admin_guide.md | 12 ++-- docs/appendix.md | 37 ++++++------ docs/dev_workflow.md | 42 +++++++------ docs/developer_guide.md | 52 ++++++++-------- docs/mindnet_functional_architecture.md | 45 +++++++------- docs/mindnet_technical_architecture.md | 80 +++++++++++++++---------- docs/pipeline_playbook.md | 43 +++++++------ docs/user_guide.md | 33 ++++++---- 11 files changed, 286 insertions(+), 220 deletions(-) diff --git a/Programmmanagement/Programmplan_V2.2.md b/Programmmanagement/Programmplan_V2.2.md index 152f1be..91ee315 100644 --- a/Programmmanagement/Programmplan_V2.2.md +++ b/Programmmanagement/Programmplan_V2.2.md @@ -1,5 +1,5 @@ # mindnet v2.2 — Programmplan -**Version:** 2.3.2 (Inkl. WP-10a GUI Evolution) +**Version:** 2.4.0 (Inkl. WP-07 Interview & WP-10a Draft Editor) **Stand:** 2025-12-10 **Status:** Aktiv @@ -24,11 +24,11 @@ - [WP-05 – Persönlichkeitsmodell \& RAG-Chat (abgeschlossen)](#wp-05--persönlichkeitsmodell--rag-chat-abgeschlossen) - [WP-05b – Advanced Chat (Optional)](#wp-05b--advanced-chat-optional) - [WP-06 – Decision Engine \& Hybrid Router (abgeschlossen)](#wp-06--decision-engine--hybrid-router-abgeschlossen) - - [WP-07 – Interview-Assistent (geplant)](#wp-07--interview-assistent-geplant) + - [WP-07 – Interview-Assistent (abgeschlossen)](#wp-07--interview-assistent-abgeschlossen) - [WP-08 – Self-Tuning v1/v2 (geplant)](#wp-08--self-tuning-v1v2-geplant) - [WP-09 – Vault-Onboarding \& Migration (geplant)](#wp-09--vault-onboarding--migration-geplant) - [WP-10 – Chat-Interface \& Writeback (abgeschlossen)](#wp-10--chat-interface--writeback-abgeschlossen) - - [WP-10a – GUI Evolution: Interaktion \& Tools (geplant)](#wp-10a--gui-evolution-interaktion--tools-geplant) + - [WP-10a – GUI Evolution: Draft Editor (abgeschlossen)](#wp-10a--gui-evolution-draft-editor-abgeschlossen) - [WP-11 – Knowledge-Builder \& Vernetzungs-Assistent (geplant)](#wp-11--knowledge-builder--vernetzungs-assistent-geplant) - [WP-12 – Knowledge Rewriter (Soft Mode, geplant)](#wp-12--knowledge-rewriter-soft-mode-geplant) - [WP-13 – MCP-Integration \& Agenten-Layer (geplant)](#wp-13--mcp-integration--agenten-layer-geplant) @@ -39,13 +39,11 @@ - [10. Governance \& Versionierung](#10-governance--versionierung) - [11. Executive Summary](#11-executive-summary) - - --- ## 1. Programmauftrag -mindnet v2.2 entwickelt ein persönliches, wachsendes KI-Gedächtnis, das: +mindnet v2.4 entwickelt ein persönliches, wachsendes KI-Gedächtnis, das: - Wissen, Erfahrungen, Werte und Entscheidungen speichert, - diese Informationen semantisch verknüpft und rekonstruierbar macht, @@ -53,7 +51,7 @@ mindnet v2.2 entwickelt ein persönliches, wachsendes KI-Gedächtnis, das: - über mehrere Kanäle gefüttert wird: - Obsidian-Markdown (primäre Quelle), - Chat-basierter Agent (Decision Engine & RAG-Chat aktiv), - - später: Interview-Assistent (strukturierte Dialogerfassung), + - **Interview-Assistent (One-Shot Extraction aktiv)**, - automatisch neue Zusammenhänge erkennt und vernetzt (Edges, Typen, Hinweise), - sich durch Rückmeldungen (Feedback) selbst verbessert (Self-Tuning). @@ -88,7 +86,7 @@ Kernprinzipien der Vision: Das System arbeitet von Anfang an mit unvollständigen Daten, kann aber schrittweise dichter werden, ohne dass alte Notizen massenhaft manuell angepasst werden müssen. - **Flexibilität (Late Binding):** - Semantik wird überwiegend in Konfiguration (z. B. `types.yaml`, `prompts.yaml`, `decision_engine.yaml`, Policies) festgelegt. Die Persönlichkeit entsteht durch das Config-Design, nicht durch Hardcoding. + Semantik wird überwiegend in Konfiguration (z. B. `types.yaml`, `prompts.yaml`, `decision_engine.yaml`) festgelegt. Die Persönlichkeit entsteht durch das Config-Design, nicht durch Hardcoding. - **Autonomie & Self-Healing:** mindnet schlägt fehlende Typen, Relationen und Edges vor (z. B. aus Inline-Relationen, Edge-Defaults, Ähnlichkeitsbeziehungen) und baut damit einen „self-healing graph“ auf. @@ -117,6 +115,7 @@ Kernprinzipien der Vision: - **Decision Engine:** System erkennt Intent (Fakt vs. Entscheidung) und wägt Werte ab (WP-06 abgeschlossen). - **Multi-Persona:** System wechselt den Tonfall (Empathisch vs. Analytisch) situativ (WP-06 abgeschlossen). - **Chat Interface:** Web-basiertes Frontend (Streamlit) für einfache Interaktion und Feedback-Gabe (WP-10 abgeschlossen). +- **Interview-Assistent (WP-07):** One-Shot Extraction von Notizen ("Neues Projekt anlegen") ist live. - Technische Basis: FastAPI, Qdrant, Ollama (Local LLM), Streamlit. - Automatisierte Erkennung von Beziehungen: - Wikilinks, Inline-Relationen, Callout-Edges, Typ-Defaults. @@ -126,10 +125,9 @@ Kernprinzipien der Vision: ### 3.2 Mittelfristig (Nächste Schritte) -- **Interview-Assistent (WP-07):** Geführte Dialoge zur Erfassung neuer Notizen (Startbereit). -- mindnet erzeugt Vorschläge für neue Notes & Edges und bietet einen „Vernetzungs-Assistenten“ für manuell angelegte Notizen. +- **Self-Tuning (WP-08):** Optimierung der Gewichte in `retriever.yaml` basierend auf dem gesammelten Feedback. +- **Knowledge-Builder (WP-11):** Assistent zur Analyse und Vernetzung manuell erstellter Notizen. - Agenten können über MCP-Tools (`mindnet_query`, `mindnet_chat`) auf mindnet zugreifen. -- Self-Tuning optimiert die Gewichte in `retriever.yaml` basierend auf dem gesammelten Feedback (WP-08). ### 3.3 Langfristig @@ -191,10 +189,10 @@ Die folgenden Prinzipien steuern alle Workpackages und Entscheidungen: Phase A – Fundament & Import (Fertig) Phase B – Semantik, Graph & Lernen (Fertig) Phase C – Persönlichkeitsmodell & KI-Zwilling (Fertig) - Phase D – Agenten, MCP & Interaktion (Laufend) + Phase D – Agenten, MCP & Interaktion (Aktiv) Phase E – Review, Refactoring, Dokumentation -Alle Workpackages sind einer Phase zugeordnet. WP-01 bis WP-06 und WP-10 sind bereits erfolgreich abgeschlossen. +Alle Workpackages sind einer Phase zugeordnet. WP-01 bis WP-07 und WP-10/10a sind erfolgreich abgeschlossen. --- @@ -354,17 +352,18 @@ Transformation vom reinen Wissens-Abrufer zum strategischen Entscheidungspartner --- -### WP-07 – Interview-Assistent (geplant) +### WP-07 – Interview-Assistent (abgeschlossen) **Phase:** C/D -**Status:** 🟡 geplant (Nächster Fokus) +**Status:** 🟢 abgeschlossen **Ziel:** Dialogbasierter Erfassungs-Assistent, der strukturierte Interviews führt und daraus konsistente Markdown-Notizen generiert. -**Umfang:** -- Design von Interview-Flows (via Router-Strategie `INTERVIEW`). -- Konvertierung von Dialogtranskripten in typisierte Notes (Draft-Erstellung). +**Erreichte Ergebnisse:** +- **One-Shot Extractor:** Extrahiert Notiz-Inhalte aus einem Prompt. +- **Schema Injection:** Typspezifische Pflichtfelder (Late Binding). +- **Draft Generator:** Liefert validen Markdown-Codeblock mit `status: draft`. **Aufwand / Komplexität:** - Aufwand: Niedrig/Mittel @@ -375,7 +374,7 @@ Dialogbasierter Erfassungs-Assistent, der strukturierte Interviews führt und da ### WP-08 – Self-Tuning v1/v2 (geplant) **Phase:** B/C -**Status:** 🟡 geplant +**Status:** 🟡 geplant (Nächster Fokus) **Ziel:** Aufbau eines Self-Tuning-Mechanismus, der auf Basis von Feedback-Daten (WP-04c) Vorschläge für Retriever- und Policy-Anpassungen macht. @@ -429,18 +428,18 @@ Ablösung der Terminal-Interaktion durch ein grafisches Interface. --- -### WP-10a – GUI Evolution: Interaktion & Tools (geplant) +### WP-10a – GUI Evolution: Draft Editor (abgeschlossen) **Phase:** D -**Status:** 🟡 geplant (nach WP07/11) +**Status:** 🟢 abgeschlossen **Ziel:** Anpassung der GUI an komplexe Interaktionsmuster, die durch den Interview-Assistenten und Knowledge-Builder entstehen. -**Umfang:** -- **Draft-Editor:** Interaktive Bearbeitung und Bestätigung der Markdown-Entwürfe aus WP-07. -- **Suggestion-UI:** Checkboxen/Toggles für Kanten-Vorschläge aus WP-11 (Human-in-the-Loop). -- **Writeback:** Physisches Speichern der bestätigten Inhalte im Vault. +**Erreichte Ergebnisse:** +- **Draft Editor:** Interaktive `st.text_area` für generierte Entwürfe. +- **Sanitizer:** `normalize_meta_and_body` zur Korrektur von LLM-Fehlern. +- **Download/Copy:** Export-Funktionen für Markdown. **Aufwand / Komplexität:** - Aufwand: Mittel @@ -512,7 +511,7 @@ Aufräumen, dokumentieren, stabilisieren – insbesondere für Onboarding Dritte WP01 → WP02 → WP03 → WP04a WP04a → WP04b → WP04c → WP08 WP03 → WP05 → WP06 → WP07 - WP07/WP11 → WP10a + WP07 → WP10a WP03 → WP09 WP01/WP03 → WP10 → WP11 → WP12 WP03/WP04 → WP13 @@ -553,11 +552,11 @@ Aufräumen, dokumentieren, stabilisieren – insbesondere für Onboarding Dritte | WP05 | 🟢 | | WP05b | ⚪ | | WP06 | 🟢 | -| WP07 | 🟡 | +| WP07 | 🟢 | | WP08 | 🟡 | | WP09 | 🟡 | | WP10 | 🟢 | -| WP10a | 🟡 | +| WP10a | 🟢 | | WP11 | 🟡 | | WP12 | 🟡 | | WP13 | 🟡 | @@ -578,15 +577,15 @@ Aufräumen, dokumentieren, stabilisieren – insbesondere für Onboarding Dritte ## 11. Executive Summary -mindnet v2.2 ist so aufgesetzt, dass: +mindnet v2.4 ist so aufgesetzt, dass: -- du **schrittweise** Wissen erfassen kannst (Obsidian + Chat + später Interview-Assistent), +- du **schrittweise** Wissen erfassen kannst (Obsidian + Chat + Interview-Drafts), - die Struktur **mitwächst**, ohne Retro-Massenarbeit im Vault, - ein **hybrider Retriever** qualitativ hochwertige, erklärbare Antworten liefert, - ein **Self-Healing- und Self-Tuning-Mechanismus** vorbereitet ist (durch WP-04c Feedback-Daten), - ein **Persönlichkeitsmodell** (Decision Engine, Empathie) existiert und den Tonfall situativ anpasst, -- eine **grafische Oberfläche** (WP-10) existiert, die komplexe Zusammenhänge (Intents, Quellen) visualisiert, +- eine **grafische Oberfläche** (WP-10/10a) existiert, die komplexe Zusammenhänge visualisiert und Co-Creation ermöglicht, - langfristig ein **KI-Zwilling** aufgebaut wird, der deine Werte, Erfahrungen und Denkweise spiegelt, - die technische Architektur (FastAPI, Qdrant, YAML-Policies, MCP-Integration) lokal, nachvollziehbar und erweiterbar bleibt. -Dieser Programmplan bildet die konsolidierte Grundlage (v2.3.1) für alle weiteren Arbeiten. \ No newline at end of file +Dieser Programmplan bildet die konsolidierte Grundlage (v2.4.0) für alle weiteren Arbeiten. \ No newline at end of file diff --git a/docs/Knowledge_Design_Manual.md b/docs/Knowledge_Design_Manual.md index 1175003..95de77e 100644 --- a/docs/Knowledge_Design_Manual.md +++ b/docs/Knowledge_Design_Manual.md @@ -1,7 +1,7 @@ -# mindnet v2.2 – Knowledge Design Manual -**Datei:** `docs/mindnet_knowledge_design_manual_v2.2.md` +# mindnet v2.4 – Knowledge Design Manual +**Datei:** `docs/mindnet_knowledge_design_manual_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Integrierter Stand WP01–WP10) +**Status:** **FINAL** (Integrierter Stand WP01–WP10a) **Quellen:** `knowledge_design.md`, `TYPE_REGISTRY_MANUAL.md`, `chunking_strategy.md`, `mindnet_functional_architecture.md`. --- @@ -28,6 +28,7 @@ Seit Version 2.3.1 verfügt Mindnet über: * **Hybrid Router:** Das System erkennt, ob du Fakten, Entscheidungen oder Empathie brauchst. * **Context Intelligence:** Das System lädt je nach Situation unterschiedliche Notiz-Typen (z.B. Werte bei Entscheidungen). * **Web UI (WP10):** Du kannst direkt sehen, welche Quellen genutzt wurden. +* **Interview Modus (WP07):** Du kannst Notizen direkt im Chat entwerfen lassen. ### 1.2 Der Vault als „Source of Truth“ Die Markdown-Dateien in deinem Vault sind die **einzige Quelle der Wahrheit**. @@ -48,7 +49,7 @@ Jede Datei muss mindestens folgende Felder enthalten, um korrekt verarbeitet zu id: 20251110-projekt-alpha-8a9b2c # Eindeutige Kennung (YYYYMMDD-slug) title: Projekt Alpha # Sprechender Titel (wird in Suchergebnissen angezeigt) type: project # Steuert Verarbeitung & Vernetzung (siehe Kap. 3) - status: active # Status (z.B. draft, active, archived) + status: active # active, archived oder 'draft' (für generierte Notes) created: 2025-11-10 # Erstellungsdatum (ISO 8601 oder YYYY-MM-DD) updated: 2025-12-07 # Letzte Änderung tags: [ki, entwicklung] # Taxonomie für Filterung @@ -79,23 +80,23 @@ Diese Felder sind technisch nicht zwingend, aber für bestimmte Typen sinnvoll: ## 3. Note-Typen & Typ-Registry -Der `type` ist der wichtigste Hebel im Knowledge Design. Er steuert nicht nur das Gewicht bei der Suche, sondern seit WP-06 auch, **wann** eine Notiz aktiv in den Chat geholt wird ("Strategic Retrieval"). +Der `type` ist der wichtigste Hebel im Knowledge Design. Er steuert nicht nur das Gewicht bei der Suche, sondern seit WP-06 auch, **wann** eine Notiz aktiv in den Chat geholt wird ("Strategic Retrieval") und **welche Felder** im Interview abgefragt werden. ### 3.1 Übersicht der Kern-Typen Mindnet unterscheidet verschiedene Wissensarten. Wähle den Typ, der die **Rolle** der Notiz am besten beschreibt: -| Typ | Beschreibung & Einsatzzweck | Rolle im Chat (Intent) | -| :--- | :--- | :--- | -| **`concept`** | Fachbegriffe, Theorien. Zeitloses Wissen. | **FACT** (Basiswissen) | -| **`project`** | Ein Vorhaben mit Ziel, Dauer und Aufgaben. | **FACT / DECISION** (Kontext) | -| **`experience`** | Persönliche Erfahrung, Lektion oder Erkenntnis. | **EMPATHY** (Spiegelung) | -| **`decision`** | Eine bewusst getroffene Entscheidung (ADR). | **DECISION** (Begründung "Warum") | -| **`value`** | Ein persönlicher Wert oder ein Prinzip. | **DECISION** (Moralischer Kompass) | -| **`goal`** | Ein strategisches Ziel (kurz- oder langfristig). | **DECISION** (Abgleich) | -| **`person`** | Eine reale Person (Netzwerk, Autor). | **FACT** | -| **`journal`** | Zeitbezogener Log-Eintrag, Daily Note. | **FACT** (Historie) | -| **`source`** | Externe Quelle (Buch, PDF, Artikel). | **FACT** (Faktenbasis) | +| Typ | Beschreibung & Einsatzzweck | Rolle im Chat (Intent) | Interview Schema (WP07) | +| :--- | :--- | :--- | :--- | +| **`concept`** | Fachbegriffe, Theorien. Zeitloses Wissen. | **FACT** | Titel, Definition, Tags | +| **`project`** | Ein Vorhaben mit Ziel, Dauer und Aufgaben. | **FACT / DECISION** | Ziel, Status, Stakeholder, Steps | +| **`experience`** | Persönliche Erfahrung, Lektion oder Erkenntnis. | **EMPATHY** | Situation, Erkenntnis, Emotionen | +| **`decision`** | Eine bewusst getroffene Entscheidung (ADR). | **DECISION** | Kontext, Entscheidung, Alternativen | +| **`value`** | Ein persönlicher Wert oder ein Prinzip. | **DECISION** | Definition, Anti-Beispiel | +| **`goal`** | Ein strategisches Ziel (kurz- oder langfristig). | **DECISION** | Zeitrahmen, KPIs, Werte | +| **`person`** | Eine reale Person (Netzwerk, Autor). | **FACT** | Rolle, Kontext | +| **`journal`** | Zeitbezogener Log-Eintrag, Daily Note. | **FACT** | Datum, Tags | +| **`source`** | Externe Quelle (Buch, PDF, Artikel). | **FACT** | Autor, URL | ### 3.2 Zusammenspiel mit `types.yaml` @@ -200,6 +201,14 @@ Da deine Notizen im Web-Interface (WP10) angezeigt werden: * **Kurze Absätze:** Das LLM liest besser, und der Mensch scannt schneller. * **Klare Headings:** Nutze H1/H2, um dem Chunker logische Trennlinien zu geben. +### 5.4 Der Interview-Modus (Auto-Drafting) +Seit v2.4 (WP07) kann Mindnet Notizen für dich entwerfen. + +* **Wie:** Schreibe im Chat einfach: *"Ich möchte ein neues Projekt 'Alpha' anlegen, Ziel ist die Markteroberung."* +* **Was passiert:** Mindnet erkennt den Wunsch (`INTERVIEW`), wählt das Schema für `project` und generiert **sofort** einen Markdown-Entwurf. +* **Ergebnis:** Du erhältst einen Code-Block mit `status: draft`. Diesen kannst du im Draft-Editor (UI) direkt bearbeiten und herunterladen. +* **Drafts:** Enthalten oft Platzhalter `[TODO]`. Das ist Absicht. Ergänze sie im Editor. + --- ## 6. Best Practices & Beispiele (Klassik) diff --git a/docs/Overview.md b/docs/Overview.md index 1162ee4..d8aacdc 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -1,8 +1,8 @@ -# Mindnet v2.2 – Overview & Einstieg -**Datei:** `docs/mindnet_overview_v2.2.md` +# Mindnet v2.4 – Overview & Einstieg +**Datei:** `docs/mindnet_overview_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Post-WP10 Release) -**Version:** 2.3.1 +**Status:** **FINAL** (Inkl. Interview-Assistent & Web-Editor) +**Version:** 2.4.0 --- @@ -13,10 +13,10 @@ Anders als herkömmliche Notiz-Apps (wie Obsidian oder Evernote), die Texte nur passiv speichern, ist Mindnet ein **aktives System**: * Es **versteht** Zusammenhänge über einen Wissensgraphen. * Es **begründet** Antworten ("Warum ist das so?"). -* Es **antwortet** situativ angepasst: Mal als harter Strategieberater, mal als empathischer Spiegel. +* Es **antwortet** situativ angepasst: Mal als Strategieberater, mal als empathischer Spiegel, und neu: **als Interviewer, der hilft, Wissen zu erfassen.** ### Die Vision -> „Ein System, das nicht nur speichert, was ich weiß, sondern auch wie ich denke und fühle.“ +> „Ein System, das nicht nur speichert, was ich weiß, sondern auch wie ich denke, fühle und arbeite.“ --- @@ -36,14 +36,16 @@ Mindnet arbeitet auf drei Schichten, die aufeinander aufbauen: * **Technik:** Hybrider Retriever (Graph + Vektor), Explanation Engine. * **Status:** 🟢 Live (WP04). -### Ebene 3: Identität (Die Persönlichkeit) -* **Funktion:** Interaktion und Bewertung. Das System nimmt eine Haltung ein. -* **Logik:** "Ich empfehle Lösung X, weil sie unserem Wert 'Datensparsamkeit' entspricht." +### Ebene 3: Identität & Interaktion (Die Persönlichkeit) +* **Funktion:** Interaktion, Bewertung und Co-Creation. +* **Logik:** + * "Ich empfehle Lösung X, weil sie unserem Wert 'Datensparsamkeit' entspricht." + * "Ich sehe, du willst ein Projekt starten. Lass uns die Eckdaten erfassen." * **Technik:** - * **Intent Router:** Erkennt Absichten (Fakt vs. Gefühl vs. Entscheidung). + * **Intent Router:** Erkennt Absichten (Fakt vs. Gefühl vs. Entscheidung vs. Interview). * **Strategic Retrieval:** Lädt gezielt Werte oder Erfahrungen nach. - * **Multi-Persona:** Passt den Tonfall an. -* **Status:** 🟢 Live (WP05–WP06). + * **One-Shot Extraction:** Generiert Entwürfe für neue Notizen. +* **Status:** 🟢 Live (WP05–WP07, WP10). --- @@ -51,18 +53,20 @@ Mindnet arbeitet auf drei Schichten, die aufeinander aufbauen: Der Datenfluss in Mindnet ist zyklisch ("Data Flywheel"): -1. **Input:** Du schreibst Notizen in Obsidian. +1. **Input:** Du schreibst Notizen in Obsidian **ODER** lässt sie von Mindnet im Chat entwerfen. 2. **Ingest:** Ein Python-Skript importiert, zerlegt (Chunking) und vernetzt (Edges) die Daten in Qdrant. -3. **Intent Recognition:** Der Router analysiert deine Frage: Willst du Fakten, Code oder Empathie? -4. **Retrieval:** Das System sucht Inhalte passend zum Intent (z.B. "Lade Erfahrungen bei Empathie"). -5. **Generation:** Ein lokales LLM (Ollama) formuliert die Antwort. +3. **Intent Recognition:** Der Router analysiert deine Frage: Willst du Fakten, Code, Empathie oder etwas dokumentieren? +4. **Retrieval / Action:** + * Bei Fragen: Das System sucht Inhalte passend zum Intent. + * Bei Interviews: Das System wählt das passende Schema (z.B. Projekt-Vorlage). +5. **Generation:** Ein lokales LLM (Ollama) formuliert die Antwort oder den Markdown-Draft. 6. **Feedback:** Du bewertest die Antwort. Das System lernt (langfristig) daraus. **Tech-Stack:** -* **Backend:** Python 3.12, FastAPI. +* **Backend:** Python 3.10+, FastAPI. * **Datenbank:** Qdrant (Vektor & Graph). -* **KI:** Ollama (Phi-3 Mini / Mistral) – 100% lokal. -* **Frontend:** Streamlit Web-UI (v2.3.1). +* **KI:** Ollama (Phi-3 Mini) – 100% lokal. +* **Frontend:** Streamlit Web-UI (v2.4). --- @@ -72,25 +76,25 @@ Wo findest du was? | Wenn du... | ...lies dieses Dokument | | :--- | :--- | -| **...wissen willst, wie man Notizen schreibt.** | `mindnet_knowledge_design_manual_v2.2.md` | -| **...das System installieren oder betreiben musst.** | `mindnet_admin_guide_v2.2.md` | -| **...am Python-Code entwickeln willst.** | `mindnet_developer_guide_v2.2.md` | -| **...die Pipeline (Import -> RAG) verstehen willst.** | `mindnet_pipeline_playbook_v2.2.md` | +| **...wissen willst, wie man Notizen schreibt.** | `mindnet_knowledge_design_manual_v2.4.md` | +| **...das System installieren oder betreiben musst.** | `mindnet_admin_guide_v2.4.md` | +| **...am Python-Code entwickeln willst.** | `mindnet_developer_guide_v2.4.md` | +| **...die Pipeline (Import -> RAG) verstehen willst.** | `mindnet_pipeline_playbook_v2.4.md` | | **...die genaue JSON-Struktur oder APIs suchst.** | `mindnet_technical_architecture.md` | | **...verstehen willst, was fachlich passiert.** | `mindnet_functional_architecture.md` | -| **...den aktuellen Projektstatus suchst.** | `Programmplan_V2.2.md` | +| **...den aktuellen Projektstatus suchst.** | `mindnet_appendices_v2.4.md` | --- ## 5. Rollen im System * **Mindmaster (User/Owner):** Du. Du erstellst Inhalte, stellst Fragen und gibst Feedback. Du definierst die Werte (`type: value`). -* **Mindnet (Der Agent):** Der digitale Zwilling. Er agiert als pragmatischer, transparenter Assistent im Chat. +* **Mindnet (Der Agent):** Der digitale Zwilling. Er agiert als pragmatischer, transparenter Assistent und Analyst. * **Administrator:** Verantwortlich für Docker-Container, Backups und LLM-Ressourcen. --- ## 6. Aktueller Fokus -Wir haben **Phase D (Interaktion)** mit WP10 (Chat Interface) begonnen und die UI erfolgreich deployed. -Das System ist nun für Endnutzer bedienbar. Als Nächstes folgt der **Interview-Assistent (WP07)**, um Wissen dialogisch zu erfassen. \ No newline at end of file +Wir haben den **Interview-Assistenten (WP07)** und den **Draft-Editor (WP10a)** erfolgreich integriert. +Das System kann nun aktiv helfen, Wissen zu strukturieren, anstatt es nur abzurufen. Der Fokus verschiebt sich nun in Richtung **Self-Tuning (WP08)**, um aus dem gesammelten Feedback automatisch zu lernen. \ No newline at end of file diff --git a/docs/admin_guide.md b/docs/admin_guide.md index 8603b3e..c9bafe8 100644 --- a/docs/admin_guide.md +++ b/docs/admin_guide.md @@ -1,8 +1,8 @@ -# Mindnet v2.2 – Admin Guide -**Datei:** `docs/mindnet_admin_guide_v2.2.md` +# Mindnet v2.4 – Admin Guide +**Datei:** `docs/mindnet_admin_guide_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Inkl. Frontend Deployment) -**Quellen:** `Handbuch.md`, `mindnet_developer_guide_v2.2.md`. +**Status:** **FINAL** (Inkl. Frontend Deployment & Interview Config) +**Quellen:** `Handbuch.md`, `mindnet_developer_guide_v2.4.md`. > Dieses Handbuch richtet sich an **Administratoren**. Es beschreibt Installation, Konfiguration, Backup-Strategien, Monitoring und den sicheren Betrieb der Mindnet-Instanz (API + UI + DB). @@ -66,7 +66,7 @@ Mindnet benötigt einen lokalen LLM-Server für den Chat. curl http://localhost:11434/api/generate -d '{"model": "phi3:mini", "prompt":"Hi"}' ### 2.5 Konfiguration (ENV) -Erstelle eine `.env` Datei im Root-Verzeichnis. Die neuen Settings für WP-06 (Timeout, Decision Config) sind essenziell für stabilen Betrieb auf CPUs. +Erstelle eine `.env` Datei im Root-Verzeichnis. Die neuen Settings für WP-06/WP-07 (Timeout, Decision Config) sind essenziell für stabilen Betrieb auf CPUs. # Qdrant Verbindung QDRANT_URL="http://localhost:6333" @@ -77,7 +77,7 @@ Erstelle eine `.env` Datei im Root-Verzeichnis. Die neuen Settings für WP-06 (T # LLM / RAG Settings MINDNET_LLM_MODEL="phi3:mini" - MINDNET_OLLAMA_URL="[http://127.0.0.1:11434](http://127.0.0.1:11434)" + MINDNET_OLLAMA_URL="http://127.0.0.1:11434" # Config & Timeouts MINDNET_PROMPTS_PATH="./config/prompts.yaml" diff --git a/docs/appendix.md b/docs/appendix.md index 091d832..d9fc8f4 100644 --- a/docs/appendix.md +++ b/docs/appendix.md @@ -1,7 +1,7 @@ -# Mindnet v2.2 – Appendices & Referenzen -**Datei:** `docs/mindnet_appendices_v2.2.md` +# Mindnet v2.4 – Appendices & Referenzen +**Datei:** `docs/mindnet_appendices_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Integrierter Stand WP01–WP10) +**Status:** **FINAL** (Integrierter Stand WP01–WP10a) **Quellen:** `TYPE_REGISTRY_MANUAL.md`, `chunking_strategy.md`, `mindnet_technical_architecture.md`, `Handbuch.md`. > Dieses Dokument bündelt Tabellen, Schemata und technische Referenzen, die in den Prozess-Dokumenten (Playbook, Guides) den Lesefluss stören würden. @@ -10,7 +10,7 @@ ## Anhang A: Typ-Registry Referenz (Default-Werte) -Diese Tabelle zeigt die Standard-Konfiguration der `types.yaml` (Stand v2.3.1). +Diese Tabelle zeigt die Standard-Konfiguration der `types.yaml` (Stand v2.4). | Typ (`type`) | Chunk Profile | Retriever Weight | Edge Defaults (Auto-Kanten) | Beschreibung | | :--- | :--- | :--- | :--- | :--- | @@ -23,8 +23,8 @@ Diese Tabelle zeigt die Standard-Konfiguration der `types.yaml` (Stand v2.3.1). | **source** | `long` | 0.50 | *(keine)* | Externe Quellen (Bücher, PDFs). | | **event** | `short` | 0.60 | `related_to` | Meetings, Konferenzen. | | **value** | `medium` | 1.00 | `related_to` | Persönliche Werte/Prinzipien. | -| **goal** | `medium` | 0.95 | `depends_on` | Strategische Ziele (Neu in WP06). | -| **belief** | `medium` | 0.90 | `related_to` | Glaubenssätze (Neu in WP06). | +| **goal** | `medium` | 0.95 | `depends_on` | Strategische Ziele. | +| **belief** | `medium` | 0.90 | `related_to` | Glaubenssätze. | | **default** | `medium` | 1.00 | `references` | Fallback, wenn Typ unbekannt. | --- @@ -71,7 +71,7 @@ Diese sind die Felder, die effektiv in Qdrant gespeichert werden. { "chunk_id": "string (keyword)", // Format: {note_id}#c{index} "note_id": "string (keyword)", // FK zur Note - "type": "string (keyword)", // Typ-Kopie aus Note (Neu in WP06a) + "type": "string (keyword)", // Typ-Kopie aus Note "text": "string (text)", // Reintext für Anzeige (ohne Overlap) "window": "string (text)", // Text + Overlap (für Embedding) "ord": "integer", // Laufende Nummer (1..N) @@ -87,10 +87,10 @@ Diese sind die Felder, die effektiv in Qdrant gespeichert werden. "source_id": "string (keyword)", // Chunk-ID (Start) "target_id": "string (keyword)", // Chunk-ID oder Note-Titel (Ziel) "kind": "string (keyword)", // z.B. 'depends_on' - "scope": "string (keyword)", // Immer 'chunk' in v2.2 + "scope": "string (keyword)", // Immer 'chunk' "rule_id": "string (keyword)", // Traceability: 'inline:rel', 'explicit:wikilink' "confidence": "float", // 0.0 - 1.0 - "note_id": "string (keyword)" // Owner Note ID (für Löschung) + "note_id": "string (keyword)" // Owner Note ID } --- @@ -107,10 +107,11 @@ Diese Variablen steuern das Verhalten der Skripte und Container. | `MINDNET_TYPES_FILE` | `config/types.yaml` | Pfad zur Typ-Registry. | | `MINDNET_RETRIEVER_CONFIG`| `config/retriever.yaml`| Pfad zur Scoring-Konfiguration. | | `MINDNET_PROMPTS_PATH` | `config/prompts.yaml` | Pfad zu LLM-Prompts (Neu in v2.2). | -| `MINDNET_DECISION_CONFIG` | `config/decision_engine.yaml` | Pfad zur Router-Config (Neu in v2.3). | +| `MINDNET_DECISION_CONFIG` | `config/decision_engine.yaml` | Router & Interview Config (Neu in v2.3). | | `MINDNET_LLM_MODEL` | `phi3:mini` | Name des Ollama-Modells (Neu in v2.2). | | `MINDNET_OLLAMA_URL` | `http://127.0.0.1:11434`| URL zum LLM-Server (Neu in v2.2). | -| `MINDNET_LLM_TIMEOUT` | `120.0` | Timeout für Ollama (Erhöhen auf 300.0 für CPU). | +| `MINDNET_LLM_TIMEOUT` | `300.0` | Timeout für Ollama (Erhöht für CPU-Inference). | +| `MINDNET_API_TIMEOUT` | `300.0` | Frontend Timeout (Streamlit). | | `MINDNET_HASH_COMPARE` | `Body` | Vergleichsmodus für Import (`Body`, `Frontmatter`, `Full`). | | `MINDNET_HASH_SOURCE` | `parsed` | Quelle für Hash (`parsed`, `raw`, `file`). | | `VECTOR_DIM` | `384` | Dimension der Embeddings (Modellabhängig). | @@ -119,16 +120,16 @@ Diese Variablen steuern das Verhalten der Skripte und Container. ## Anhang E: Glossar -* **Callout-Edge:** Kante via `> [!edge]`. * **Decision Engine:** Komponente, die den Intent prüft und Strategien wählt (WP06). +* **Draft Editor:** Web-Komponente zur Bearbeitung generierter Notizen (WP10a). * **Explanation Layer:** Komponente, die Scores und Graphen als Begründung liefert. * **Hybrid Router:** Kombination aus Keyword-Matching und LLM-Klassifizierung für Intents. +* **One-Shot Extractor:** LLM-Strategie zur sofortigen Generierung von Drafts ohne Rückfragen (WP07). * **RAG (Retrieval Augmented Generation):** Kombination aus Suche und Text-Generierung. -* **Strategic Retrieval:** Gezieltes Nachladen von Werten (`value`) bei Entscheidungfragen. --- -## Anhang F: Workpackage Status (v2.3.2) +## Anhang F: Workpackage Status (v2.4.0) Aktueller Implementierungsstand der Module. @@ -140,9 +141,9 @@ Aktueller Implementierungsstand der Module. | **WP04a**| Retriever Scoring | 🟢 Live | Hybrider Score (Semantik + Graph). | | **WP04b**| Explanation Layer | 🟢 Live | API liefert Reasons & Breakdown. | | **WP04c**| Feedback Loop | 🟢 Live | Logging (JSONL) & Traceability aktiv. | -| **WP05** | Persönlichkeit / Chat | 🟢 Live | RAG-Chat mit Context Enrichment. | -| **WP06** | Decision Engine | 🟢 Live | Hybrid Router, Strategic Retrieval, Multi-Persona. | -| **WP07** | Interview Assistent | 🟡 Geplant | Dialog-Modus (Nächster Schritt). | +| **WP05** | Persönlichkeit / Chat | 🟢 Live | RAG-Integration mit Context Enrichment. | +| **WP06** | Decision Engine | 🟢 Live | Intent-Router & Strategic Retrieval. | +| **WP07** | Interview Assistent | 🟢 Live | **One-Shot Extractor & Schemas aktiv.** | | **WP08** | Self-Tuning | 🔴 Geplant | Auto-Adjustment der Gewichte. | | **WP10** | Chat Interface | 🟢 Live | Web-Interface (Streamlit). | -| **WP10a**| GUI Evolution | 🔴 Geplant | Interaktive Tools & Editor. | \ No newline at end of file +| **WP10a**| Draft Editor | 🟢 Live | **Interaktives UI für WP07 Drafts.** | \ No newline at end of file diff --git a/docs/dev_workflow.md b/docs/dev_workflow.md index 513aa98..70537eb 100644 --- a/docs/dev_workflow.md +++ b/docs/dev_workflow.md @@ -1,6 +1,6 @@ -# Mindnet v2.2 – Entwickler-Workflow -**Datei:** `DEV_WORKFLOW.md` -**Stand:** 2025-12-10 (Aktualisiert: Inkl. Frontend WP10) +# Mindnet v2.4 – Entwickler-Workflow +**Datei:** `docs/DEV_WORKFLOW.md` +**Stand:** 2025-12-10 (Aktualisiert: Inkl. Interview-Tests WP07) Dieses Handbuch beschreibt den Entwicklungszyklus zwischen **Windows PC** (IDE), **Raspberry Pi** (Gitea) und **Beelink** (Runtime/Server). @@ -35,14 +35,14 @@ Hier erstellst du die neue Funktion in einer sicheren Umgebung. 2. **Branch erstellen:** * Klicke wieder unten links auf `main`. * Wähle `+ Create new branch...`. - * Gib den Namen ein: `feature/was-ich-tue` (z.B. `feature/wp06-decision`). + * Gib den Namen ein: `feature/was-ich-tue` (z.B. `feature/wp07-interview`). * Drücke **Enter**. 3. **Sicherheits-Check:** * Steht unten links jetzt dein Feature-Branch? **Nur dann darfst du Code ändern!** 4. **Coden:** - * Nimm deine Änderungen vor (z.B. neue YAML-Configs). + * Nimm deine Änderungen vor (z.B. neue Schemas in `decision_engine.yaml`). 5. **Sichern & Hochladen:** * **Source Control** Icon (Gabel-Symbol) -> Nachricht eingeben -> **Commit**. @@ -64,7 +64,7 @@ Hier prüfst du, ob dein neuer Code auf dem echten Server läuft. ```bash git fetch # Tipp: 'git branch -r' zeigt alle verfügbaren Branches an - git checkout feature/wp06-decision + git checkout feature/wp07-interview git pull ``` @@ -80,9 +80,9 @@ Hier prüfst du, ob dein neuer Code auf dem echten Server läuft. **Option A: Standard (Als Service laufen lassen)** Ideal, wenn du nur testen willst, ob es läuft. ```bash - # 1. API neustarten + # 1. API neustarten (Backend) sudo systemctl restart mindnet-dev - # 2. UI neustarten (falls Frontend-Änderungen) + # 2. UI neustarten (Frontend) sudo systemctl restart mindnet-ui-dev # Logs prüfen (um Fehler zu sehen): @@ -109,12 +109,21 @@ Hier prüfst du, ob dein neuer Code auf dem echten Server läuft. sudo systemctl start mindnet-ui-dev ``` -6. **Validieren:** - * **Browser:** Öffne `http://:8502` um die UI zu testen. +6. **Validieren (Smoke Tests):** + + * **Browser:** Öffne `http://:8502` um die UI zu testen (Intent Badge prüfen!). * **CLI:** Führe Testskripte in einem **zweiten Terminal** aus: + + **Test A: Decision Engine** ```bash - # Beispiel für Decision Engine Test - python tests/test_wp06_decision.py -p 8002 -q "Soll ich...?" + python tests/test_wp06_decision.py -p 8002 -q "Soll ich Qdrant nutzen?" + # Erwartung: Intent DECISION + ``` + + **Test B: Interview Modus (Neu!)** + ```bash + python tests/test_wp06_decision.py -p 8002 -q "Ich will ein neues Projekt starten" + # Erwartung: Intent INTERVIEW, Output ist Markdown Codeblock ``` --- @@ -165,7 +174,7 @@ Damit das Chaos nicht wächst, löschen wir den fertigen Branch. cd ~/mindnet_dev git checkout main git pull - git branch -d feature/wp06-decision + git branch -d feature/wp07-interview ``` 3. **VS Code:** * Auf `main` wechseln. @@ -189,12 +198,11 @@ Damit das Chaos nicht wächst, löschen wir den fertigen Branch. ## 4. Troubleshooting -**"Read timed out (120s)" / 500 Error beim Chat** -* **Ursache:** Das LLM (Ollama) braucht zu lange zum Laden ("Cold Start"). +**"Read timed out (300s)" / 500 Error beim Interview** +* **Ursache:** Das LLM (Ollama) braucht für den One-Shot Draft länger als das Timeout erlaubt. * **Lösung:** - 1. Erhöhe in `.env` den Wert: `MINDNET_LLM_TIMEOUT=300.0` (Backend) oder `MINDNET_API_TIMEOUT` (Frontend). + 1. Erhöhe in `.env` den Wert: `MINDNET_LLM_TIMEOUT=300.0`. 2. Starte die Server neu. - 3. Stelle sicher, dass dein Test-Skript (Client) auch ein hohes Timeout hat. **"Port 8002 / 8502 already in use"** * **Ursache:** Du willst `uvicorn` oder `streamlit` manuell starten, aber der Service läuft noch. diff --git a/docs/developer_guide.md b/docs/developer_guide.md index 14e33d9..e536dcd 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -1,14 +1,14 @@ -# Mindnet v2.2 – Developer Guide -**Datei:** `docs/mindnet_developer_guide_v2.2.md` +# Mindnet v2.4 – Developer Guide +**Datei:** `docs/mindnet_developer_guide_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Inkl. RAG, Decision Engine & Frontend WP10) +**Status:** **FINAL** (Inkl. RAG, Interview Mode & Frontend WP10) **Quellen:** `mindnet_technical_architecture.md`, `Handbuch.md`, `DEV_WORKFLOW.md`. > **Zielgruppe:** Entwickler:innen. > **Zweck:** Anleitung zum Aufsetzen der Entwicklungsumgebung, Verständnis der Modulstruktur und Durchführung von Tests. --- -- [Mindnet v2.2 – Developer Guide](#mindnet-v22--developer-guide) +- [Mindnet v2.4 – Developer Guide](#mindnet-v24--developer-guide) - [1. Projektstruktur (Post-WP10)](#1-projektstruktur-post-wp10) - [2. Lokales Setup (Development)](#2-lokales-setup-development) - [2.1 Voraussetzungen](#21-voraussetzungen) @@ -27,6 +27,7 @@ - [5. Das "Teach-the-AI" Paradigma (Context Intelligence)](#5-das-teach-the-ai-paradigma-context-intelligence) - [A. Workflow: Einen neuen Typ implementieren (z. B. `type: risk`)](#a-workflow-einen-neuen-typ-implementieren-z-b-type-risk) - [B. Workflow: Neue Beziehungen (Edges) nutzen (z. B. `beeinflusst_von`)](#b-workflow-neue-beziehungen-edges-nutzen-z-b-beeinflusst_von) + - [C. Workflow: Interview-Schema anpassen (WP07)](#c-workflow-interview-schema-anpassen-wp07) - [Fazit](#fazit) - [6. Nützliche Einzeiler](#6-nützliche-einzeiler) @@ -48,7 +49,7 @@ Der Code ist modular in `app` (Logik), `scripts` (CLI) und `config` (Steuerung) │ │ └── dto.py # Zentrale DTO-Definition │ ├── routers/ # FastAPI Endpoints │ │ ├── query.py # Suche - │ │ ├── chat.py # Hybrid Router & Decision Engine (WP06) + │ │ ├── chat.py # Hybrid Router & Interview Logic (WP06/WP07) │ │ ├── feedback.py # Feedback (WP04c) │ │ └── ... │ ├── services/ # Interne & Externe Dienste @@ -56,12 +57,12 @@ Der Code ist modular in `app` (Logik), `scripts` (CLI) und `config` (Steuerung) │ │ ├── feedback_service.py # Logging (JSONL Writer) │ │ └── embeddings_client.py │ ├── frontend/ # NEU (WP10) - │ │ └── ui.py # Streamlit Application + │ │ └── ui.py # Streamlit Application inkl. Draft-Editor │ └── main.py # Entrypoint der API ├── config/ # YAML-Konfigurationen (Single Source of Truth) │ ├── types.yaml # Import-Regeln - │ ├── prompts.yaml # LLM Prompts & RAG Templates (WP06) - │ ├── decision_engine.yaml # Router-Strategien (WP06) + │ ├── prompts.yaml # LLM Prompts & Interview Templates (WP06/07) + │ ├── decision_engine.yaml # Router-Strategien & Schemas (WP06/07) │ └── retriever.yaml # Scoring-Regeln & Kantengewichte ├── data/ │ └── logs/ # Lokale Logs (search_history.jsonl, feedback.jsonl) @@ -108,7 +109,7 @@ Erstelle eine `.env` Datei im Root-Verzeichnis. MINDNET_TYPES_FILE="./config/types.yaml" MINDNET_RETRIEVER_CONFIG="./config/retriever.yaml" - # LLM / RAG Settings (WP06) + # LLM / RAG Settings (WP06/07) MINDNET_LLM_MODEL="phi3:mini" MINDNET_OLLAMA_URL="http://127.0.0.1:11434" MINDNET_LLM_TIMEOUT=300.0 @@ -148,9 +149,9 @@ Dies ist das komplexeste Modul. * **Debugging:** Nutze `--dry-run` oder `scripts/payload_dryrun.py`. ### 3.2 Der Hybrid Router (`app.routers.chat`) -Hier liegt die Logik für Intent Detection (WP06). +Hier liegt die Logik für Intent Detection (WP06) und Interview-Modus (WP07). * **Logic:** `_classify_intent` prüft zuerst Keywords (Fast Path) und fällt auf `llm_service.generate_raw_response` zurück (Slow Path), wenn konfiguriert. -* **Config:** Gesteuert durch `decision_engine.yaml`. +* **One-Shot:** Wenn Intent `INTERVIEW` erkannt wird, wird **kein Retrieval** ausgeführt. Stattdessen wird ein Draft generiert. * **Erweiterung:** Um neue Intents hinzuzufügen, editiere nur die YAML, nicht den Python-Code (Late Binding). ### 3.3 Der Retriever (`app.core.retriever`) @@ -160,9 +161,9 @@ Hier passiert das Scoring. ### 3.4 Das Frontend (`app.frontend.ui`) Eine Streamlit-App (WP10). +* **Draft Editor:** Enthält einen YAML-Sanitizer (`normalize_meta_and_body`), der sicherstellt, dass LLM-Halluzinationen im Frontmatter nicht das File zerstören. * **State:** Nutzt `st.session_state` für Chat-History und Drafts. * **Logik:** Ruft `/chat` und `/feedback` Endpoints der API auf. -* **Anpassung:** CSS Styles sind direkt in `ui.py` eingebettet. --- @@ -204,7 +205,7 @@ Prüfen das laufende System gegen eine echte Qdrant-Instanz und Ollama. ## 5. Das "Teach-the-AI" Paradigma (Context Intelligence) -Mindnet lernt nicht durch Training (Fine-Tuning), sondern durch **Konfiguration**, **Vernetzung** und **Prompting**. Wenn du dem System ein neues Konzept beibringen willst, musst du in der Regel an drei Stellen eingreifen. Dies ist der **Core-Workflow** für Erweiterungen. +Mindnet lernt nicht durch Training (Fine-Tuning), sondern durch **Konfiguration**, **Vernetzung** und **Prompting**. Dies ist der **Core-Workflow** für Erweiterungen. ### A. Workflow: Einen neuen Typ implementieren (z. B. `type: risk`) @@ -227,26 +228,29 @@ Damit dieser Typ aktiv geladen wird, musst du ihn einer Strategie zuordnen. **3. Kognitive Ebene (`config/prompts.yaml`)** Erkläre dem LLM im Template, was es damit tun soll. - decision_template: | - ... - - Prüfe auf [RISK]: Wenn vorhanden, warne mich davor! - ### B. Workflow: Neue Beziehungen (Edges) nutzen (z. B. `beeinflusst_von`) Beziehungen sind der Klebstoff für die "Why"-Erklärungen. **1. Erfassung im Vault (Markdown)** -Du musst dem System keinen neuen Edge-Typ "beibringen" (Schema-less). Du nutzt ihn einfach. Nutze die Inline-Syntax im Fließtext: - - Die Entscheidung für Qdrant wurde [[rel:beeinflusst_von Budgetkürzung 2024]]. +> "Die Entscheidung für Qdrant wurde [[rel:beeinflusst_von Budgetkürzung 2024]]." **2. Gewichtung (`config/retriever.yaml`)** -Standardmäßig werden alle expliziten Kanten gleich behandelt. Wenn Kausalität wichtiger ist als Ähnlichkeit, konfiguriere dies hier. +Konfiguriere `edge_weights`, wenn Kausalität wichtiger ist als Ähnlichkeit. - scoring: - edge_type_weights: - beeinflusst_von: 1.5 # Sehr starker Einfluss auf das Ranking +### C. Workflow: Interview-Schema anpassen (WP07) + +Wenn Mindnet neue Fragen stellen soll: + +**1. Schema erweitern (`config/decision_engine.yaml`)** +Füge das Feld in die Liste ein. + + project: + fields: ["Titel", "Ziel", "Budget"] # <--- Budget neu + +**2. Keine Code-Änderung nötig** +Der `One-Shot Extractor` (Prompt Template) liest diese Liste dynamisch und weist das LLM an, das Budget zu extrahieren oder `[TODO]` zu setzen. ### Fazit * **Vault:** Liefert das Wissen. diff --git a/docs/mindnet_functional_architecture.md b/docs/mindnet_functional_architecture.md index a9b683f..25dfff5 100644 --- a/docs/mindnet_functional_architecture.md +++ b/docs/mindnet_functional_architecture.md @@ -1,15 +1,15 @@ -# Mindnet v2.2 – Fachliche Architektur -**Datei:** `docs/mindnet_functional_architecture_v2.2.md` +# Mindnet v2.4 – Fachliche Architektur +**Datei:** `docs/mindnet_functional_architecture_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Integrierter Stand WP01–WP10) +**Status:** **FINAL** (Integrierter Stand WP01–WP10 + WP07) -> Dieses Dokument beschreibt **was** Mindnet fachlich tut und **warum** – mit Fokus auf die Erzeugung und Nutzung von **Edges** (Kanten), die Logik des Retrievers und den **RAG-Chat** (Decision Engine & Persönlichkeit). Die technische Umsetzung wird im technischen Dokument detailliert. +> Dieses Dokument beschreibt **was** Mindnet fachlich tut und **warum** – mit Fokus auf die Erzeugung und Nutzung von **Edges** (Kanten), die Logik des Retrievers und den **RAG-Chat** (Decision Engine, Interview-Modus & Persönlichkeit). Die technische Umsetzung wird im technischen Dokument detailliert. ---
📖 Inhaltsverzeichnis (Klicken zum Öffnen) -- [Mindnet v2.2 – Fachliche Architektur](#mindnet-v22--fachliche-architektur) +- [Mindnet v2.4 – Fachliche Architektur](#mindnet-v24--fachliche-architektur) - [](#) - [0) Zielbild \& Grundprinzip](#0-zielbild--grundprinzip) - [1) Notizen \& Chunks (fachliche Perspektive)](#1-notizen--chunks-fachliche-perspektive) @@ -26,11 +26,12 @@ - [5) Der Retriever (Funktionaler Layer)](#5-der-retriever-funktionaler-layer) - [5.1 Scoring-Modell](#51-scoring-modell) - [5.2 Erklärbarkeit (Explainability) – WP04b](#52-erklärbarkeit-explainability--wp04b) - - [6) Context Intelligence \& Intent Router (WP06)](#6-context-intelligence--intent-router-wp06) + - [6) Context Intelligence \& Intent Router (WP06/WP07)](#6-context-intelligence--intent-router-wp06wp07) - [6.1 Das Problem: Statische vs. Dynamische Antworten](#61-das-problem-statische-vs-dynamische-antworten) - [6.2 Der Intent-Router (Keyword \& Semantik)](#62-der-intent-router-keyword--semantik) - [6.3 Strategic Retrieval (Injektion von Werten)](#63-strategic-retrieval-injektion-von-werten) - [6.4 Reasoning (Das Gewissen)](#64-reasoning-das-gewissen) + - [6.5 Der Interview-Modus (One-Shot Extraction) – Neu in v2.4](#65-der-interview-modus-one-shot-extraction--neu-in-v24) - [7) Future Concepts: The Empathic Digital Twin (Ausblick)](#7-future-concepts-the-empathic-digital-twin-ausblick) - [7.1 Antizipation durch Erfahrung](#71-antizipation-durch-erfahrung) - [7.2 Empathie \& "Ich"-Modus](#72-empathie--ich-modus) @@ -46,7 +47,7 @@ - [13) Lösch-/Update-Garantien (Idempotenz)](#13-lösch-update-garantien-idempotenz) - [14) Beispiel – Von Markdown zu Kanten (v2.2)](#14-beispiel--von-markdown-zu-kanten-v22) - [15) Referenzen (Projektdateien \& Leitlinien)](#15-referenzen-projektdateien--leitlinien) - - [16) Workpackage Status (v2.3.2)](#16-workpackage-status-v232) + - [16) Workpackage Status (v2.4.0)](#16-workpackage-status-v240)
--- @@ -208,13 +209,13 @@ Die API gibt diese Analysen als menschenlesbare Sätze (`reasons`) und als Daten --- -## 6) Context Intelligence & Intent Router (WP06) +## 6) Context Intelligence & Intent Router (WP06/WP07) -Seit WP06 agiert Mindnet nicht mehr statisch, sondern passt seine Suchstrategie dem **Intent** (der Absicht) des Nutzers an. Dies ist die Transformation vom reinen Wissens-Abrufer zum strategischen Entscheidungspartner. +Seit WP06/WP07 agiert Mindnet nicht mehr statisch, sondern passt seine Suchstrategie dem **Intent** (der Absicht) des Nutzers an. Dies ist die Transformation vom reinen Wissens-Abrufer zum strategischen Partner. ### 6.1 Das Problem: Statische vs. Dynamische Antworten * **Früher (Pre-WP06):** Jede Frage ("Was ist X?" oder "Soll ich X?") wurde gleich behandelt -> Fakten-Retrieval. -* **Heute (WP06):** Das System erkennt, *was* der User will, und lädt unterschiedliche Wissensbausteine. +* **Heute (WP06):** Das System erkennt, *was* der User will (Rat, Fakten oder Datenerfassung) und wechselt den Modus. ### 6.2 Der Intent-Router (Keyword & Semantik) Der Router prüft vor jeder Antwort die Absicht über konfigurierbare Strategien (`config/decision_engine.yaml`): @@ -222,11 +223,8 @@ Der Router prüft vor jeder Antwort die Absicht über konfigurierbare Strategien 1. **FACT:** Reine Wissensfrage ("Was ist Qdrant?"). → Standard RAG. 2. **DECISION:** Frage nach Rat oder Strategie ("Soll ich Qdrant nutzen?"). → Aktiviert die Decision Engine. 3. **EMPATHY:** Emotionale Zustände ("Ich bin gestresst"). → Aktiviert den empathischen Modus. -4. **CODING:** Technische Anfragen. - -**Erkennung:** -* **Fast Path:** Prüfung auf Schlüsselwörter (z.B. "soll ich"). -* **Slow Path (Smart Fallback):** Wenn kein Keyword passt, prüft ein LLM (Phi-3) die Semantik des Satzes. +4. **INTERVIEW (Neu in WP07):** Wunsch, Wissen zu erfassen ("Neues Projekt anlegen"). → Aktiviert den Draft-Generator. +5. **CODING:** Technische Anfragen. ### 6.3 Strategic Retrieval (Injektion von Werten) Im Modus `DECISION` führt das System eine **zweite Suchstufe** aus. Es sucht nicht nur nach semantisch passenden Texten zur Frage, sondern erzwingt das Laden von strategischen Notizen wie: @@ -234,12 +232,18 @@ Im Modus `DECISION` führt das System eine **zweite Suchstufe** aus. Es sucht ni * **Principles (`type: principle`):** Handlungsanweisungen. * **Goals (`type: goal`):** Strategische Ziele. -Dies wird über **Late Binding** in `config/decision_engine.yaml` gesteuert. Neue Facetten der Persönlichkeit können dort konfiguriert werden, ohne den Code zu ändern. - ### 6.4 Reasoning (Das Gewissen) Das LLM erhält im Prompt die explizite Anweisung: *"Wäge die Fakten (aus der Suche) gegen die injizierten Werte ab."* Dadurch entstehen Antworten, die nicht nur technisch korrekt sind, sondern subjektiv passend ("Tool X passt nicht zu deinem Ziel Z"). +### 6.5 Der Interview-Modus (One-Shot Extraction) – Neu in v2.4 +Wenn der User Wissen erfassen will ("Ich möchte ein neues Projekt anlegen"), wechselt Mindnet in den **Interview-Modus**. + +* **Late Binding Schema:** Das System lädt ein konfiguriertes Schema für den Ziel-Typ (z.B. `project`: Pflichtfelder sind Titel, Ziel, Status). +* **One-Shot Extraction:** Statt eines langen Dialogs extrahiert das LLM **sofort** alle verfügbaren Infos aus dem Prompt und generiert einen validen Markdown-Draft mit Frontmatter. +* **Draft-Status:** Fehlende Pflichtfelder werden mit `[TODO]` markiert. +* **UI-Integration:** Das Frontend rendert statt einer Chat-Antwort einen **interaktiven Editor** (WP10), in dem der Entwurf finalisiert werden kann. + --- ## 7) Future Concepts: The Empathic Digital Twin (Ausblick) @@ -401,10 +405,11 @@ Frontmatter-Eigenschaften (Properties) bleiben **minimiert**: - Persönlichkeit & Chat: `config/prompts.yaml` & `app/routers/chat.py`. - Decision Engine: `config/decision_engine.yaml`. - Logging Service: `app/services/feedback_service.py`. +- Frontend UI: `app/frontend/ui.py`. --- -## 16) Workpackage Status (v2.3.2) +## 16) Workpackage Status (v2.4.0) Aktueller Implementierungsstand der Module. @@ -418,7 +423,7 @@ Aktueller Implementierungsstand der Module. | **WP04c**| Feedback Loop | 🟢 Live | Logging (JSONL) & Traceability aktiv. | | **WP05** | Persönlichkeit / Chat | 🟢 Live | RAG-Integration mit Context Enrichment. | | **WP06** | Decision Engine | 🟢 Live | Intent-Router & Strategic Retrieval. | -| **WP07** | Interview Assistent | 🟡 Geplant | Dialog-Modus (Nächster Schritt). | +| **WP07** | Interview Assistent | 🟢 Live | **One-Shot Extractor & Schemas aktiv.** | | **WP08** | Self-Tuning | 🔴 Geplant | Auto-Adjustment der Gewichte. | | **WP10** | Chat Interface | 🟢 Live | Web-UI mit Feedback & Intents. | -| **WP10a**| GUI Evolution | 🔴 Geplant | Erweiterte Interaktion. | \ No newline at end of file +| **WP10a**| Draft Editor | 🟢 Live | **Interaktiver Editor für WP07 Drafts.** | \ No newline at end of file diff --git a/docs/mindnet_technical_architecture.md b/docs/mindnet_technical_architecture.md index dcb3312..7636dd6 100644 --- a/docs/mindnet_technical_architecture.md +++ b/docs/mindnet_technical_architecture.md @@ -1,17 +1,17 @@ -# Mindnet v2.2 – Technische Architektur -**Datei:** `docs/mindnet_technical_architecture_v2.2.md` +# Mindnet v2.4 – Technische Architektur +**Datei:** `docs/mindnet_technical_architecture_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Integrierter Stand WP01–WP10) +**Status:** **FINAL** (Integrierter Stand WP01–WP10 + WP07) **Quellen:** `Programmplan_V2.2.md`, `Handbuch.md`, `chunking_strategy.md`, `wp04_retriever_scoring.md`. > **Ziel dieses Dokuments:** -> Vollständige Beschreibung der technischen Architektur inkl. Graph-Datenbank, Retrieval-Logik, der **neuen RAG-Komponenten (Decision Engine & Hybrid Router)** und dem **Frontend (Streamlit)**. +> Vollständige Beschreibung der technischen Architektur inkl. Graph-Datenbank, Retrieval-Logik, der **RAG-Komponenten (Decision Engine & Hybrid Router)**, des **Interview-Modus** und dem **Frontend (Streamlit)**. ---
📖 Inhaltsverzeichnis (Klicken zum Öffnen) -- [Mindnet v2.2 – Technische Architektur](#mindnet-v22--technische-architektur) +- [Mindnet v2.4 – Technische Architektur](#mindnet-v24--technische-architektur) - [](#) - [1. Systemüberblick](#1-systemüberblick) - [1.1 Architektur-Zielbild](#11-architektur-zielbild) @@ -33,16 +33,17 @@ - [5.2 Scoring-Formel (WP04a)](#52-scoring-formel-wp04a) - [5.3 Explanation Layer (WP04b)](#53-explanation-layer-wp04b) - [5.4 Graph-Expansion](#54-graph-expansion) - - [6. RAG \& Chat Architektur (WP06 Hybrid Router)](#6-rag--chat-architektur-wp06-hybrid-router) + - [6. RAG \& Chat Architektur (WP06 Hybrid Router + WP07 Interview)](#6-rag--chat-architektur-wp06-hybrid-router--wp07-interview) - [6.1 Architektur-Pattern: Intent Router](#61-architektur-pattern-intent-router) - [6.2 Schritt 1: Intent Detection (Hybrid)](#62-schritt-1-intent-detection-hybrid) - [6.3 Schritt 2: Strategy Resolution (Late Binding)](#63-schritt-2-strategy-resolution-late-binding) - - [6.4 Schritt 3: Multi-Stage Retrieval](#64-schritt-3-multi-stage-retrieval) + - [6.4 Schritt 3: Retrieval vs. Extraction](#64-schritt-3-retrieval-vs-extraction) - [6.5 Schritt 4: Generation \& Response](#65-schritt-4-generation--response) - [7. Frontend Architektur (WP10)](#7-frontend-architektur-wp10) - [7.1 Kommunikation](#71-kommunikation) - [7.2 Features \& UI-Logik](#72-features--ui-logik) - - [7.3 Deployment Ports](#73-deployment-ports) + - [7.3 Draft-Editor \& Sanitizer (Neu in WP10a)](#73-draft-editor--sanitizer-neu-in-wp10a) + - [7.4 Deployment Ports](#74-deployment-ports) - [8. Feedback \& Logging Architektur (WP04c)](#8-feedback--logging-architektur-wp04c) - [8.1 Komponenten](#81-komponenten) - [8.2 Log-Dateien](#82-log-dateien) @@ -64,7 +65,7 @@ Mindnet ist ein **lokales RAG-System (Retrieval Augmented Generation)** mit Web- * **Qdrant:** Vektor-Datenbank für Graph und Semantik (Collections: notes, chunks, edges). * **Local Files (JSONL):** Append-Only Logs für Feedback und Search-History (Data Flywheel). 4. **Backend:** Eine FastAPI-Anwendung stellt Endpunkte für **Semantische** und **Hybride Suche** sowie **Feedback** bereit. -5. **Frontend:** Streamlit-App (`ui.py`) für Interaktion und Visualisierung. +5. **Frontend:** Streamlit-App (`ui.py`) für Interaktion und Visualisierung inkl. **Draft Editor**. 6. **Inference:** Lokales LLM (Ollama: Phi-3 Mini) für RAG-Chat und Antwortgenerierung. Das System arbeitet **deterministisch** (stabile IDs) und ist **konfigurationsgetrieben** (`types.yaml`, `retriever.yaml`, `decision_engine.yaml`, `prompts.yaml`). @@ -87,7 +88,7 @@ Das System arbeitet **deterministisch** (stabile IDs) und ist **konfigurationsge │ ├── models/ # Pydantic DTOs │ ├── routers/ │ │ ├── query.py # Such-Endpunkt - │ │ ├── chat.py # Hybrid Router & Decision Engine (WP06) + │ │ ├── chat.py # Hybrid Router & Interview Logic (WP06/07) │ │ ├── feedback.py # Feedback-Endpunkt (WP04c) │ │ └── ... │ ├── services/ @@ -95,11 +96,11 @@ Das System arbeitet **deterministisch** (stabile IDs) und ist **konfigurationsge │ │ ├── feedback_service.py # JSONL Logging (WP04c) │ │ └── embeddings_client.py │ ├── frontend/ # NEU (WP10) - │ └── ui.py # Streamlit Application + │ └── ui.py # Streamlit Application inkl. Sanitizer ├── config/ │ ├── types.yaml # Typ-Definitionen (Import-Zeit) │ ├── retriever.yaml # Scoring-Gewichte (Laufzeit) - │ ├── decision_engine.yaml # Strategien & Keywords (WP06) + │ ├── decision_engine.yaml # Strategien & Schemas (WP06/WP07) │ └── prompts.yaml # LLM System-Prompts & Templates (WP06) ├── data/ │ └── logs/ # Lokale JSONL-Logs (WP04c) @@ -193,14 +194,14 @@ Steuert das Scoring zur Laufzeit (WP04a). centrality_weight: 0.5 ### 3.3 Decision Engine (`config/decision_engine.yaml`) -**Neu in WP06:** Steuert den Intent-Router. -* Definiert Strategien (`DECISION`, `EMPATHY`, `CODING`, `FACT`). -* Definiert `trigger_keywords` und `inject_types` (Late Binding). +**Neu in WP06/07:** Steuert den Intent-Router und die Interview-Schemas. +* Definiert Strategien (`DECISION`, `INTERVIEW`, etc.). +* Definiert `schemas` für den Interview-Modus (Pflichtfelder pro Typ). * Definiert LLM-Router-Settings (`llm_fallback_enabled`). ### 3.4 Prompts (`config/prompts.yaml`) Steuert die LLM-Persönlichkeit und Templates. -* Enthält Templates für alle Strategien (z.B. `decision_template`, `empathy_template`, `technical_template`). +* Enthält Templates für alle Strategien inkl. `interview_template` mit One-Shot Logik. ### 3.5 Environment (`.env`) Erweiterung für LLM-Steuerung: @@ -273,7 +274,7 @@ Der Hybrid-Modus lädt dynamisch die Nachbarschaft der Top-K Vektor-Treffer ("Se --- -## 6. RAG & Chat Architektur (WP06 Hybrid Router) +## 6. RAG & Chat Architektur (WP06 Hybrid Router + WP07 Interview) Der Flow für eine Chat-Anfrage (`/chat`) wurde in WP06 auf eine **Configuration-Driven Architecture** umgestellt. Der `ChatRouter` (`app/routers/chat.py`) fungiert als zentraler Dispatcher. @@ -295,46 +296,61 @@ Der Router ermittelt die Absicht (`Intent`) des Nutzers. * Nur aktiv, wenn `llm_fallback_enabled: true`. * Greift, wenn keine Keywords gefunden wurden. * Sendet die Query an das LLM mit einem Klassifizierungs-Prompt (`llm_router_prompt`). - * Ergebnis: `EMPATHY`, `DECISION`, `CODING` oder `FACT` (Default). + * Ergebnis: `EMPATHY`, `DECISION`, `INTERVIEW`, `CODING` oder `FACT`. ### 6.3 Schritt 2: Strategy Resolution (Late Binding) Basierend auf dem Intent lädt der Router die Parameter: -* **inject_types:** Liste der Notiz-Typen, die erzwungen werden sollen (z.B. `["value", "goal"]` bei `DECISION`). -* **prompt_template:** Schlüssel für das Template in `prompts.yaml` (z.B. `decision_template`, `empathy_template`). +* **Bei RAG (FACT/DECISION):** `inject_types` für Strategic Retrieval. +* **Bei INTERVIEW (WP07):** `schemas` (Pflichtfelder) basierend auf der erkannten Ziel-Entität (`_detect_target_type`). -### 6.4 Schritt 3: Multi-Stage Retrieval -1. **Primary Retrieval:** Hybride Suche nach der User-Query (findet Fakten). +### 6.4 Schritt 3: Retrieval vs. Extraction +Der Router verzweigt hier: + +**A) RAG Modus (FACT, DECISION, EMPATHY):** +1. **Primary Retrieval:** Hybride Suche nach der User-Query. 2. **Strategic Retrieval (Conditional):** Wenn `inject_types` definiert sind, erfolgt eine zweite Suche, die explizit auf diese Typen filtert. 3. **Merge:** Ergebnisse werden dedupliziert zusammengeführt. +**B) Interview Modus (INTERVIEW):** +1. **Kein Retrieval:** Der Kontext ist der Chat-Verlauf (oder initial die Query). +2. **Schema Injection:** Das Schema für den erkannten Typ (z.B. "Project") wird geladen. +3. **Prompt Assembly:** Der `interview_template` Prompt wird mit der Schema-Definition ("Ziel", "Status") gefüllt. + ### 6.5 Schritt 4: Generation & Response -* **Context Enrichment:** Metadaten (`[VALUE]`, `[GOAL]`, `[SCORE]`) werden in den Context-String injiziert, um dem LLM die Rolle des Textstücks zu signalisieren. * **Templating:** Das LLM erhält den Prompt basierend auf dem gewählten Template. * **Execution:** Der `LLMService` führt den Call aus. Ein konfigurierbarer Timeout (`MINDNET_LLM_TIMEOUT`) fängt Cold-Start-Verzögerungen auf CPU-Hardware ab. -* **Response:** Rückgabe enthält Antworttext, Quellenliste und den erkannten `intent` (für Debugging/Frontend). +* **Response:** Rückgabe enthält Antworttext (im Interview-Modus: Markdown Codeblock), Quellenliste und den erkannten `intent`. --- ## 7. Frontend Architektur (WP10) -Das Frontend ist eine **Streamlit-Anwendung**, die als separater Prozess läuft und via HTTP mit dem Backend kommuniziert. +Das Frontend ist eine **Streamlit-Anwendung** (`app/frontend/ui.py`), die als separater Prozess läuft und via HTTP mit dem Backend kommuniziert. ### 7.1 Kommunikation * **Backend-URL:** Konfiguriert via `MINDNET_API_URL` (Default: `http://localhost:8002`). * **Endpoints:** Nutzt `/chat` für Interaktion und `/feedback` für Bewertungen. -* **Resilienz:** Das Frontend implementiert eigene Timeouts (`MINDNET_API_TIMEOUT`, Default 300s), um lange Wartezeiten lokaler LLMs abzufangen. +* **Resilienz:** Das Frontend implementiert eigene Timeouts (`MINDNET_API_TIMEOUT`, Default 300s). ### 7.2 Features & UI-Logik -* **State Management:** Session-State speichert Chat-Verlauf und `query_id` für Feedback. +* **State Management:** Session-State speichert Chat-Verlauf und `query_id`. * **Visualisierung:** - * **Intent Badges:** Zeigt Router-Entscheidung (`DECISION`, `FACT`, etc.) und Quelle (`Keyword` vs. `LLM`). + * **Intent Badges:** Zeigt Router-Entscheidung (`DECISION`, `INTERVIEW`, etc.). * **Source Expanders:** Zeigt verwendete Chunks inkl. Score und "Why"-Explanation. * **Sidebar:** Zeigt Suchhistorie (Log-basiert) und Konfiguration. -* **Feedback:** - * Global (Antwort): 1-5 Sterne Rating. - * Granular (Quellen): Faces-Rating (Mapped auf 1-5 Score). -### 7.3 Deployment Ports +### 7.3 Draft-Editor & Sanitizer (Neu in WP10a) +Wenn der Intent `INTERVIEW` ist, rendert die UI statt einer Textblase den **Draft-Editor**. + +1. **Parsing:** Die Funktion `parse_markdown_draft` extrahiert den Codeblock aus der LLM-Antwort. +2. **Sanitization (`normalize_meta_and_body`):** + * Prüft den YAML-Frontmatter auf unerlaubte Felder (Halluzinationen des LLMs). + * Verschiebt ungültige Felder (z.B. "Situation") in den Body der Notiz. + * Stellt sicher, dass das Markdown valide bleibt. +3. **Editor Widget:** `st.text_area` erlaubt das Bearbeiten des Inhalts vor dem Speichern. +4. **Action:** Buttons zum Download oder Kopieren des fertigen Markdowns. + +### 7.4 Deployment Ports Zur sauberen Trennung von Prod und Dev laufen Frontend und Backend auf dedizierten Ports: | Umgebung | Backend (FastAPI) | Frontend (Streamlit) | diff --git a/docs/pipeline_playbook.md b/docs/pipeline_playbook.md index 1930cfe..2834ee3 100644 --- a/docs/pipeline_playbook.md +++ b/docs/pipeline_playbook.md @@ -1,14 +1,14 @@ -# mindnet v2.2 – Pipeline Playbook -**Datei:** `docs/mindnet_pipeline_playbook_v2.2.md` +# mindnet v2.4 – Pipeline Playbook +**Datei:** `docs/mindnet_pipeline_playbook_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Inkl. WP10 Frontend) -**Quellen:** `mindnet_v2_implementation_playbook.md`, `Handbuch.md`, `chunking_strategy.md`, `docs_mindnet_retriever.md`, `mindnet_admin_guide_v2.2.md`. +**Status:** **FINAL** (Inkl. WP07 Interview & WP10a Editor) +**Quellen:** `mindnet_v2_implementation_playbook.md`, `Handbuch.md`, `chunking_strategy.md`, `docs_mindnet_retriever.md`, `mindnet_admin_guide_v2.4.md`. ---
📖 Inhaltsverzeichnis (Klicken zum Öffnen) -- [mindnet v2.2 – Pipeline Playbook](#mindnet-v22--pipeline-playbook) +- [mindnet v2.4 – Pipeline Playbook](#mindnet-v24--pipeline-playbook) - [](#) - [1. Zweck \& Einordnung](#1-zweck--einordnung) - [2. Die Import-Pipeline (Runbook)](#2-die-import-pipeline-runbook) @@ -24,7 +24,7 @@ - [4.2 Typ-Defaults](#42-typ-defaults) - [5. Retriever, Chat \& Generation (RAG Pipeline)](#5-retriever-chat--generation-rag-pipeline) - [5.1 Retrieval (Hybrid)](#51-retrieval-hybrid) - - [5.2 Intent Router (WP06)](#52-intent-router-wp06) + - [5.2 Intent Router (WP06/07)](#52-intent-router-wp0607) - [5.3 Context Enrichment](#53-context-enrichment) - [5.4 Generation (LLM)](#54-generation-llm) - [6. Feedback \& Lernen (WP04c)](#6-feedback--lernen-wp04c) @@ -33,18 +33,18 @@ - [7.2 Smoke-Test (E2E)](#72-smoke-test-e2e) - [8. Ausblick \& Roadmap (Technische Skizzen)](#8-ausblick--roadmap-technische-skizzen) - [8.1 WP-08: Self-Tuning (Skizze)](#81-wp-08-self-tuning-skizze) - - [16. Workpackage Status (v2.3.2)](#16-workpackage-status-v232) + - [16. Workpackage Status (v2.4.0)](#16-workpackage-status-v240)
--- ## 1. Zweck & Einordnung -Dieses Playbook ist das zentrale operative Handbuch für die **mindnet-Pipeline**. Es beschreibt, wie Daten vom Markdown-Vault in den Wissensgraphen (Qdrant) gelangen, wie der Retriever betrieben wird und wie die **RAG-Generierung** (inkl. Decision Engine) funktioniert. +Dieses Playbook ist das zentrale operative Handbuch für die **mindnet-Pipeline**. Es beschreibt, wie Daten vom Markdown-Vault in den Wissensgraphen (Qdrant) gelangen, wie der Retriever betrieben wird und wie die **RAG-Generierung** (inkl. Decision Engine & Interviewer) funktioniert. **Zielgruppe:** Dev/Ops, Tech-Leads. **Scope:** -* **Ist-Stand (WP01–WP10):** Import, Chunking, Edge-Erzeugung, Hybrider Retriever, RAG-Chat (Hybrid Router), Feedback Loop, Frontend. +* **Ist-Stand (WP01–WP10a):** Import, Chunking, Edge-Erzeugung, Hybrider Retriever, RAG-Chat (Hybrid Router), Feedback Loop, Frontend, Draft Editor. * **Roadmap (Ausblick):** Technische Skizze für Self-Tuning (WP08). --- @@ -156,22 +156,26 @@ Der Datenfluss endet nicht beim Finden. Er geht weiter bis zur Antwort. ### 5.1 Retrieval (Hybrid) Der `/chat` Endpunkt nutzt **Hybrid Retrieval** (Semantic + Graph), um auch logisch verbundene, aber textlich unterschiedliche Notizen zu finden (z.B. Decisions zu einem Projekt). -### 5.2 Intent Router (WP06) +### 5.2 Intent Router (WP06/07) Der Request durchläuft den **Hybrid Router**: 1. **Fast Path:** Prüfung auf `trigger_keywords` aus `decision_engine.yaml`. -2. **Slow Path:** Falls kein Keyword matched und `llm_fallback_enabled=true`, klassifiziert das LLM den Intent (`DECISION`, `EMPATHY`, `FACT`, `CODING`). -3. **Result:** Auswahl der Strategie und der `inject_types` (z.B. Values & Goals). +2. **Slow Path:** Falls kein Keyword matched und `llm_fallback_enabled=true`, klassifiziert das LLM den Intent. + * `FACT`: Wissen abfragen. + * `DECISION`: Rat suchen. + * `EMPATHY`: Trost suchen. + * `INTERVIEW`: Wissen eingeben (Neu in WP07). +3. **Result:** Auswahl der Strategie und der `inject_types` oder `schemas`. ### 5.3 Context Enrichment Der Router (`chat.py`) reichert die gefundenen Chunks mit Metadaten an: * **Typ-Injection:** `[DECISION]`, `[PROJECT]`. * **Reasoning-Infos:** `(Score: 0.75)`. -* **Zweck:** Ermöglicht kleinen Modellen (Phi-3) das Erkennen von logischen Rollen ("Warum?" vs "Was?"). ### 5.4 Generation (LLM) * **Engine:** Ollama (lokal). * **Modell:** `phi3:mini` (Standard). -* **Prompting:** Template wird basierend auf Intent gewählt (`decision_template` vs `empathy_template`). +* **Prompting:** Template wird basierend auf Intent gewählt (`decision_template`, `interview_template` etc.). +* **One-Shot (WP07):** Im Interview-Modus generiert das LLM direkt einen Markdown-Block ohne Rückfragen. --- @@ -208,6 +212,9 @@ Prüft am laufenden System (Prod oder Dev), ob Semantik, Graph und Feedback funk # Decision Engine Test (WP06) python tests/test_wp06_decision.py -p 8002 -e EMPATHY -q "Alles ist grau" + # Interview Test (WP07) + python tests/test_wp06_decision.py -p 8002 -e INTERVIEW -q "Neues Projekt starten" + # Feedback Test python tests/test_feedback_smoke.py --url http://localhost:8001/query @@ -226,7 +233,7 @@ Wie entwickeln wir die Pipeline weiter? --- -## 16. Workpackage Status (v2.3.2) +## 16. Workpackage Status (v2.4.0) Aktueller Implementierungsstand der Module. @@ -240,7 +247,7 @@ Aktueller Implementierungsstand der Module. | **WP04c**| Feedback Loop | 🟢 Live | Logging (JSONL) & Traceability aktiv. | | **WP05** | Persönlichkeit / Chat | 🟢 Live | RAG-Integration mit Context Enrichment. | | **WP06** | Decision Engine | 🟢 Live | Hybrid Router, Strategic Retrieval. | -| **WP07** | Interview Assistent | 🟡 Geplant | Nächster Schritt (Dialog-Modus). | +| **WP07** | Interview Assistent | 🟢 Live | **One-Shot Extractor & Schemas aktiv.** | | **WP08** | Self-Tuning | 🔴 Geplant | Auto-Adjustment der Gewichte. | -| **WP10** | Chat Interface | 🟢 Live | Streamlit Web-UI mit Feedback & Intents. | -| **WP10a**| GUI Evolution | 🔴 Geplant | Draft-Editor & Interaktionstools. | \ No newline at end of file +| **WP10** | Chat Interface | 🟢 Live | Web-Interface (Streamlit). | +| **WP10a**| Draft Editor | 🟢 Live | **Interaktives UI für WP07 Drafts.** | \ No newline at end of file diff --git a/docs/user_guide.md b/docs/user_guide.md index 10bec25..09756d2 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -1,11 +1,11 @@ -# Mindnet v2.2 – User Guide -**Datei:** `docs/mindnet_user_guide_v2.2.md` +# Mindnet v2.4 – User Guide +**Datei:** `docs/mindnet_user_guide_v2.4.md` **Stand:** 2025-12-10 -**Status:** **FINAL** (Inkl. RAG & Web-Interface) +**Status:** **FINAL** (Inkl. RAG, Web-Interface & Interview-Assistent) **Quellen:** `knowledge_design.md`, `wp04_retriever_scoring.md`, `Programmplan_V2.2.md`, `Handbuch.md`. > **Willkommen bei Mindnet.** -> Dies ist dein persönliches Wissensnetzwerk und dein Digitaler Zwilling. Es hilft dir beim Erinnern, beim Entscheiden und beim Reflektieren. +> Dies ist dein persönliches Wissensnetzwerk und dein Digitaler Zwilling. Es hilft dir beim Erinnern, beim Entscheiden, beim Reflektieren und beim **Erstellen von Inhalten**. --- @@ -24,7 +24,7 @@ Mindnet passt seinen Charakter dynamisch an deine Frage an: 1. **Der Bibliothekar (FACT):** Liefert präzise Fakten und Definitionen. 2. **Der Berater (DECISION):** Hilft dir beim Abwägen, basierend auf deinen Werten und Zielen. 3. **Der Spiegel (EMPATHY):** Hört zu und antwortet basierend auf deinen Erfahrungen ("Ich"-Perspektive). -4. **Der Coder (CODING):** Liefert technische Schnipsel ohne Smalltalk. +4. **Der Analyst (INTERVIEW):** Hilft dir, neue Notizen zu entwerfen und strukturiert zu erfassen. --- @@ -41,7 +41,7 @@ Seit Version 2.3.1 bedienst du Mindnet über eine grafische Oberfläche im Brows * **Klick darauf:** Zeigt den Textauszug und die **Begründung** ("Warum wurde das gefunden?"). ### 2.2 Die Sidebar (Einstellungen & Verlauf) -* **Modus-Wahl:** Umschalten zwischen "💬 Chat" und "📝 Neuer Eintrag" (Vorbereitung für WP07). +* **Modus-Wahl:** Umschalten zwischen "💬 Chat" und "📝 Manueller Editor". * **Verlauf:** Die letzten Suchanfragen sind hier gelistet. Ein Klick führt die Suche erneut aus. * **Settings:** * **Top-K:** Wie viele Quellen sollen gelesen werden? (Standard: 5). @@ -68,9 +68,11 @@ Wenn du frustriert bist oder reflektieren willst, wechselt Mindnet in den "Ich"- * **Auslöser (Keywords & Semantik):** "Ich fühle mich...", "Traurig", "Gestresst", "Alles ist sinnlos", "Ich bin überfordert". * **Was passiert:** Mindnet lädt deine **Erfahrungen** (`type: experience`) und **Glaubenssätze** (`type: belief`). Es antwortet verständnisvoll und zitiert deine eigenen Lektionen. -### 3.3 Modus: Fakten (Standard) -* **Auslöser:** Alles andere. "Was ist Qdrant?", "Zusammenfassung von X". -* **Was passiert:** Standard-Suche nach Wissen ohne spezielle Filter. +### 3.3 Modus: Interview ("Der Analyst") – Neu! +Wenn du Wissen festhalten willst, statt zu suchen. + +* **Auslöser:** "Neues Projekt", "Notiz erstellen", "Ich will etwas festhalten", "Neue Entscheidung dokumentieren". +* **Was passiert:** Siehe Kapitel 6.3. --- @@ -115,4 +117,15 @@ Statt einfach nur `[[Link]]` zu schreiben, versuche zu sagen, *wie* es zusammenh ### 6.2 Typen nutzen (Wichtig!) Setze im Frontmatter deiner Notizen den richtigen Typ. Das ist der wichtigste Hebel für den Chat: * Willst du, dass Mindnet etwas als **Regel** nutzt? -> `type: principle` -* Willst du, dass Mindnet dich an eine **Lektion** erinnert? -> `type: experience` \ No newline at end of file +* Willst du, dass Mindnet dich an eine **Lektion** erinnert? -> `type: experience` + +### 6.3 Der Interview-Assistent (Drafting) +Mindnet kann dir helfen, Markdown-Notizen zu schreiben. + +1. **Start:** Schreibe im Chat: *"Ich möchte ein neues Projekt 'Apollo' starten, Ziel ist die Mondlandung."* +2. **Generierung:** Mindnet erkennt den Wunsch (`INTERVIEW`), analysiert den Satz und erstellt **sofort** einen Entwurf. +3. **Editor:** Die UI wechselt von der Chat-Blase zu einem **Draft-Editor**. + * Du siehst das generierte Frontmatter (`type: project`, `status: draft`). + * Du siehst den Body-Text mit Platzhaltern (`[TODO]`), wo Infos fehlten (z.B. Stakeholder). +4. **Finalisierung:** Ergänze die fehlenden Infos direkt im Editor und klicke auf **Download** oder **Kopieren**. +5. **Speichern:** Speichere die Datei in deinen Obsidian Vault. Beim nächsten Import ist sie im System. \ No newline at end of file