Files
ai_agent/backend/app/api/routes/v1/health.py
T
furyhawk 8351e73d39 feat: add Zustand stores for conversation, file preview, sidebar, theme, and knowledge base selection
- Implemented `conversation-store` for managing conversations and messages.
- Created `file-preview-store` to handle file preview state.
- Added `sidebar-store` for sidebar visibility management.
- Developed `theme-store` for theme persistence and management.
- Introduced `kb-selection-store` for managing active knowledge base selections with persistence.

chore: define API and chat types

- Added types for API responses, authentication, chat messages, conversations, and projects.
- Defined interfaces for various entities including users, sessions, and message ratings.

build: configure TypeScript and testing setup

- Set up `tsconfig.json` for TypeScript configuration.
- Created `vitest.config.ts` for testing configuration with Vitest.
- Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries.
- Configured Vercel deployment settings in `vercel.json`.
2026-06-11 16:54:43 +08:00

187 lines
5.7 KiB
Python

"""Health check endpoints.
Provides Kubernetes-compatible health check endpoints:
- /health - Simple liveness check
- /health/live - Detailed liveness probe
- /health/ready - Readiness probe with dependency checks
"""
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from sqlalchemy import text
from app.api.deps import DBSession, Redis
from app.core.config import settings
from app.services.health import build_health_response
router = APIRouter()
@router.get("/health")
async def health_check() -> dict[str, Any]:
"""Simple liveness probe - check if application is running.
This is a lightweight check that should always succeed if the
application is running. Use this for basic connectivity tests.
Returns:
{"status": "healthy"}
"""
return {
"status": "healthy",
"max_upload_size_mb": settings.MAX_UPLOAD_SIZE_MB,
}
@router.get("/health/live")
async def liveness_probe() -> dict[str, Any]:
"""Detailed liveness probe for Kubernetes.
This endpoint is designed for Kubernetes liveness probes.
It checks if the application process is alive and responding.
Failure indicates the container should be restarted.
Returns:
Structured response with timestamp and service info.
"""
return build_health_response(
status="alive",
details={
"version": getattr(settings, "VERSION", "1.0.0"),
"environment": settings.ENVIRONMENT,
},
)
@router.get("/health/ready", response_model=None)
async def readiness_probe(
db: DBSession,
redis: Redis,
) -> dict[str, Any] | JSONResponse:
"""Readiness probe for Kubernetes.
This endpoint checks if all dependencies are ready to handle traffic.
It verifies database connections, Redis, and other critical services.
Failure indicates traffic should be temporarily diverted.
Checks performed:
- Database connectivity
- Redis connectivity
Returns:
Structured response with individual check results.
Returns 503 if any critical check fails.
"""
checks: dict[str, dict[str, Any]] = {}
# Database check
try:
start = datetime.now(UTC)
await db.execute(text("SELECT 1"))
latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
checks["database"] = {
"status": "healthy",
"latency_ms": round(latency_ms, 2),
"type": "postgresql",
}
except Exception as e:
checks["database"] = {
"status": "unhealthy",
"error": str(e),
"type": "postgresql",
}
# Redis check
try:
start = datetime.now(UTC)
is_healthy = await redis.ping()
latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
if is_healthy:
checks["redis"] = {
"status": "healthy",
"latency_ms": round(latency_ms, 2),
}
else:
checks["redis"] = {
"status": "unhealthy",
"error": "Ping failed",
}
except Exception as e:
checks["redis"] = {
"status": "unhealthy",
"error": str(e),
}
# Vector store — Milvus connectivity probe (TCP).
try:
import socket
start = datetime.now(UTC)
with socket.create_connection((settings.MILVUS_HOST, settings.MILVUS_PORT), timeout=2):
pass
latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
checks["vector_store"] = {
"status": "healthy",
"latency_ms": round(latency_ms, 2),
"type": "milvus",
}
except Exception as e:
checks["vector_store"] = {
"status": "unhealthy",
"error": str(e),
"type": "milvus",
}
# LLM provider — config-only check (avoid spending money on a probe call).
llm_provider = (getattr(settings, "LLM_PROVIDER", None) or "").lower()
if llm_provider:
key_field = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
"openrouter": "OPENROUTER_API_KEY",
}.get(llm_provider)
api_key = getattr(settings, key_field, None) if key_field else None
checks["llm"] = {
"status": "healthy" if api_key else "unhealthy",
"provider": llm_provider,
"detail": "API key configured" if api_key else "API key missing",
}
else:
checks["llm"] = {"status": "unknown", "detail": "not configured"}
# Determine overall health — only db + redis are critical for readiness.
critical = {k: v for k, v in checks.items() if k in ("database", "redis")}
all_healthy = (
all(check.get("status") == "healthy" for check in critical.values()) if critical else True
)
# The admin /system page reads each service from the top level, so flatten
# the checks alongside the structured `checks` field for K8s probes.
response_data = build_health_response(
status="ready" if all_healthy else "not_ready",
checks=checks,
)
response_data.update(checks)
if not all_healthy:
return JSONResponse(status_code=503, content=response_data)
return response_data
# Backward compatibility - keep /ready endpoint
@router.get("/ready", response_model=None)
async def readiness_check(
db: DBSession,
redis: Redis,
) -> dict[str, Any] | JSONResponse:
"""Readiness check (alias for /health/ready).
Deprecated: Use /health/ready instead.
"""
return await readiness_probe(
db=db,
redis=redis,
)