fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
174
backend/llm_gateway/routes/tools.py
Normal file
174
backend/llm_gateway/routes/tools.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user