This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/voice-service/main.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

226 lines
6.9 KiB
Python

"""
Voice Service - PersonaPlex + TaskOrchestrator Integration
Voice-First Interface fuer Breakpilot
DSGVO-konform:
- Keine Audio-Persistenz (nur RAM)
- Namespace-Verschluesselung (Key nur auf Lehrergeraet)
- TTL-basierte Auto-Loeschung
Main FastAPI Application
"""
import structlog
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import time
from typing import Dict
from config import settings
# Configure structured logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer() if not settings.is_development else structlog.dev.ConsoleRenderer(),
],
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
logger = structlog.get_logger(__name__)
# Active WebSocket connections (transient, not persisted)
active_connections: Dict[str, WebSocket] = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager."""
# Startup
logger.info(
"Starting Voice Service",
environment=settings.environment,
port=settings.port,
personaplex_enabled=settings.personaplex_enabled,
orchestrator_enabled=settings.orchestrator_enabled,
audio_persistence=settings.audio_persistence,
)
# Verify DSGVO compliance settings
if settings.audio_persistence:
logger.error("DSGVO VIOLATION: Audio persistence is enabled!")
raise RuntimeError("Audio persistence must be disabled for DSGVO compliance")
# Initialize services
from services.task_orchestrator import TaskOrchestrator
from services.encryption_service import EncryptionService
app.state.orchestrator = TaskOrchestrator()
app.state.encryption = EncryptionService()
logger.info("Voice Service initialized successfully")
yield
# Shutdown
logger.info("Shutting down Voice Service")
# Clear all active connections
for session_id in list(active_connections.keys()):
try:
await active_connections[session_id].close()
except Exception:
pass
active_connections.clear()
logger.info("Voice Service shutdown complete")
# Create FastAPI app
app = FastAPI(
title="Breakpilot Voice Service",
description="Voice-First Interface mit PersonaPlex-7B und Task-Orchestrierung",
version="1.0.0",
docs_url="/docs" if settings.is_development else None,
redoc_url="/redoc" if settings.is_development else None,
lifespan=lifespan,
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Request timing middleware
@app.middleware("http")
async def add_timing_header(request: Request, call_next):
"""Add X-Process-Time header to all responses."""
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
# Import and register routers
from api.sessions import router as sessions_router
from api.streaming import router as streaming_router
from api.tasks import router as tasks_router
from api.bqas import router as bqas_router
app.include_router(sessions_router, prefix="/api/v1/sessions", tags=["Sessions"])
app.include_router(tasks_router, prefix="/api/v1/tasks", tags=["Tasks"])
app.include_router(bqas_router, prefix="/api/v1/bqas", tags=["BQAS"])
# Note: streaming router is mounted at root level for WebSocket
app.include_router(streaming_router, tags=["Streaming"])
# Health check endpoint
@app.get("/health", tags=["System"])
async def health_check():
"""
Health check endpoint for Docker/Kubernetes probes.
Returns service status and DSGVO compliance verification.
"""
return {
"status": "healthy",
"service": "voice-service",
"version": "1.0.0",
"environment": settings.environment,
"dsgvo_compliance": {
"audio_persistence": settings.audio_persistence,
"encryption_enabled": settings.encryption_enabled,
"transcript_ttl_days": settings.transcript_ttl_days,
"audit_log_ttl_days": settings.audit_log_ttl_days,
},
"backends": {
"personaplex_enabled": settings.personaplex_enabled,
"orchestrator_enabled": settings.orchestrator_enabled,
"fallback_llm": settings.fallback_llm_provider,
},
"audio_config": {
"sample_rate": settings.audio_sample_rate,
"frame_size_ms": settings.audio_frame_size_ms,
},
"active_connections": len(active_connections),
}
# Root endpoint
@app.get("/", tags=["System"])
async def root():
"""Root endpoint with service information."""
return {
"service": "Breakpilot Voice Service",
"description": "Voice-First Interface fuer Breakpilot",
"version": "1.0.0",
"docs": "/docs" if settings.is_development else "disabled",
"endpoints": {
"sessions": "/api/v1/sessions",
"tasks": "/api/v1/tasks",
"websocket": "/ws/voice",
},
"privacy": {
"audio_stored": False,
"transcripts_encrypted": True,
"data_retention": f"{settings.transcript_ttl_days} days",
},
}
# Error handlers
@app.exception_handler(404)
async def not_found_handler(request: Request, exc):
"""Handle 404 errors - preserve HTTPException details."""
from fastapi import HTTPException
# If this is an HTTPException with a detail, use that
if isinstance(exc, HTTPException) and exc.detail:
return JSONResponse(
status_code=404,
content={"detail": exc.detail},
)
# Generic 404 for route not found
return JSONResponse(
status_code=404,
content={"error": "Not found", "path": str(request.url.path)},
)
@app.exception_handler(500)
async def internal_error_handler(request: Request, exc):
"""Handle 500 errors."""
logger.error("Internal server error", path=str(request.url.path), error=str(exc))
return JSONResponse(
status_code=500,
content={"error": "Internal server error"},
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=settings.port,
reload=settings.is_development,
)