Files
breakpilot-lehrer/backend-lehrer/llm_gateway/routes/comparison.py
Benjamin Admin bd4b956e3c [split-required] Split final 43 files (500-668 LOC) to complete refactoring
klausur-service (11 files):
- cv_gutter_repair, ocr_pipeline_regression, upload_api
- ocr_pipeline_sessions, smart_spell, nru_worksheet_generator
- ocr_pipeline_overlays, mail/aggregator, zeugnis_api
- cv_syllable_detect, self_rag

backend-lehrer (17 files):
- classroom_engine/suggestions, generators/quiz_generator
- worksheets_api, llm_gateway/comparison, state_engine_api
- classroom/models (→ 4 submodules), services/file_processor
- alerts_agent/api/wizard+digests+routes, content_generators/pdf
- classroom/routes/sessions, llm_gateway/inference
- classroom_engine/analytics, auth/keycloak_auth
- alerts_agent/processing/rule_engine, ai_processor/print_versions

agent-core (5 files):
- brain/memory_store, brain/knowledge_graph, brain/context_manager
- orchestrator/supervisor, sessions/session_manager

admin-lehrer (5 components):
- GridOverlay, StepGridReview, DevOpsPipelineSidebar
- DataFlowDiagram, sbom/wizard/page

website (2 files):
- DependencyMap, lehrer/abitur-archiv

Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:41:42 +02:00

234 lines
6.7 KiB
Python

"""
LLM Comparison Route - Vergleicht Antworten verschiedener LLM Backends.
Dieses Modul ermoeglicht:
- Parallele Anfragen an OpenAI, Claude, Self-hosted+Tavily, Self-hosted+EduSearch
- Speichern von Vergleichsergebnissen fuer QA
- Parameter-Tuning fuer Self-hosted Modelle
"""
import asyncio
import logging
import uuid
from datetime import datetime, timezone
from typing import Optional
from fastapi import APIRouter, HTTPException, Depends
from ..middleware.auth import verify_api_key
from .comparison_models import (
ComparisonRequest,
LLMResponse,
ComparisonResponse,
SavedComparison,
_comparisons_store,
_system_prompts_store,
)
from .comparison_providers import (
call_openai,
call_claude,
search_tavily,
search_edusearch,
call_selfhosted_with_search,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/comparison", tags=["LLM Comparison"])
@router.post("/run", response_model=ComparisonResponse)
async def run_comparison(
request: ComparisonRequest,
_: str = Depends(verify_api_key),
):
"""
Fuehrt LLM-Vergleich durch.
Sendet den Prompt parallel an alle aktivierten Provider und
sammelt die Antworten.
"""
comparison_id = f"cmp-{uuid.uuid4().hex[:12]}"
tasks = []
system_prompt = request.system_prompt
if request.enable_openai:
tasks.append(("openai", call_openai(request.prompt, system_prompt)))
if request.enable_claude:
tasks.append(("claude", call_claude(request.prompt, system_prompt)))
if request.enable_selfhosted_tavily:
tavily_results = await search_tavily(request.prompt, request.search_results_count)
tasks.append((
"selfhosted_tavily",
call_selfhosted_with_search(
request.prompt,
system_prompt,
"tavily",
tavily_results,
request.selfhosted_model,
request.temperature,
request.top_p,
request.max_tokens,
)
))
if request.enable_selfhosted_edusearch:
edu_results = await search_edusearch(
request.prompt,
request.search_results_count,
request.edu_search_filters,
)
tasks.append((
"selfhosted_edusearch",
call_selfhosted_with_search(
request.prompt,
system_prompt,
"edusearch",
edu_results,
request.selfhosted_model,
request.temperature,
request.top_p,
request.max_tokens,
)
))
responses = []
if tasks:
results = await asyncio.gather(*[t[1] for t in tasks], return_exceptions=True)
for (name, _), result in zip(tasks, results):
if isinstance(result, Exception):
responses.append(LLMResponse(
provider=name,
model="unknown",
response="",
latency_ms=0,
error=str(result),
))
else:
responses.append(result)
return ComparisonResponse(
comparison_id=comparison_id,
prompt=request.prompt,
system_prompt=system_prompt,
responses=responses,
)
@router.post("/save/{comparison_id}")
async def save_comparison(
comparison_id: str,
comparison: ComparisonResponse,
notes: Optional[str] = None,
rating: Optional[dict] = None,
_: str = Depends(verify_api_key),
):
"""Speichert einen Vergleich fuer spaetere Analyse."""
saved = SavedComparison(
comparison_id=comparison_id,
prompt=comparison.prompt,
system_prompt=comparison.system_prompt,
responses=comparison.responses,
notes=notes,
rating=rating,
created_at=comparison.created_at,
)
_comparisons_store[comparison_id] = saved
return {"status": "saved", "comparison_id": comparison_id}
@router.get("/history")
async def get_comparison_history(
limit: int = 50,
_: str = Depends(verify_api_key),
):
"""Gibt gespeicherte Vergleiche zurueck."""
comparisons = list(_comparisons_store.values())
comparisons.sort(key=lambda x: x.created_at, reverse=True)
return {"comparisons": comparisons[:limit]}
@router.get("/history/{comparison_id}")
async def get_comparison(
comparison_id: str,
_: str = Depends(verify_api_key),
):
"""Gibt einen bestimmten Vergleich zurueck."""
if comparison_id not in _comparisons_store:
raise HTTPException(status_code=404, detail="Vergleich nicht gefunden")
return _comparisons_store[comparison_id]
# System Prompt Management
@router.get("/prompts")
async def list_system_prompts(
_: str = Depends(verify_api_key),
):
"""Listet alle gespeicherten System Prompts."""
return {"prompts": list(_system_prompts_store.values())}
@router.post("/prompts")
async def create_system_prompt(
name: str,
prompt: str,
_: str = Depends(verify_api_key),
):
"""Erstellt einen neuen System Prompt."""
prompt_id = f"sp-{uuid.uuid4().hex[:8]}"
_system_prompts_store[prompt_id] = {
"id": prompt_id,
"name": name,
"prompt": prompt,
"created_at": datetime.now(timezone.utc).isoformat(),
}
return {"status": "created", "prompt_id": prompt_id}
@router.put("/prompts/{prompt_id}")
async def update_system_prompt(
prompt_id: str,
name: str,
prompt: str,
_: str = Depends(verify_api_key),
):
"""Aktualisiert einen System Prompt."""
if prompt_id not in _system_prompts_store:
raise HTTPException(status_code=404, detail="System Prompt nicht gefunden")
_system_prompts_store[prompt_id].update({
"name": name,
"prompt": prompt,
"updated_at": datetime.now(timezone.utc).isoformat(),
})
return {"status": "updated", "prompt_id": prompt_id}
@router.delete("/prompts/{prompt_id}")
async def delete_system_prompt(
prompt_id: str,
_: str = Depends(verify_api_key),
):
"""Loescht einen System Prompt."""
if prompt_id not in _system_prompts_store:
raise HTTPException(status_code=404, detail="System Prompt nicht gefunden")
if prompt_id in ["default", "curriculum", "worksheet"]:
raise HTTPException(status_code=400, detail="Standard-Prompts koennen nicht geloescht werden")
del _system_prompts_store[prompt_id]
return {"status": "deleted", "prompt_id": prompt_id}
@router.get("/prompts/{prompt_id}")
async def get_system_prompt(
prompt_id: str,
_: str = Depends(verify_api_key),
):
"""Gibt einen System Prompt zurueck."""
if prompt_id not in _system_prompts_store:
raise HTTPException(status_code=404, detail="System Prompt nicht gefunden")
return _system_prompts_store[prompt_id]