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 Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +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,
)