feat: implement FastAPI application with Pinecone/Redis integration and candidate matching router
Browse files- backend/main.py +14 -3
- backend/src/routers/matching.py +2 -2
- backend/src/workers/celery_app.py +2 -0
backend/main.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import logging
|
|
|
|
|
|
|
| 3 |
from contextlib import asynccontextmanager
|
| 4 |
from fastapi import FastAPI, Request
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -15,16 +17,22 @@ settings = get_settings()
|
|
| 15 |
|
| 16 |
_pc_client: Pinecone | None = None
|
| 17 |
_pc_ready: bool = False
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
def get_pinecone() -> Pinecone:
|
| 21 |
return _pc_client
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
@asynccontextmanager
|
| 25 |
async def lifespan(app: FastAPI):
|
| 26 |
-
global _pc_client, _pc_ready
|
| 27 |
_pc_client = Pinecone(api_key=settings.pinecone_api_key)
|
|
|
|
| 28 |
|
| 29 |
try:
|
| 30 |
existing = [idx.name for idx in _pc_client.list_indexes()]
|
|
@@ -40,14 +48,15 @@ async def lifespan(app: FastAPI):
|
|
| 40 |
except Exception as exc:
|
| 41 |
_pc_ready = False
|
| 42 |
logger.warning(
|
| 43 |
-
"Pinecone unavailable at startup (%s).
|
| 44 |
-
"The API will start but vector search will fail until Pinecone is reachable.",
|
| 45 |
exc,
|
| 46 |
)
|
| 47 |
|
| 48 |
app.state.pinecone = _pc_client
|
| 49 |
app.state.pinecone_ready = _pc_ready
|
|
|
|
| 50 |
yield
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
app = FastAPI(
|
|
@@ -74,10 +83,12 @@ app.include_router(matching.router, prefix="/api/match", tags=["Matching"])
|
|
| 74 |
@app.get("/health")
|
| 75 |
async def health(request: "Request"):
|
| 76 |
pc_ok = getattr(request.app.state, "pinecone_ready", False)
|
|
|
|
| 77 |
return {
|
| 78 |
"status": "ok",
|
| 79 |
"version": "1.0.0",
|
| 80 |
"pinecone": "connected" if pc_ok else "unavailable",
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
|
|
|
|
| 1 |
import os
|
| 2 |
import logging
|
| 3 |
+
import json
|
| 4 |
+
import redis.asyncio as redis
|
| 5 |
from contextlib import asynccontextmanager
|
| 6 |
from fastapi import FastAPI, Request
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 17 |
|
| 18 |
_pc_client: Pinecone | None = None
|
| 19 |
_pc_ready: bool = False
|
| 20 |
+
_redis_client: redis.Redis | None = None
|
| 21 |
|
| 22 |
|
| 23 |
def get_pinecone() -> Pinecone:
|
| 24 |
return _pc_client
|
| 25 |
|
| 26 |
|
| 27 |
+
def get_redis() -> redis.Redis:
|
| 28 |
+
return _redis_client
|
| 29 |
+
|
| 30 |
+
|
| 31 |
@asynccontextmanager
|
| 32 |
async def lifespan(app: FastAPI):
|
| 33 |
+
global _pc_client, _pc_ready, _redis_client
|
| 34 |
_pc_client = Pinecone(api_key=settings.pinecone_api_key)
|
| 35 |
+
_redis_client = redis.from_url(settings.redis_url, decode_responses=False)
|
| 36 |
|
| 37 |
try:
|
| 38 |
existing = [idx.name for idx in _pc_client.list_indexes()]
|
|
|
|
| 48 |
except Exception as exc:
|
| 49 |
_pc_ready = False
|
| 50 |
logger.warning(
|
| 51 |
+
"Pinecone unavailable at startup (%s).",
|
|
|
|
| 52 |
exc,
|
| 53 |
)
|
| 54 |
|
| 55 |
app.state.pinecone = _pc_client
|
| 56 |
app.state.pinecone_ready = _pc_ready
|
| 57 |
+
app.state.redis = _redis_client
|
| 58 |
yield
|
| 59 |
+
await _redis_client.close()
|
| 60 |
|
| 61 |
|
| 62 |
app = FastAPI(
|
|
|
|
| 83 |
@app.get("/health")
|
| 84 |
async def health(request: "Request"):
|
| 85 |
pc_ok = getattr(request.app.state, "pinecone_ready", False)
|
| 86 |
+
redis_conn = getattr(request.app.state, "redis", None)
|
| 87 |
return {
|
| 88 |
"status": "ok",
|
| 89 |
"version": "1.0.0",
|
| 90 |
"pinecone": "connected" if pc_ok else "unavailable",
|
| 91 |
+
"redis": "ok" if redis_conn else "missing",
|
| 92 |
}
|
| 93 |
|
| 94 |
|
backend/src/routers/matching.py
CHANGED
|
@@ -128,11 +128,11 @@ async def trigger_match(
|
|
| 128 |
@router.get("/{jd_id}", response_model=MatchResponse)
|
| 129 |
async def get_match_results(
|
| 130 |
jd_id: uuid.UUID,
|
|
|
|
| 131 |
session_id: uuid.UUID | None = Query(None),
|
| 132 |
db: AsyncSession = Depends(get_db),
|
| 133 |
):
|
| 134 |
-
|
| 135 |
-
r = redis.Redis.from_url(settings.redis_url)
|
| 136 |
cache_key = f"match_v2:{jd_id}:{session_id or 'none'}"
|
| 137 |
|
| 138 |
try:
|
|
|
|
| 128 |
@router.get("/{jd_id}", response_model=MatchResponse)
|
| 129 |
async def get_match_results(
|
| 130 |
jd_id: uuid.UUID,
|
| 131 |
+
request: Request,
|
| 132 |
session_id: uuid.UUID | None = Query(None),
|
| 133 |
db: AsyncSession = Depends(get_db),
|
| 134 |
):
|
| 135 |
+
r = request.app.state.redis
|
|
|
|
| 136 |
cache_key = f"match_v2:{jd_id}:{session_id or 'none'}"
|
| 137 |
|
| 138 |
try:
|
backend/src/workers/celery_app.py
CHANGED
|
@@ -21,4 +21,6 @@ celery_app.conf.update(
|
|
| 21 |
result_expires=3600,
|
| 22 |
worker_prefetch_multiplier=1,
|
| 23 |
task_acks_late=True,
|
|
|
|
|
|
|
| 24 |
)
|
|
|
|
| 21 |
result_expires=3600,
|
| 22 |
worker_prefetch_multiplier=1,
|
| 23 |
task_acks_late=True,
|
| 24 |
+
broker_pool_limit=1,
|
| 25 |
+
redis_max_connections=5,
|
| 26 |
)
|