feat(status): add /health aggregate endpoint and 10s TTL cache on /stats
This commit is contained in:
@@ -1,32 +1,53 @@
|
|||||||
"""Define API routes for status."""
|
"""Define API routes for status."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.config.settings import settings
|
from app.config.settings import settings
|
||||||
from app.shared.bootstrap import get_document_query_service, get_vector_index
|
from app.shared.bootstrap import (
|
||||||
# Keep route handlers close to their transport-layer wiring for easier auditing.
|
get_bm25_retriever,
|
||||||
|
get_binary_store,
|
||||||
|
get_conversation_store,
|
||||||
|
get_document_query_service,
|
||||||
|
get_vector_index,
|
||||||
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/status", tags=["系统状态"])
|
router = APIRouter(prefix="/status", tags=["系统状态"])
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Simple TTL cache for /stats (avoids O(N) doc scan on every request)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
_stats_cache: dict[str, Any] = {}
|
||||||
|
_stats_cache_time: float = 0.0
|
||||||
|
_STATS_TTL_SECONDS: float = 10.0
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stats")
|
@router.get("/stats")
|
||||||
async def get_stats():
|
async def get_stats():
|
||||||
"""Return stats."""
|
"""Return document statistics (cached for 10 s)."""
|
||||||
|
global _stats_cache, _stats_cache_time
|
||||||
|
now = time.time()
|
||||||
|
if _stats_cache and (now - _stats_cache_time) < _STATS_TTL_SECONDS:
|
||||||
|
return _stats_cache
|
||||||
|
|
||||||
documents = get_document_query_service().list_documents()
|
documents = get_document_query_service().list_documents()
|
||||||
indexed = sum(1 for item in documents if item.status.value == "indexed")
|
indexed = sum(1 for d in documents if d.status.value == "indexed")
|
||||||
failed = sum(1 for item in documents if item.status.value == "failed")
|
failed = sum(1 for d in documents if d.status.value == "failed")
|
||||||
return {
|
_stats_cache = {
|
||||||
"documents_total": len(documents),
|
"documents_total": len(documents),
|
||||||
"documents_indexed": indexed,
|
"documents_indexed": indexed,
|
||||||
"documents_failed": failed,
|
"documents_failed": failed,
|
||||||
"chunks_total": sum(item.chunk_count for item in documents),
|
"chunks_total": sum(d.chunk_count for d in documents),
|
||||||
}
|
}
|
||||||
|
_stats_cache_time = now
|
||||||
|
return _stats_cache
|
||||||
|
|
||||||
|
|
||||||
@router.get("/config")
|
@router.get("/config")
|
||||||
async def get_config():
|
async def get_config():
|
||||||
"""Return config."""
|
"""Return system configuration."""
|
||||||
return {
|
return {
|
||||||
"embedding_model": settings.embedding_model,
|
"embedding_model": settings.embedding_model,
|
||||||
"embedding_dim": settings.embedding_dim,
|
"embedding_dim": settings.embedding_dim,
|
||||||
@@ -44,5 +65,49 @@ async def get_config():
|
|||||||
|
|
||||||
@router.get("/milvus/health")
|
@router.get("/milvus/health")
|
||||||
async def milvus_health():
|
async def milvus_health():
|
||||||
"""Handle milvus health."""
|
"""Return Milvus health (kept for backwards compat)."""
|
||||||
return get_vector_index().health()
|
return get_vector_index().health()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/health")
|
||||||
|
async def get_health():
|
||||||
|
"""Return aggregate health of all backend services."""
|
||||||
|
# --- Milvus ---
|
||||||
|
try:
|
||||||
|
milvus_info = get_vector_index().health()
|
||||||
|
milvus_status = "ok" if milvus_info.get("connected") else "error"
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
milvus_info = {}
|
||||||
|
milvus_status = "error"
|
||||||
|
milvus_info["error"] = str(exc)
|
||||||
|
|
||||||
|
# --- MinIO ---
|
||||||
|
try:
|
||||||
|
minio_connected = get_binary_store().client.connected
|
||||||
|
minio_status = "ok" if minio_connected else "error"
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
minio_status = "error"
|
||||||
|
minio_connected = False
|
||||||
|
|
||||||
|
# --- BM25 ---
|
||||||
|
bm25 = get_bm25_retriever()
|
||||||
|
|
||||||
|
# --- Sessions ---
|
||||||
|
try:
|
||||||
|
session_count = len(get_conversation_store().list_sessions())
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
session_count = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"milvus": {"status": milvus_status, **milvus_info},
|
||||||
|
"minio": {"status": minio_status, "connected": minio_connected},
|
||||||
|
"bm25": {"available": bm25 is not None},
|
||||||
|
"reranker": {
|
||||||
|
"enabled": settings.reranker_enabled,
|
||||||
|
"model": settings.reranker_model if settings.reranker_enabled else None,
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"active": session_count,
|
||||||
|
"max": settings.session_max_sessions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user