feat: add aiprovider service foundation
This commit is contained in:
109
backend/app/services/ai_client.py
Normal file
109
backend/app/services/ai_client.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from app.core.config import settings
|
||||
from app.schemas.ai import (
|
||||
AIProviderStatusResponse,
|
||||
SituationalAnalysisRequest,
|
||||
SituationalAnalysisResponse,
|
||||
)
|
||||
|
||||
|
||||
class AIProviderClient:
|
||||
def __init__(self) -> None:
|
||||
self.service_url = settings.AI_PROVIDER_SERVICE_URL.rstrip("/")
|
||||
self.service_token = settings.AI_PROVIDER_SERVICE_TOKEN
|
||||
self.timeout = settings.AI_PROVIDER_TIMEOUT_SECONDS
|
||||
self.retry_attempts = max(settings.AI_PROVIDER_RETRY_ATTEMPTS, 1)
|
||||
|
||||
def _headers(self, request_id: str | None = None) -> dict[str, str]:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if self.service_token:
|
||||
headers["X-Provider-Token"] = self.service_token
|
||||
if request_id:
|
||||
headers["X-Request-ID"] = request_id
|
||||
return headers
|
||||
|
||||
async def get_status(self, request_id: str | None = None) -> AIProviderStatusResponse:
|
||||
if not self.service_url:
|
||||
return AIProviderStatusResponse(
|
||||
provider="unconfigured",
|
||||
enabled=False,
|
||||
configured=False,
|
||||
model=None,
|
||||
base_url=None,
|
||||
)
|
||||
|
||||
data = await self._request("GET", "/v1/provider/status", request_id=request_id)
|
||||
return AIProviderStatusResponse.model_validate(data)
|
||||
|
||||
async def analyze(
|
||||
self,
|
||||
payload: SituationalAnalysisRequest,
|
||||
request_id: str | None = None,
|
||||
) -> SituationalAnalysisResponse:
|
||||
if not self.service_url:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="AI provider service URL is not configured.",
|
||||
)
|
||||
|
||||
data = await self._request(
|
||||
"POST",
|
||||
"/v1/analyze",
|
||||
json=payload.model_dump(),
|
||||
request_id=request_id,
|
||||
)
|
||||
return SituationalAnalysisResponse.model_validate(data)
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
json: dict | None = None,
|
||||
request_id: str | None = None,
|
||||
) -> dict:
|
||||
last_error: Exception | None = None
|
||||
for attempt in range(1, self.retry_attempts + 1):
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.request(
|
||||
method,
|
||||
f"{self.service_url}{path}",
|
||||
headers=self._headers(request_id),
|
||||
json=json,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
last_error = exc
|
||||
if attempt < self.retry_attempts and exc.response.status_code >= 500:
|
||||
await asyncio.sleep(0.3 * attempt)
|
||||
continue
|
||||
detail = exc.response.text or "AI provider service returned an error"
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"AI provider service request failed: {detail}",
|
||||
) from exc
|
||||
except httpx.HTTPError as exc:
|
||||
last_error = exc
|
||||
if attempt < self.retry_attempts:
|
||||
await asyncio.sleep(0.3 * attempt)
|
||||
continue
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"Failed to reach AI provider service: {exc}",
|
||||
) from exc
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"AI provider service request failed: {last_error}",
|
||||
)
|
||||
|
||||
|
||||
def get_ai_provider_client() -> AIProviderClient:
|
||||
return AIProviderClient()
|
||||
@@ -19,6 +19,10 @@ ALLOWED_ACTIONS: dict[str, dict[str, Any]] = {
|
||||
"command": ["./planet.sh", "restart", "-b"],
|
||||
"recovery_mode": "backend",
|
||||
},
|
||||
"restart-ai-provider": {
|
||||
"command": ["./planet.sh", "restart", "-a"],
|
||||
"recovery_mode": "ai-provider",
|
||||
},
|
||||
"restart-database": {
|
||||
"command": ["./planet.sh", "restart", "-d"],
|
||||
"recovery_mode": "database",
|
||||
|
||||
Reference in New Issue
Block a user