feat(1b): Automatische Notfallerkennung raus → manueller Notruf-Knopf
Die unzuverlässige/intransparente Auto-Erkennung (Stichwörter + LLM) ist
ersatzlos entfernt: emergency.py neu (nur manueller, vom Nutzer bestätigter
Notruf), llm_classifier.py gelöscht, Aufrufe in chat.py/ws.py raus,
emergency_llm_* aus der Config.
Neu: prominenter "🆘 Hilfe rufen"-Knopf (Bestätigung) → POST /api/emergency
→ protokolliert + liefert Hinweis in Nutzersprache. Eskalation (Benachrichtigung
an Angehörige) folgt später; vorerst Platzhalter "ACHTUNG: Es ist noch keine
Benachrichtigung eingebaut." (10 Sprachen). Admin-Tab "Notfälle" → "Notrufe".
Tests: test_emergency_llm.py entfernt, test_quota_safety.py auf Notruf-Endpunkt
umgeschrieben. 208 passed. Live verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
be5206c7ed
commit
3389f75af0
10 changed files with 112 additions and 433 deletions
|
|
@ -17,8 +17,8 @@ from app.dependencies import (
|
|||
)
|
||||
from app.core.memory_extractor import maybe_schedule_extraction
|
||||
from app.quota import enforce_quota, record_usage, QuotaExceededError
|
||||
from app.safety.emergency import handle_emergency, schedule_llm_emergency_check
|
||||
from app.schemas import ChatRequest
|
||||
from app.safety.emergency import record_manual_emergency
|
||||
from app.schemas import ChatRequest, EmergencyRequest
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -84,16 +84,10 @@ async def chat(
|
|||
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)
|
||||
# Stufe 2: LLM-Klassifikation als Hintergrund-Task (nur wenn Stichwoerter nichts fanden).
|
||||
schedule_llm_emergency_check(user, payload.text, store, emergency)
|
||||
|
||||
if emergency is None:
|
||||
try:
|
||||
enforce_quota(user, store)
|
||||
except QuotaExceededError as exc:
|
||||
raise HTTPException(status_code=429, detail=str(exc))
|
||||
try:
|
||||
enforce_quota(user, store)
|
||||
except QuotaExceededError as exc:
|
||||
raise HTTPException(status_code=429, detail=str(exc))
|
||||
|
||||
# Explizit angeforderte Stimme gewinnt; sonst folgt sie der Routensprache.
|
||||
voice = payload.voice or voice_for_route(route.tts_provider, route.language, route.voice_gender)
|
||||
|
|
@ -126,7 +120,6 @@ async def chat(
|
|||
"route": route.as_dict(),
|
||||
"history_len": len(conversation),
|
||||
"memories_len": len(memories),
|
||||
"emergency": emergency,
|
||||
"trace": {
|
||||
"raw_transcript": trace.raw_transcript,
|
||||
"cleaned_transcript": trace.cleaned_transcript,
|
||||
|
|
@ -148,3 +141,13 @@ async def chat(
|
|||
if emergency:
|
||||
headers["X-Emergency"] = emergency["category"]
|
||||
return StreamingResponse(BytesIO(audio), media_type="audio/pcm", headers=headers)
|
||||
|
||||
|
||||
@router.post("/emergency")
|
||||
async def trigger_emergency(payload: EmergencyRequest, user: User = Depends(require_user)):
|
||||
"""Vom Nutzer bestätigter Notruf. Protokolliert + liefert den Hinweis (in Nutzersprache).
|
||||
|
||||
Die eigentliche Benachrichtigung von Angehörigen folgt später.
|
||||
"""
|
||||
store = get_store()
|
||||
return record_manual_emergency(user, store, payload.language)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ from app.audio.vad import EnergyVAD
|
|||
from app.core.memory_extractor import maybe_schedule_extraction
|
||||
from app.quota import enforce_quota, record_usage, QuotaExceededError
|
||||
from app.auth import authenticate
|
||||
from app.safety.emergency import handle_emergency, schedule_llm_emergency_check
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -86,30 +85,11 @@ async def _run_turn(
|
|||
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)
|
||||
|
||||
async def _on_llm_emergency(category):
|
||||
try:
|
||||
await websocket.send_json(
|
||||
{"type": "emergency", "category": category, "source": "llm"}
|
||||
)
|
||||
except Exception: # Socket evtl. geschlossen -> ignorieren
|
||||
pass
|
||||
|
||||
# Stufe 2: LLM-Klassifikation als Hintergrund-Task (nur wenn Stichwoerter nichts fanden).
|
||||
schedule_llm_emergency_check(
|
||||
user, text, store, emergency, on_emergency=_on_llm_emergency
|
||||
)
|
||||
|
||||
if emergency:
|
||||
await websocket.send_json({"type": "emergency", "category": emergency["category"]})
|
||||
else:
|
||||
try:
|
||||
enforce_quota(user, store)
|
||||
except QuotaExceededError as exc:
|
||||
await websocket.send_json({"type": "error", "status": 429, "detail": str(exc)})
|
||||
return
|
||||
try:
|
||||
enforce_quota(user, store)
|
||||
except QuotaExceededError as exc:
|
||||
await websocket.send_json({"type": "error", "status": 429, "detail": str(exc)})
|
||||
return
|
||||
|
||||
await websocket.send_json({"type": "ack", "route": route.as_dict()})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue