WP11 #8
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
app/routers/ingest.py
|
app/routers/ingest.py
|
||||||
API-Endpunkte für WP-11 (Discovery & Persistence).
|
API-Endpunkte für WP-11 (Discovery & Persistence).
|
||||||
Robustified for Frontend Integration.
|
Fixed Async/Await Issues.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
@ -11,14 +11,15 @@ from pydantic import BaseModel
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
from app.core.ingestion import IngestionService
|
from app.core.ingestion import IngestionService
|
||||||
from app.services.discovery import DiscoveryService
|
from app.core.retriever import Retriever
|
||||||
|
from app.models.dto import QueryRequest
|
||||||
|
|
||||||
# Logger für Backend-Debugging aktivieren
|
# Logger Konfiguration
|
||||||
logger = logging.getLogger("uvicorn.error")
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# --- DTOs (Data Transfer Objects) ---
|
# --- DTOs ---
|
||||||
|
|
||||||
class AnalyzeRequest(BaseModel):
|
class AnalyzeRequest(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
|
|
@ -27,7 +28,7 @@ class AnalyzeRequest(BaseModel):
|
||||||
class SaveRequest(BaseModel):
|
class SaveRequest(BaseModel):
|
||||||
markdown_content: str
|
markdown_content: str
|
||||||
filename: Optional[str] = None
|
filename: Optional[str] = None
|
||||||
folder: str = "00_Inbox" # Standard-Ordner
|
folder: str = "00_Inbox"
|
||||||
|
|
||||||
class SaveResponse(BaseModel):
|
class SaveResponse(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
|
|
@ -35,18 +36,52 @@ class SaveResponse(BaseModel):
|
||||||
note_id: str
|
note_id: str
|
||||||
stats: Dict[str, Any]
|
stats: Dict[str, Any]
|
||||||
|
|
||||||
# --- Services ---
|
# --- Endpoints ---
|
||||||
discovery_service = DiscoveryService()
|
|
||||||
|
|
||||||
@router.post("/analyze")
|
@router.post("/analyze")
|
||||||
async def analyze_draft(req: AnalyzeRequest):
|
async def analyze_draft(req: AnalyzeRequest):
|
||||||
"""
|
"""
|
||||||
WP-11 Intelligence: Liefert Link-Vorschläge basierend auf Text und Typ.
|
WP-11 Intelligence: Liefert Link-Vorschläge via Retriever.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Prio 2: Intelligence Service aufrufen
|
# Wir nutzen den Retriever direkt, statt eines extra DiscoveryServices
|
||||||
result = await discovery_service.analyze_draft(req.text, req.type)
|
retriever = Retriever()
|
||||||
return result
|
suggestions = []
|
||||||
|
|
||||||
|
# 1. Suche nach ähnlichen Inhalten (Semantic)
|
||||||
|
query_text = req.text[:400]
|
||||||
|
if not query_text.strip():
|
||||||
|
return {"suggestions": []}
|
||||||
|
|
||||||
|
# Check ob Retriever async ist (in v2.3 oft ja)
|
||||||
|
if hasattr(retriever.search, '__await__'):
|
||||||
|
hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid"))
|
||||||
|
else:
|
||||||
|
# Fallback sync
|
||||||
|
hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid"))
|
||||||
|
|
||||||
|
seen_titles = set()
|
||||||
|
for hit in hits_result.results:
|
||||||
|
title = hit.payload.get("note_id") or hit.node_id
|
||||||
|
if not title or title in seen_titles: continue
|
||||||
|
seen_titles.add(title)
|
||||||
|
|
||||||
|
# Simple Edge Logic
|
||||||
|
edge_kind = "related_to"
|
||||||
|
if req.type == "project": edge_kind = "depends_on"
|
||||||
|
if req.type == "decision": edge_kind = "references"
|
||||||
|
|
||||||
|
if hit.total_score > 0.65:
|
||||||
|
suggestions.append({
|
||||||
|
"target_title": title,
|
||||||
|
"target_id": hit.node_id,
|
||||||
|
"suggested_markdown": f"[[rel:{edge_kind} {title}]]",
|
||||||
|
"reason": f"Semantisch ähnlich ({hit.total_score:.2f})",
|
||||||
|
"type": "semantic"
|
||||||
|
})
|
||||||
|
|
||||||
|
return {"suggestions": suggestions}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Analyze failed: {e}", exc_info=True)
|
logger.error(f"Analyze failed: {e}", exc_info=True)
|
||||||
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
||||||
|
|
@ -57,41 +92,40 @@ async def save_note(req: SaveRequest):
|
||||||
WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort.
|
WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. Vault Root sicher ermitteln
|
# 1. Pfad-Setup
|
||||||
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
||||||
|
|
||||||
# Absolute Pfade auflösen, um CWD-Probleme zu vermeiden
|
|
||||||
abs_vault_root = os.path.abspath(vault_root)
|
abs_vault_root = os.path.abspath(vault_root)
|
||||||
|
|
||||||
if not os.path.exists(abs_vault_root):
|
if not os.path.exists(abs_vault_root):
|
||||||
error_msg = f"Vault root not found at: {abs_vault_root}. Check MINDNET_VAULT_ROOT in .env"
|
# Versuche ihn zu erstellen, falls er fehlt
|
||||||
logger.error(error_msg)
|
os.makedirs(abs_vault_root, exist_ok=True)
|
||||||
raise HTTPException(status_code=500, detail=error_msg)
|
|
||||||
|
|
||||||
# 2. Filename Fallback
|
# 2. Filename
|
||||||
final_filename = req.filename
|
final_filename = req.filename
|
||||||
if not final_filename:
|
if not final_filename:
|
||||||
final_filename = f"draft_{int(time.time())}.md"
|
final_filename = f"draft_{int(time.time())}.md"
|
||||||
|
|
||||||
# 3. Ingestion Service aufrufen
|
# 3. Ingestion Service
|
||||||
ingest_service = IngestionService()
|
ingest_service = IngestionService()
|
||||||
|
|
||||||
logger.info(f"Attempting to save {final_filename} to {req.folder} in {abs_vault_root}")
|
logger.info(f"Saving {final_filename} to {req.folder}")
|
||||||
|
|
||||||
result = ingest_service.create_from_text(
|
# --- DER FIX: AWAIT HINZUFÜGEN ---
|
||||||
|
# Da ingestion.py auf async umgestellt wurde, MÜSSEN wir hier warten.
|
||||||
|
result = await ingest_service.create_from_text(
|
||||||
markdown_content=req.markdown_content,
|
markdown_content=req.markdown_content,
|
||||||
filename=final_filename,
|
filename=final_filename,
|
||||||
vault_root=abs_vault_root,
|
vault_root=abs_vault_root,
|
||||||
folder=req.folder
|
folder=req.folder
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fehler vom Service abfangen
|
# Fehlerprüfung auf dem Dictionary
|
||||||
if result.get("status") == "error":
|
if result.get("status") == "error":
|
||||||
raise HTTPException(status_code=500, detail=result.get("error"))
|
raise HTTPException(status_code=500, detail=result.get("error"))
|
||||||
|
|
||||||
return SaveResponse(
|
return SaveResponse(
|
||||||
status="success",
|
status="success",
|
||||||
file_path=result["path"],
|
file_path=result.get("path", "unknown"),
|
||||||
note_id=result.get("note_id", "unknown"),
|
note_id=result.get("note_id", "unknown"),
|
||||||
stats={
|
stats={
|
||||||
"chunks": result.get("chunks_count", 0),
|
"chunks": result.get("chunks_count", 0),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user