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>
175 lines
4.7 KiB
Python
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)
|