Files
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

175 lines
4.7 KiB
Python

"""
Tool Routes für LLM Gateway.
Bietet API-Endpoints für externe Tools wie Web Search.
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from ..middleware.auth import verify_api_key
from ..services.tool_gateway import (
ToolGateway,
get_tool_gateway,
SearchDepth,
TavilyError,
ToolGatewayError,
)
router = APIRouter()
# Request/Response Models
class SearchRequest(BaseModel):
"""Request für Web-Suche."""
query: str = Field(..., min_length=1, max_length=1000, description="Suchanfrage")
search_depth: Optional[SearchDepth] = Field(
default=None,
description="Suchtiefe: basic (schnell) oder advanced (gründlich)",
)
max_results: Optional[int] = Field(
default=None,
ge=1,
le=20,
description="Maximale Anzahl Ergebnisse (1-20)",
)
include_domains: Optional[list[str]] = Field(
default=None,
description="Nur diese Domains durchsuchen",
)
exclude_domains: Optional[list[str]] = Field(
default=None,
description="Diese Domains ausschließen",
)
class SearchResultItem(BaseModel):
"""Ein Suchergebnis."""
title: str
url: str
content: str
score: float
published_date: Optional[str] = None
class SearchResponse(BaseModel):
"""Response für Web-Suche."""
query: str
redacted_query: Optional[str] = Field(
default=None,
description="Redaktierte Query (nur wenn PII gefunden)",
)
results: list[SearchResultItem]
answer: Optional[str] = Field(
default=None,
description="KI-generierte Zusammenfassung der Ergebnisse",
)
pii_detected: bool = Field(
default=False,
description="True wenn PII in der Anfrage erkannt und redaktiert wurde",
)
pii_types: list[str] = Field(
default_factory=list,
description="Liste der erkannten PII-Typen",
)
response_time_ms: int = Field(
default=0,
description="Antwortzeit in Millisekunden",
)
class ToolsHealthResponse(BaseModel):
"""Health-Status der Tools."""
tavily: dict
pii_redaction: dict
@router.post("/search", response_model=SearchResponse)
async def web_search(
request: SearchRequest,
_: str = Depends(verify_api_key),
tool_gateway: ToolGateway = Depends(get_tool_gateway),
):
"""
Führt eine Web-Suche durch.
Die Suchanfrage wird automatisch auf personenbezogene Daten (PII)
geprüft. Gefundene PII werden vor dem Versand an den Suchdienst
redaktiert, um DSGVO-Konformität zu gewährleisten.
**PII-Erkennung umfasst:**
- E-Mail-Adressen
- Telefonnummern
- IBAN/Bankkonten
- Kreditkartennummern
- Sozialversicherungsnummern
- IP-Adressen
- Geburtsdaten
**Beispiel:**
```
POST /llm/tools/search
{
"query": "Schulrecht Bayern Datenschutz",
"max_results": 5
}
```
"""
try:
result = await tool_gateway.search(
query=request.query,
search_depth=request.search_depth,
max_results=request.max_results,
include_domains=request.include_domains,
exclude_domains=request.exclude_domains,
)
return SearchResponse(
query=result.query,
redacted_query=result.redacted_query,
results=[
SearchResultItem(
title=r.title,
url=r.url,
content=r.content,
score=r.score,
published_date=r.published_date,
)
for r in result.results
],
answer=result.answer,
pii_detected=result.pii_detected,
pii_types=result.pii_types,
response_time_ms=result.response_time_ms,
)
except TavilyError as e:
# TavilyError first (more specific, inherits from ToolGatewayError)
raise HTTPException(
status_code=502,
detail=f"Search service error: {e}",
)
except ToolGatewayError as e:
raise HTTPException(
status_code=503,
detail=f"Tool service unavailable: {e}",
)
@router.get("/health", response_model=ToolsHealthResponse)
async def tools_health(
_: str = Depends(verify_api_key),
tool_gateway: ToolGateway = Depends(get_tool_gateway),
):
"""
Prüft den Gesundheitsstatus der Tool-Services.
Gibt Status für jeden konfigurierten Tool-Service zurück:
- Tavily: Web-Suche
- PII Redaction: Datenschutz-Filter
"""
status = await tool_gateway.health_check()
return ToolsHealthResponse(**status)