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>
234 lines
6.7 KiB
Python
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]
|