arcshukla's picture
handling profile statuses more robustly, adding analytics tab and service, and improving error handling/logging for LLM split failures.
e58df34
Raw
History Blame Contribute Delete
4.7 kB
"""
api/chat.py β€” Chat endpoints
Rate limiting: 20 requests / minute per IP on the POST /chat endpoint.
Configured via settings.CHAT_RATE_LIMIT (default "20/minute").
"""
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request
from app.core.constants import STATUS_ENABLED, STATUS_DISABLED, STATUS_SUSPENDED, STATUS_SOFT_DELETED, STATUS_DELETED
from app.core.logging_config import get_logger
from app.models.api_models import ChatRequest, ChatResponse
from app.models.user_models import UserEntity
from app.services.chat_service import chat_service
from app.services.profile_service import profile_service
from app.services.prompt_service import prompt_service
logger = get_logger(__name__)
router = APIRouter(prefix="/api/profiles/{slug}/chat", tags=["chat"])
# ── Rate limiter ──────────────────────────────────────────────────────────────
try:
from slowapi import Limiter
from slowapi.util import get_remote_address
_limiter = Limiter(key_func=get_remote_address)
_RATE_LIMIT_AVAILABLE = True
except ImportError:
_limiter = None
_RATE_LIMIT_AVAILABLE = False
logger.warning("slowapi not installed β€” chat rate limiting is DISABLED")
# ── Shared dependency ──────────────────────────────────────────────────────────
def _require_enabled_profile(slug: str) -> UserEntity:
"""FastAPI dependency: resolve slug β†’ enabled UserEntity or raise HTTP error."""
entry = profile_service.get_entry(slug)
if not entry:
raise HTTPException(404, "This profile does not exist. If you are a professional looking to create your own AI-powered career page, visit the platform to get started.")
if entry.status == STATUS_DISABLED:
raise HTTPException(503, "This profile is currently disabled.")
if entry.status == STATUS_SUSPENDED:
raise HTTPException(403, "This profile is not available.")
if entry.status in (STATUS_SOFT_DELETED, STATUS_DELETED):
raise HTTPException(410, "This profile is no longer available.")
if entry.status != STATUS_ENABLED:
raise HTTPException(403, f"Profile '{slug}' is not active")
return entry
# ── Endpoints ──────────────────────────────────────────────────────────────────
def _chat_handler(
request: Request,
slug: str,
req: ChatRequest,
background_tasks: BackgroundTasks,
_entry: UserEntity,
) -> ChatResponse:
logger.info("Chat request | slug=%s | len=%d", slug, len(req.message))
resp = chat_service.chat(
slug = slug,
message = req.message,
history = req.history,
session_id = req.session_id,
background_tasks = background_tasks,
)
logger.info("Chat response | slug=%s | tokens=%d calls=%d | followups=%d",
slug, resp.tokens_used.total_tokens,
resp.tokens_used.call_count, len(resp.followups))
return resp
if _RATE_LIMIT_AVAILABLE:
from app.core.config import settings as _settings
@router.post("", response_model=ChatResponse)
@_limiter.limit(getattr(_settings, "CHAT_RATE_LIMIT", "20/minute"))
def chat(
request: Request,
slug: str,
req: ChatRequest,
background_tasks: BackgroundTasks,
_entry: UserEntity = Depends(_require_enabled_profile),
) -> ChatResponse:
return _chat_handler(request, slug, req, background_tasks, _entry)
else:
@router.post("", response_model=ChatResponse)
def chat(
request: Request,
slug: str,
req: ChatRequest,
background_tasks: BackgroundTasks,
_entry: UserEntity = Depends(_require_enabled_profile),
) -> ChatResponse:
return _chat_handler(request, slug, req, background_tasks, _entry)
@router.get("/welcome")
def get_welcome(slug: str):
if not profile_service.profile_exists(slug):
raise HTTPException(404, f"Profile '{slug}' not found")
logger.debug("Welcome request | slug=%s", slug)
return {
"welcome": chat_service.get_welcome_message(slug),
"followups": chat_service.get_initial_followups(slug),
"placeholder": prompt_service.chat_placeholder(slug),
}