feat(users): SSO-Nutzer-Personalisierung + Admin-Endpunkte
Hintergrund: SSO-Nutzer (va.linix.de) werden bereits beim ersten Besuch
automatisch registriert, hatten aber keinen echten Namen im LLM-Kontext
und konnten vom Admin nicht vorbereitet werden.
Änderungen:
- ws.py + chat.py: Nutzeridentität (display_name + Erinnerungen) wird als
führende System-Message bei jeder Anfrage injiziert; für anonyme
Dev-Nutzer (AUTH_ENABLED=false) wird diese Injection übersprungen
- store.py: update_display_name() im ABC und SQLiteStore
- schemas.py: UserUpdate (display_name)
- admin.py:
- PUT /api/admin/users/{id}: Anzeigenamen eines SSO-Nutzers setzen
- POST /api/admin/users/{id}/memories: initiale Erinnerungen vorbelegen
- BEDIENUNGSANLEITUNG §7.2: neuer Abschnitt "SSO-Nutzer — automatische
Registrierung" mit vollständigem Workflow; §7.3/7.4 neu nummeriert;
Anhang B.5 mit neuen Endpunkten ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b76ec76c45
commit
68b02f42a1
6 changed files with 157 additions and 14 deletions
|
|
@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
|||
from app.auth import is_admin_user, require_admin, require_admin_or_user
|
||||
from app.config import settings
|
||||
from app.dependencies import get_store
|
||||
from app.schemas import UserCreate, UserCreated
|
||||
from app.schemas import MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -70,6 +70,25 @@ async def reset_token(user_id: str):
|
|||
return UserCreated(user_id=user.id, display_name=user.display_name, token=token)
|
||||
|
||||
|
||||
@router.put("/admin/users/{user_id}", dependencies=[Depends(require_admin)])
|
||||
async def update_user(user_id: str, payload: UserUpdate):
|
||||
"""Aktualisiert den Anzeigenamen eines Nutzers (z. B. nach erstem SSO-Login)."""
|
||||
user = get_store().update_display_name(user_id, payload.display_name)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
|
||||
return {"user_id": user.id, "display_name": user.display_name}
|
||||
|
||||
|
||||
@router.post("/admin/users/{user_id}/memories", response_model=MemoryOut, dependencies=[Depends(require_admin)])
|
||||
async def add_user_memory(user_id: str, payload: MemoryCreate):
|
||||
"""Legt eine Erinnerung fuer einen Nutzer an (Admin kann Kontext vorbelegen)."""
|
||||
store = get_store()
|
||||
if store.get_user(user_id) is None:
|
||||
raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
|
||||
memory = store.add_memory(user_id, payload.content)
|
||||
return MemoryOut(id=memory.id, content=memory.content, created_at=memory.created_at)
|
||||
|
||||
|
||||
@router.get("/admin/users", dependencies=[Depends(require_admin_or_user)])
|
||||
async def list_users():
|
||||
"""Listet die Nutzer (ohne Secrets). Fuer Admins (SSO/ADMIN_USERS) oder ADMIN_API_KEY."""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
|
|||
from app.config import settings
|
||||
from app.errors import RoutingError
|
||||
from app.auth import require_user
|
||||
from app.store import User, SessionOwnershipError
|
||||
from app.store import ANONYMOUS_USER_ID, User, SessionOwnershipError
|
||||
from app.dependencies import (
|
||||
resolve_route,
|
||||
build_orchestrator,
|
||||
|
|
@ -75,11 +75,15 @@ async def chat(
|
|||
raise HTTPException(status_code=422, detail=str(exc))
|
||||
|
||||
llm_context = list(conversation)
|
||||
user_context_parts = []
|
||||
if user.id != ANONYMOUS_USER_ID:
|
||||
user_context_parts.append(f"Du sprichst mit {user.display_name}.")
|
||||
if memories:
|
||||
memory_text = "Was du ueber den Nutzer weisst:\n" + "\n".join(
|
||||
f"- {m.content}" for m in memories
|
||||
user_context_parts.append(
|
||||
"Was du ueber den Nutzer weisst:\n" + "\n".join(f"- {m.content}" for m in memories)
|
||||
)
|
||||
llm_context = [{"role": "system", "content": memory_text}] + llm_context
|
||||
if user_context_parts:
|
||||
llm_context = [{"role": "system", "content": "\n".join(user_context_parts)}] + llm_context
|
||||
|
||||
# Notfall-Erkennung zuerst (immer eskalieren, auch bei Quota-Limit).
|
||||
emergency = handle_emergency(user, payload.text, store)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from app.dependencies import (
|
|||
build_orchestrator,
|
||||
resolve_output_endpoint,
|
||||
)
|
||||
from app.store import SessionOwnershipError
|
||||
from app.store import ANONYMOUS_USER_ID, SessionOwnershipError
|
||||
from app.audio.vad import EnergyVAD
|
||||
from app.core.memory_extractor import maybe_schedule_extraction
|
||||
from app.quota import enforce_quota, record_usage, QuotaExceededError
|
||||
|
|
@ -71,11 +71,15 @@ async def _run_turn(websocket, store, user, session_id, route, orchestrator, out
|
|||
)
|
||||
memories = store.get_memories(user.id)
|
||||
llm_context = list(conversation)
|
||||
user_context_parts = []
|
||||
if user.id != ANONYMOUS_USER_ID:
|
||||
user_context_parts.append(f"Du sprichst mit {user.display_name}.")
|
||||
if memories:
|
||||
memory_text = "Was du ueber den Nutzer weisst:\n" + "\n".join(
|
||||
f"- {m.content}" for m in memories
|
||||
user_context_parts.append(
|
||||
"Was du ueber den Nutzer weisst:\n" + "\n".join(f"- {m.content}" for m in memories)
|
||||
)
|
||||
llm_context = [{"role": "system", "content": memory_text}] + llm_context
|
||||
if user_context_parts:
|
||||
llm_context = [{"role": "system", "content": "\n".join(user_context_parts)}] + llm_context
|
||||
|
||||
# Notfall-Erkennung zuerst (immer eskalieren, auch bei Quota-Limit).
|
||||
emergency = handle_emergency(user, text, store)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue