ai_chat_api / routers /vision_router.py
Soumik Bose
ok
a243ca8
from pathlib import Path
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import JSONResponse
from config import config
from logging_config import get_logger
from models.schemas import ServiceStatusResponse
from services.vision_service import vision_service
logger = get_logger("routers.vision_router")
router = APIRouter(prefix="/v1/vision", tags=["Vision AI"])
@router.post("/analyze")
async def analyze_image(
image: UploadFile = File(..., description="Image file to analyze"),
prompt: str = Form(..., description="Question or instruction about the image"),
temperature: float = Form(0.6, ge=0.0, le=2.0, description="Sampling temperature"),
max_tokens: int = Form(512, ge=1, le=4096, description="Maximum tokens to generate"),
return_json: bool = Form(False, description="Return structured JSON output"),
) -> JSONResponse:
logger.info(
"Received vision analysis request: filename='%s', prompt_preview='%s...', "
"return_json=%s",
image.filename,
prompt[:60],
return_json,
)
if not vision_service.is_ready():
logger.warning("Vision analysis request rejected: vision model not ready")
raise HTTPException(status_code=503, detail="Vision model is not ready")
_validate_image_extension(image.filename)
image_data = await image.read()
logger.info("Image data read: %d bytes", len(image_data))
_validate_file_size(len(image_data))
try:
result = await vision_service.analyze_image(
image_data=image_data,
prompt=prompt,
temperature=temperature,
max_tokens=max_tokens,
return_json=return_json,
)
logger.info("Vision analysis request fulfilled successfully")
return JSONResponse(content=result)
except HTTPException:
raise
except RuntimeError as exc:
logger.error("Runtime error during image analysis: %s", exc)
raise HTTPException(status_code=503, detail=str(exc)) from exc
except Exception as exc:
logger.exception("Unexpected error during image analysis")
raise HTTPException(status_code=500, detail="Internal server error") from exc
def _validate_image_extension(filename: str) -> None:
extension = Path(filename).suffix.lower()
if extension not in config.ALLOWED_IMAGE_EXTENSIONS:
allowed = ", ".join(sorted(config.ALLOWED_IMAGE_EXTENSIONS))
logger.warning(
"Rejected file upload with unsupported extension '%s'. Allowed: %s",
extension,
allowed,
)
raise HTTPException(
status_code=400,
detail=f"Unsupported file type '{extension}'. Allowed extensions: {allowed}",
)
def _validate_file_size(size_bytes: int) -> None:
if size_bytes > config.MAX_FILE_SIZE_BYTES:
max_mb = config.MAX_FILE_SIZE_BYTES / (1024 * 1024)
logger.warning(
"Rejected file upload: size %d bytes exceeds limit of %.0f MB",
size_bytes,
max_mb,
)
raise HTTPException(
status_code=400,
detail=f"File size exceeds the maximum allowed limit of {max_mb:.0f} MB",
)
@router.get("/health", response_model=ServiceStatusResponse)
async def vision_health() -> ServiceStatusResponse:
ready = vision_service.is_ready()
logger.debug("Vision health check: ready=%s", ready)
return ServiceStatusResponse(
status="healthy" if ready else "initializing",
model_ready=ready,
)