Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ jobs:
registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}

lint-and-test:
uses: ./.github/workflows/lint-and-test.yml
with:
registry-name: ${{ vars.DOCKER_PROD_REGISTRY }}
image-name: welearn-api
image-tag: ${{ github.sha }}
secrets:
registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}
needs:
- build-docker
# lint-and-test:
# uses: ./.github/workflows/lint-and-test.yml
# with:
# registry-name: ${{ vars.DOCKER_PROD_REGISTRY }}
# image-name: welearn-api
# image-tag: ${{ github.sha }}
# secrets:
# registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
# registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}
# needs:
# - build-docker

tag-deploy:
needs:
- build-docker
- lint-and-test
# - lint-and-test
uses: CyberCRI/github-workflows/.github/workflows/tag-deploy.yaml@main
3 changes: 2 additions & 1 deletion k8s/welearn-api/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ spec:
{{- end }}
imagePullPolicy: IfNotPresent
name: welearn-api
args: ["uvicorn", "src.main:app", "--workers", "{{.Values.uvicorn.workersCount}}", "--host", "0.0.0.0", "--port", "{{.Values.containerPort}}", "--limit-max-requests", "{{.Values.uvicorn.limitMaxRequests}}"]
ports:
- name: http
containerPort: 8080
containerPort: {{ .Values.containerPort }}
envFrom:
{{- if .Values.config.nonSensitive }}
- configMapRef:
Expand Down
6 changes: 6 additions & 0 deletions k8s/welearn-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ resources:
limits:
memory: 1508M

containerPort: 8080

config:
nonSensitive:
CLIENT_ORIGINS_REGEX: '^{{ join "|" (values .Values.allowedHostsRegexes | sortAlpha ) }}$'
Expand All @@ -52,3 +54,7 @@ runOnGpu: false # Schedule on the GPU node pool to lower its cost
allowedHostsRegexes:
localhost: |-
http:\/\/localhost:5173

uvicorn:
workersCount: 2
limitMaxRequests: 1000
2 changes: 2 additions & 0 deletions src/app/api/api_v1/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# src/app/api/api_v1/api.py

from fastapi import APIRouter

from src.app.api.api_v1.endpoints import chat, micro_learning, search, tutor, user
Expand Down
42 changes: 34 additions & 8 deletions src/app/api/api_v1/endpoints/search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends, Response
# src/app/api/api_v1/endpoints/search.py

from fastapi import APIRouter, Depends, HTTPException, Response
from fastapi.concurrency import run_in_threadpool
from qdrant_client.models import ScoredPoint

Expand Down Expand Up @@ -49,7 +51,7 @@ def get_params(

if not resp.query:
e = EmptyQueryError()
return bad_request(message=e.message, msg_code=e.msg_code)
bad_request(message=e.message, msg_code=e.msg_code)

return resp

Expand Down Expand Up @@ -112,8 +114,10 @@ async def search_doc_by_collection(

return res
except (CollectionNotFoundError, ModelNotFoundError) as e:
response.status_code = 404
return e.message
raise HTTPException(
status_code=404,
detail={"message": e.message, "code": e.msg_code},
)


@router.post(
Expand All @@ -138,10 +142,30 @@ async def search_all_slices_by_lang(

return res
except CollectionNotFoundError as e:
response.status_code = 404
return e.message
raise HTTPException(
status_code=404,
detail={"message": e.message, "code": e.msg_code},
)


@router.post(
"/test",
summary="search all slices",
description="Search slices in all collections or in collections specified",
response_model=list[ScoredPoint] | None,
)
async def test_thread(
response: Response,
query: str,
sp: SearchService = Depends(get_search_service),
):
qp = EnhancedSearchQuery(
query=query,
sdg_filter=[]
)
result = await sp.simple_search_handler(qp=qp)
return result

@router.post(
"/multiple_by_slices",
summary="search all slices",
Expand Down Expand Up @@ -187,8 +211,10 @@ async def search_all(
response.status_code = 204
return []
except CollectionNotFoundError as e:
response.status_code = 404
return e.message
raise HTTPException(
status_code=404,
detail={"message": e.message, "code": e.msg_code},
)

response.status_code = 200

Expand Down
2 changes: 1 addition & 1 deletion src/app/api/shared/enpoints/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_health() -> HealthCheck:
status_code=status.HTTP_200_OK,
response_model=HealthCheck,
)
def get_db_health(settings: ConfigDepend) -> HealthCheck:
async def get_db_health(settings: ConfigDepend) -> HealthCheck:
"""
## Perform a Health Check
Endpoint to perform a healthcheck on. This endpoint can primarily be used Docker
Expand Down
14 changes: 11 additions & 3 deletions src/app/core/lifespan.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# src/app/core/lifespan.py

from contextlib import asynccontextmanager

from fastapi import FastAPI
from qdrant_client import AsyncQdrantClient

from src.app.services.search import close_qdrant, init_qdrant
from src.app.api.dependencies import get_settings


@asynccontextmanager
async def lifespan(app: FastAPI):
await init_qdrant()
settings = get_settings()
app.state.qdrant = AsyncQdrantClient(
url=settings.QDRANT_HOST,
port=settings.QDRANT_PORT,
timeout=100,
)
yield
await close_qdrant()
await app.state.qdrant.close()
11 changes: 5 additions & 6 deletions src/app/middleware/monitor_requests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from fastapi import Request
from fastapi.concurrency import run_in_threadpool
from starlette.background import BackgroundTask
from starlette.middleware.base import BaseHTTPMiddleware

from src.app.services.sql_service import register_endpoint
Expand All @@ -11,18 +12,16 @@
class MonitorRequestsMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
session_id = request.headers.get("X-Session-ID")
response = await call_next(request)

if session_id and request.url.path.startswith("/api/v1/"):
try:
await run_in_threadpool(
register_endpoint,
endpoint=request.url.path,
session_id=session_id,
http_code=200,
response.background = BackgroundTask(
register_endpoint, request.url.path, session_id, 200
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP status code is hardcoded to 200, but response.status_code should be used instead to accurately log the actual response status (e.g., 404, 500). Replace 200 with response.status_code.

Suggested change
register_endpoint, request.url.path, session_id, 200
register_endpoint, request.url.path, session_id, response.status_code

Copilot uses AI. Check for mistakes.
)
except Exception as e:
logger.error(f"Failed to register endpoint {request.url.path}: {e}")
Comment on lines 22 to 23
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception handler will never catch exceptions from register_endpoint because BackgroundTask executes after the response is returned. The try-except block should be removed from the middleware since error handling is now in sql_service.py's register_endpoint method.

Copilot uses AI. Check for mistakes.
else:
logger.warning(f"No X-Session-ID header provided for {request.url.path}")

response = await call_next(request)
return response
Loading