""" 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)