From 72f105c65854fba7f9abc63822593d467cc29dc2 Mon Sep 17 00:00:00 2001 From: PrajanManojKumarRekha Date: Sun, 31 May 2026 21:21:52 +0530 Subject: [PATCH 1/3] feat: add default_llm_provider and update config, .env.example and callers --- backend/.env.example | 5 +++++ .../app/background/taskiq/tasks/resume_processing.py | 9 ++++++--- backend/app/config.py | 1 + backend/app/utils/default_providers.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 360647b..8515433 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -11,3 +11,8 @@ DATABASE_URL="postgresql://user:password@localhost/dbname" SECRET_KEY="your-secret-key-change-in-production" ALGORITHM="HS256" ACCESS_TOKEN_EXPIRE_MINUTES=3000 + +# LLM +LLM_MODEL_NAME=groq/openai/gpt-oss-120b +GROQ_API_KEY= +LLM_PROVIDER=litellm diff --git a/backend/app/background/taskiq/tasks/resume_processing.py b/backend/app/background/taskiq/tasks/resume_processing.py index 84d72d9..1adadc3 100644 --- a/backend/app/background/taskiq/tasks/resume_processing.py +++ b/backend/app/background/taskiq/tasks/resume_processing.py @@ -2,7 +2,6 @@ from sqlalchemy import select -from app.ai.lite_llm import LiteLLMProvider from app.ai.resume_evaluator import ResumeEvaluator from app.ai.schema import ResumeEvaluatorRequest from app.background.taskiq.taskiq import broker @@ -10,7 +9,10 @@ from app.logger import get_logger from app.models.application import Application from app.models.interview import CustomInterview -from app.utils.default_providers import default_storage_provider +from app.utils.default_providers import ( + default_llm_provider, + default_storage_provider, +) from app.utils.pdf import extract_pdf_content logger = get_logger(__name__) @@ -22,6 +24,7 @@ async def process_resume_task(file_bytes_b64: str, file_name: str, application_i file_bytes = base64.b64decode(file_bytes_b64) provider = default_storage_provider() + llm_provider = default_llm_provider() async with AsyncSessionLocal() as session: app_to_update = await session.get(Application, application_id) @@ -41,7 +44,7 @@ async def process_resume_task(file_bytes_b64: str, file_name: str, application_i raise ValueError(f"Interview not found for application {application_id}.") extracted_text = extract_pdf_content(file_bytes) - evaluator = ResumeEvaluator(llm_provider=LiteLLMProvider()) + evaluator = ResumeEvaluator(llm_provider=llm_provider) req = ResumeEvaluatorRequest( resume_text=extracted_text, diff --git a/backend/app/config.py b/backend/app/config.py index 4e8ac27..4991412 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -20,6 +20,7 @@ class Settings(BaseSettings): # LLM LLM_MODEL_NAME: str = "groq/openai/gpt-oss-120b" GROQ_API_KEY: str = "" + LLM_PROVIDER: str = "litellm" # Supabase SUPABASE_URL: str = "" diff --git a/backend/app/utils/default_providers.py b/backend/app/utils/default_providers.py index 5124f35..ca01937 100644 --- a/backend/app/utils/default_providers.py +++ b/backend/app/utils/default_providers.py @@ -1,5 +1,6 @@ from app.config import settings from app.interfaces.background_worker import BackgroundWorkerInterface +from app.interfaces.llm_provider import LLMProviderInterface from app.interfaces.storage_proivder import StorageProviderInterface @@ -19,3 +20,12 @@ def default_worker_provider() -> BackgroundWorkerInterface: return worker raise ValueError(f"Unknown background worker: '{settings.BACKGROUND_WORKER}'") + + +def default_llm_provider() -> LLMProviderInterface: + if settings.LLM_PROVIDER == "litellm": + from app.ai.lite_llm import LiteLLMProvider + + return LiteLLMProvider() + + raise ValueError(f"Unknown LLM provider: '{settings.LLM_PROVIDER}'") From b5e10b010f4bc20af14b8a90fecee88f2ea51362 Mon Sep 17 00:00:00 2001 From: PrajanManojKumarRekha Date: Sun, 31 May 2026 21:22:55 +0530 Subject: [PATCH 2/3] feat: add fallback_llm_provider and update config, .env.example and callers --- backend/.env.example | 2 ++ .../background/taskiq/tasks/resume_processing.py | 15 ++++++++++++++- backend/app/config.py | 2 ++ backend/app/utils/default_providers.py | 11 +++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/.env.example b/backend/.env.example index 8515433..ae40996 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -16,3 +16,5 @@ ACCESS_TOKEN_EXPIRE_MINUTES=3000 LLM_MODEL_NAME=groq/openai/gpt-oss-120b GROQ_API_KEY= LLM_PROVIDER=litellm +FALLBACK_LLM_PROVIDER= +LITELLM_API_KEY= diff --git a/backend/app/background/taskiq/tasks/resume_processing.py b/backend/app/background/taskiq/tasks/resume_processing.py index 1adadc3..20aaa1a 100644 --- a/backend/app/background/taskiq/tasks/resume_processing.py +++ b/backend/app/background/taskiq/tasks/resume_processing.py @@ -6,10 +6,12 @@ from app.ai.schema import ResumeEvaluatorRequest from app.background.taskiq.taskiq import broker from app.database import AsyncSessionLocal +from app.exceptions.ai import AIProviderError, AITimeoutError from app.logger import get_logger from app.models.application import Application from app.models.interview import CustomInterview from app.utils.default_providers import ( + default_fallback_llm_provider, default_llm_provider, default_storage_provider, ) @@ -25,6 +27,7 @@ async def process_resume_task(file_bytes_b64: str, file_name: str, application_i file_bytes = base64.b64decode(file_bytes_b64) provider = default_storage_provider() llm_provider = default_llm_provider() + fallback_llm_provider = default_fallback_llm_provider() async with AsyncSessionLocal() as session: app_to_update = await session.get(Application, application_id) @@ -54,7 +57,17 @@ async def process_resume_task(file_bytes_b64: str, file_name: str, application_i ) logger.info("Starting resume evaluation for application %d...", application_id) - res = await evaluator.evaluate(req) + try: + res = await evaluator.evaluate(req) + except (AIProviderError, AITimeoutError): + if fallback_llm_provider is None: + raise + logger.warning( + "Primary LLM provider failed, retrying with fallback for application %d", + application_id, + ) + fallback_evaluator = ResumeEvaluator(llm_provider=fallback_llm_provider) + res = await fallback_evaluator.evaluate(req) app_to_update.resume = public_url app_to_update.extracted_resume = res.extracted_standardized_resume diff --git a/backend/app/config.py b/backend/app/config.py index 4991412..5983dfd 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -21,6 +21,8 @@ class Settings(BaseSettings): LLM_MODEL_NAME: str = "groq/openai/gpt-oss-120b" GROQ_API_KEY: str = "" LLM_PROVIDER: str = "litellm" + FALLBACK_LLM_PROVIDER: str = "gemini/gemini-2.5-flash" + LITELLM_API_KEY: str = "" # Supabase SUPABASE_URL: str = "" diff --git a/backend/app/utils/default_providers.py b/backend/app/utils/default_providers.py index ca01937..98674a9 100644 --- a/backend/app/utils/default_providers.py +++ b/backend/app/utils/default_providers.py @@ -29,3 +29,14 @@ def default_llm_provider() -> LLMProviderInterface: return LiteLLMProvider() raise ValueError(f"Unknown LLM provider: '{settings.LLM_PROVIDER}'") + + +def default_fallback_llm_provider() -> LLMProviderInterface | None: + if not settings.FALLBACK_LLM_PROVIDER: + return None + + from app.ai.lite_llm import LiteLLMProvider + + return LiteLLMProvider( + model_name=settings.FALLBACK_LLM_PROVIDER, api_key=settings.LITELLM_API_KEY + ) From d75ef4284eedb3705a94f583a2129813cd10c72b Mon Sep 17 00:00:00 2001 From: PrajanManojKumarRekha Date: Sun, 31 May 2026 21:23:14 +0530 Subject: [PATCH 3/3] docs: update documentation for fallback LLM configuration --- backend/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/README.md b/backend/README.md index 13abf1a..8c9b03e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -138,9 +138,15 @@ ACCESS_TOKEN_EXPIRE_MINUTES=30000 # TaskIQ / Redis REDIS_URL=redis://localhost:6379/0 -# LLM (Groq) +# LLM — primary provider LLM_MODEL_NAME=groq/openai/gpt-oss-120b GROQ_API_KEY=your-groq-api-key +LLM_PROVIDER=litellm + +# LLM — fallback provider (optional; leave blank to disable) +# Set to any LiteLLM-compatible model string, e.g. gemini/gemini-2.5-flash +FALLBACK_LLM_PROVIDER= +LITELLM_API_KEY= # Supabase Storage SUPABASE_URL=https://your-project.supabase.co @@ -259,7 +265,7 @@ app/interfaces/encrypter.py → app/utils/ (JwtEncrypter) app/interfaces/base_agent.py → app/ai/resume_evaluator.py ``` -This makes it straightforward to swap providers (e.g., Groq → OpenAI, Supabase → S3) without touching business logic. +This makes it straightforward to swap providers (e.g., Groq → OpenAI, Supabase → S3) without touching business logic. The active provider is selected at startup via `LLM_PROVIDER`; a fallback is instantiated from `FALLBACK_LLM_PROVIDER` and activated automatically on `AIProviderError` or `AITimeoutError` during resume evaluation. ### Model Structure