refactor(d3): ARCH-051 — 14 dict-body-Routes auf Pydantic-Schemas umgestellt (entity_permissions bulk ×2, guests invite, users menu-order, system_settings backup-config+dsar, knowledge ×3, self_improvement ×5); DSAR-Export F821-Bug behoben (datetime/timezone undefined → NameError beim GDPR-Export), Zeitstempel auf datetime.now(UTC); Validierung jetzt im Schema statt in Routen
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-08-24 10:55:22 +02:00
parent ef90d57f0a
commit c32e4bb34e
7 changed files with 189 additions and 118 deletions
+41 -24
View File
@@ -1,51 +1,70 @@
"""Knowledge plugin routes — extraction, ask, review queue."""
from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.deps import require_permission
from app.plugins.builtins.knowledge.services import extract_knowledge, ask_knowledge, get_review_queue, review_extraction
from app.plugins.builtins.knowledge.services import (
ask_knowledge,
extract_knowledge,
get_review_queue,
review_extraction,
)
router = APIRouter(prefix="/api/v1/knowledge", tags=["knowledge"])
class KnowledgeExtractRequest(BaseModel):
"""Extract knowledge from a source."""
source_type: str = Field(..., min_length=1)
source_id: uuid.UUID
source_title: str | None = None
source_text: str = Field(..., min_length=1)
class KnowledgeAskRequest(BaseModel):
"""Ask a knowledge question."""
question: str = Field(..., min_length=1)
class KnowledgeReviewRequest(BaseModel):
"""Approve or reject a knowledge extraction."""
approved: bool = False
notes: str | None = None
@router.post("/extract")
async def extract(
body: dict,
body: KnowledgeExtractRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("wiki:read")),
):
"""Extract knowledge from a source (wiki article, dms file, mail, communication)."""
tenant_id = uuid.UUID(current_user["tenant_id"])
source_type = body.get("source_type", "")
source_id = body.get("source_id", "")
source_title = body.get("source_title")
source_text = body.get("source_text", "")
if not source_type or not source_id or not source_text:
raise HTTPException(400, detail={"detail": "source_type, source_id, source_text required", "code": "missing_fields"})
try:
sid = uuid.UUID(source_id)
except ValueError:
raise HTTPException(400, detail={"detail": "Invalid source_id", "code": "invalid_id"}) from None
result = await extract_knowledge(
db=db, tenant_id=tenant_id, source_type=source_type, source_id=sid,
source_title=source_title, source_text=source_text,
db=db, tenant_id=tenant_id, source_type=body.source_type, source_id=body.source_id,
source_title=body.source_title, source_text=body.source_text,
user_id=uuid.UUID(current_user["user_id"]) if current_user.get("user_id") else None,
)
return result
@router.post("/ask")
async def ask(
body: dict,
body: KnowledgeAskRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("wiki:read")),
):
"""Ask a knowledge question — uses wiki + graph_rag as context."""
tenant_id = uuid.UUID(current_user["tenant_id"])
question = body.get("question") or body.get("query", "")
if not question:
raise HTTPException(400, detail={"detail": "question required", "code": "missing_question"})
result = await ask_knowledge(db=db, tenant_id=tenant_id, question=question)
result = await ask_knowledge(db=db, tenant_id=tenant_id, question=body.question)
return result
@router.get("/review")
@@ -62,22 +81,20 @@ async def review_queue(
@router.post("/review/{extraction_id}")
async def review(
extraction_id: str,
body: dict,
body: KnowledgeReviewRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("wiki:write")),
):
"""Approve or reject a knowledge extraction."""
tenant_id = uuid.UUID(current_user["tenant_id"])
approved = body.get("approved", False)
notes = body.get("notes")
try:
eid = uuid.UUID(extraction_id)
except ValueError:
raise HTTPException(400, detail={"detail": "Invalid extraction_id", "code": "invalid_id"}) from None
result = await review_extraction(
db=db, tenant_id=tenant_id, extraction_id=eid, approved=approved,
db=db, tenant_id=tenant_id, extraction_id=eid, approved=body.approved,
user_id=uuid.UUID(current_user["user_id"]) if current_user.get("user_id") else None,
notes=notes,
notes=body.notes,
)
if "error" in result:
raise HTTPException(404, detail={"detail": result["error"], "code": "not_found"})