Skip to content

About

Backend for AI-assisted technical interview practice real-time guided dialogue, resume-aware sessions and reliable async CV insights with notifications

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

313 Commits

Folders and files

Repository files navigation

AI Interview Tutor — Backend

Backend services for an AI-assisted technical interview platform: REST authentication and user APIs, a real-time interview agent over WebSockets, asynchronous CV analysis (PDF from object storage through an LLM into MongoDB), and email notifications driven by RabbitMQ. The repository is structured as a production-ready polyglot backend—environment-driven configuration, durable messaging with separate job and result routes for CV analysis, layered domain/application/infrastructure code in each service, and Compose-based local stacks that mirror deployment boundaries.


Table of contents


Overview

Component Type Responsibility
user-management-service HTTP (FastAPI) Users, JWT auth (RS256), OAuth (Google/GitHub), billing entitlements, GDPR export/delete, refresh-token rotation with Redis, CV upload + RabbitMQ jobs
interview-service HTTP + WebSocket LangGraph mock interviews (modes, resume, transcripts), JD match, coding sandbox, question bank / learning paths
practice-service HTTP + workers Personalized practice plans, grading (MCQ/open/flashcard/code_review/scenario), progress, in-app notifications
analyze-service Worker Consume cv-analyze-stream → S3 PDF → Docling extract → LLM → CVData → MongoDB → cv-analysis-results
notification-service Worker Transactional email via AWS SES (reset password, verify email, plan ready, digests)
libs/jwt_handler Python package JWT encode/decode, token DTOs
libs/shared_models Python package Shared CV/interview/practice/billing/JD DTOs and messaging contracts
libs/observability Python package Correlation IDs, metrics, rate limits, PII redaction, Langfuse/Sentry hooks

Architecture

High-level view of the platform (API gateway, services, RabbitMQ, databases, and S3). This repository ships User Management, Interview, Practice, Analyze, and Notification backends; the frontend lives in a separate Next.js repo.

Architecture overview

Synchronous paths. Clients talk HTTP to User Management / Practice and HTTP/WebSocket to Interview (nginx sticky sessions for WS).

Asynchronous paths. Analyze consumes cv-analyze-stream (Docling + LLM). Notification consumes email streams. Practice workers consume interview-completed and practice-plan jobs and publish practice-plan-ready-stream.

Kind Service Typical command Role
HTTP user-management-service uvicorn src.main:app Auth, users, billing, privacy
HTTP interview-service uvicorn src.main:app WebSocket interview + coding/JD APIs
HTTP practice-service uvicorn src.main:app Practice plans + notifications inbox
Worker analyze-service python -m src.main CV analysis pipeline (Docling)
Worker notification-service python -m src.main Transactional email

Observability, billing, privacy

  • Correlation ID + Prometheus-style /metrics + in-process rate limits via libs/observability.
  • Optional Sentry (SENTRY_DSN), Langfuse (LANGFUSE_*), OTEL (OTEL_EXPORTER_OTLP_ENDPOINT).
  • Billing: GET /api/v1/billing/entitlements, POST /api/v1/billing/checkout-session (Stripe when FEATURE_STRIPE_BILLING + keys set).
  • Privacy: GET /api/v1/privacy/export, hardened DELETE /api/v1/user/me/.
  • Runbooks: docs/runbooks/.

Repository layout

Path Role
user-management-service/ FastAPI app, domain/application/infrastructure layers, Alembic migrations, aio-pika producer for cv-analyze-stream and reset-password-stream, Redis refresh-token rotation and reuse detection
interview-service/ FastAPI router, LangGraph workflow, LLM factory (llm.py), static interview_client.html
analyze-service/ dependency-injector composition root, aio-pika consumer on cv-analyze-stream, producer to cv-analysis-results, S3 + Mongo + LangChain CV parser
notification-service/ Blocking pika consumer on reset-password-stream, ResetPasswordUseCase, Mongo repository + SES adapter
libs/jwt_handler/ Reusable JWT handling consumed by user-management (pyproject.toml Git or path dependency)
pyproject.toml / poetry.lock Repository-root Poetry project: pins shared dev tooling and locks root dependency versions
.pre-commit-config.yaml Hooks run Black, Ruff, and scoped mypy
.gitignore Ignores .env, virtualenvs, IDE metadata, bytecode

Root tooling and repository hygiene

  • Declares python = "^3.12".
  • Root [tool.poetry.dependencies] includes shared packages such as motor where scripts or future shared tooling need MongoDB async access alongside service-local stacks.
  • Dev dependencies: pre-commit, black, mypy, ruff.
  • [tool.black] — line-length = 120.
  • [tool.ruff] — target-version = "py312", line-length = 120, excludes Alembic migrations under user-management and prompt directories under analyze/interview.
Hook Scope / notes
trailing-whitespace, end-of-file-fixer, check-added-large-files Whole repo
Black 25.1.0, language_version: python3.12 Whole repo
Ruff via ruff-pre-commit Whole repo
mypy v1.15.0, hook entries Files under ^user-management-service/ and ^interview-service/; additional_dependencies: [types-PyYAML]

Additional services can opt into the same hooks by extending the configuration as the codebase grows.

Ignores .env, venv/, .idea/, __pycache__/. Service-specific artifacts (for example Compose volumes) are not globally ignored—prefer keeping machine-local paths out of commits manually.


Shared library: jwt_handler

Purpose. Centralize RS256 JWT creation and verification for microservices that share RSA keys.

Package metadata. libs/jwt_handler/pyproject.toml — package name jwt_handler, Python >=3.9 (services use 3.12).

Core types.

  • JWTTokenHandler — encode_jwt / decode_jwt using PyJWT; maps PyJWT exceptions to library-specific errors.
  • Defaults for algorithm (RS256) and token TTL fields live on JWTSettings. Optional YAML under key jwt_handler via load_from_yaml() complements environment-first configuration for local profiles.

Consumption in user-management. user-management-service/pyproject.toml pulls jwt-handler from Git (branch = dev, subdirectory = libs/jwt_handler). For forks or offline work, switch to a path dependency pointing at libs/jwt_handler.

Imports. Prefer explicit submodule imports (jwt_handler.models, jwt_handler.value_objects, etc.). The package __init__.py does not re-export the public API.


Services

User Management Service

Entrypoints. src/main.py builds FastAPI with lifespan: obtains Redis and async SQLAlchemy engine via dependency helpers, waits for RabbitMQ readiness (wait_for_rabbitmq), then serves requests; on shutdown closes Redis and the engine.

Configuration. src/config.py via pydantic-settings:

Group Env prefix / keys Purpose
Postgres POSTGRES_* Async URL postgresql+asyncpg://…
SQLAlchemy (defaults on SQLAlchemySettings) Pool size, echo
Redis REDIS_* Host, port, passwords, decode_responses
JWT RSA PUBLIC_KEY, PRIVATE_KEY PEM strings for RS256
RabbitMQ RABBITMQ_* URL builder; cv_analyzer_queue_name defaults to cv-analyze-stream; connection wait timeout

Layering.

  • Domain — entities such as User (UserRole, timestamps), repository interfaces, Redis/RabbitMQ ports.
  • Application — use cases (registration, login, refresh, CRUD, delete me, password reset). RefreshTokenUseCase: decode refresh JWT, verify TokenType.REFRESH, load user, reject blocked users, reject reuse if refresh-key:{token} already exists in Redis, otherwise issue new access + refresh pair and SETEX the old refresh token key so it cannot be reused until expiry window ends.
  • API — FastAPI routers under api/v1, global exception wiring exception_handler, OAuth2 scheme security.py with tokenUrl="/api/v1/auth/token".

Messaging. RabbitMQProducer implements IRabbitMQProducer: aio-pika, durable queues, persistent messages on the default exchange with routing_key = queue_name. Application use cases publish CV analysis jobs to cv-analyze-stream and password-reset payloads to reset-password-stream as users progress through signup, profile updates, and account recovery.

Auth HTTP surface includes POST /api/v1/auth/signup, POST /api/v1/auth/token, POST /api/v1/auth/refresh, POST /api/v1/auth/reset-password, and POST /api/v1/auth/reset-password/{token}. Integration tests cover this surface under test_reset_password.py.

Persistence. Alembic under src/migrations; initial users table with unique email/username/phone constraints.

Containers. docker-compose.yaml: Postgres 12, Redis 8 with ACL file, RabbitMQ 4.1 management image. Dockerfile: multi-stage Poetry install, uvicorn src.main:app.


Interview Service

Entrypoints. src/main.py: mounts /api/v1 and static files at /static; root GET / returns JSON pointing to /static/interview_client.html.

Configuration. src/config.py: YAML-backed logging (config.yaml → logger key), LLM settings via Pydantic Settings:

  • LLM_PROVIDER — openai (default), openrouter, or google (.env.example).
  • OpenAI — OPENAI_API_KEY, OPENAI_LLM__MODEL (default gpt-4o-mini), OPENAI_LLM__TEMPERATURE.
  • OpenRouter — OPENROUTER_API_KEY, OPENROUTER_LLM__MODEL, OPENROUTER_LLM__API_BASE (default https://openrouter.ai/api/v1), OPENROUTER_LLM__TEMPERATURE.
  • Google Gemini — GOOGLE_API_KEY, GOOGLE_LLM__MODEL (default gemini-2.0-flash), GOOGLE_LLM__TEMPERATURE.
  • INTERVIEW_LLM__TEMPERATURE — optional per-service temperature override.

LLM wiring. src/agent/llm.py: LLMFactory.create_llm() selects the provider from LLM_PROVIDER and returns a LangChain BaseChatModel (ChatOpenAI for OpenAI/OpenRouter, ChatGoogleGenerativeAI for Google).

Agent graph. create_interview_workflow(): LangGraph StateGraph over InterviewState. Nodes include greeting, soft/hard question askers, small talk, wrap-up, question routing, and answer evaluation. Conditional edges:

  • section_router — drives high-level stage transitions (including end).
  • question_router_decision — async LLM classification (SMALLTALK vs continuing soft/hard questioning using QUESTION_ROUTER_DECISION_PROMPT).

Checkpointing is LangGraph-compatible and configurable: lean defaults for local demos, durable backends where multiple instances share interview state.

WebSocket contract. websocket_endpoint at /api/v1/interview/ws/{user_id} delegates to InterviewConnectionManager.

  • Client JSON type: user_message (requires content), end_interview, get_status; unknown types fall back to forwarding content as user text.
  • Server JSON examples: interview_started, agent_message (includes stage), interview_status, interview_complete, error.

Reference demo vs production profile. SAMPLE_CV backs the static demo. Production deployments bind user_id and CV context to authenticated sessions and structured outputs from Analyze (for example documents keyed by user or correlation id), so the agent tailors dialogue to the candidate’s profile.

Dependencies. interview-service/pyproject.toml: FastAPI with websockets, LangGraph + SQLite extras, LangChain, LangChain OpenAI, LangChain Google GenAI (optional provider), checkpoint SQLite, pydantic-settings.


Analyze Service

Entrypoints. src/main.py: wires Container, waits for RabbitMQ, runs await rabbitmq_consumer.process_messages() (async forever).

Configuration. src/config.py: S3_*, RABBITMQ_* (inbound cv-analyze-stream, results cv-analysis-results), MONGODB_* (includes cv_analysis_collection_name), and shared LLM settings (LLM_PROVIDER, provider-specific keys/models, ANALYZE_LLM__TEMPERATURE default 0.2). Logging can be driven from config.yaml via the container’s dictConfig resource.

LLM wiring. src/agent/llm.py: LLMFactory.create_llm() uses the same LLM_PROVIDER switch as interview-service; CVAnalyzer calls structured output via with_structured_output(CVData).

Pipeline. RabbitMQConsumer:

  • aio-pika connect_robust, prefetch_count=5, durable subscription on cv-analyze-stream.
  • On message: JSON decode; if routing_key equals configured analyzer queue name → CVAnalyzeUseCase.

CVAnalyzeUseCase:

  1. Parse CVInitialAnalysisMessage.
  2. Download PDF bytes from S3 using message url (IS3Client).
  3. Extract text in a thread (IPDFLoader, PyPDF-based implementation).
  4. Run CVAnalyzer: LangChain structured output into CVData.
  5. insert_one document into Mongo (IMongoRepository).
  6. Publish CVResultAnalysisMessage as JSON via RabbitMQProducer to cv-analysis-results.

Docker. docker-compose.yaml: app service depends on RabbitMQ, MongoDB (Bitnami 5.0), LocalStack for S3. Dockerfile: worker-oriented image running python3 -m src.main (the default runtime is the async consumer, not an HTTP server).


Notification Service

Entrypoints. src/main.py: synchronous main() — waits for RabbitMQ, starts blocking consumer, closes Mongo on exit.

Packaging. pyproject.toml uses PEP 621 [project] metadata while retaining poetry-core as the build backend; dependencies include pika, pymongo, boto3, dependency-injector.

Configuration. src/config.py:

Group Env prefix Notes
RabbitMQ RABBITMQ_* Defaults: host rabbitmq, port 5672
MongoDB MONGODB_* Defaults: host mongodb, port 27017; URI construction supports authSource and related cluster settings
SES SES_* AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY, region, sender

Pipeline. RabbitMQConsumer: blocking pika, durable queue reset-password-stream, prefetch_count=5. Successful handling basic_ack; failures basic_nack(..., requeue=False) after logging.

ResetPasswordUseCase: validates ResetPasswordMessageModel, inserts into Mongo via repository context manager, retries SES send up to five times; raises NotSentError if every attempt fails.

Outbound wiring. OutboundAdaptersContainer: singleton MongoClient, factory SESClient wrapping boto3 SES.

There is no .env.example in this service; infer required variables from config.py.


Data stores

Technology Service Usage
PostgreSQL user-management users table · Alembic revision 0167f6ba46f8
Redis user-management Refresh-token reuse prevention / rotation bookkeeping (refresh-key:{token})
MongoDB analyze-service CV analysis documents in MONGODB_CV_ANALYSIS_COLLECTION_NAME
MongoDB notification-service Outbound message records in MONGODB_MESSAGES_COLLECTION_NAME
S3 analyze-service CV PDF objects (LocalStack or AWS) · vars S3_*
AWS SES notification-service Email delivery · SES_* + boto3 client

Prerequisites

  • Python 3.12 for all runnable services
  • Poetry — install per service directory
  • Docker / Docker Compose — optional but practical for Postgres, Redis, RabbitMQ, MongoDB, LocalStack

Quick start

1. Clone and repository hooks

git clone https://github.com/fedorakooo/AI-Interview-Tutor.git
cd AI-Interview-Tutor

poetry install
poetry run pre-commit install

2. User Management

Infra: user-management-service/docker-compose.yaml.

cd user-management-service
cp .env.example .env   # fill values (Postgres, Redis, JWT keys, RabbitMQ, ...)
poetry install
poetry run alembic upgrade head
poetry run uvicorn src.main:app --reload --host 0.0.0.0 --port 8000

3. Interview service

cd interview-service
cp .env.example .env   # set LLM_PROVIDER and the matching API key (e.g. OPENAI_API_KEY)
poetry install
poetry run uvicorn src.main:app --reload --host 0.0.0.0 --port 8000

Open http://localhost:8000/static/interview_client.html (or your chosen host/port).

4. Workers

cd analyze-service && poetry install && poetry run python -m src.main
cd notification-service && poetry install && poetry run python -m src.main

Local infra for analyze (RabbitMQ, MongoDB, LocalStack): analyze-service/docker-compose.yaml.


Development

Formatting, lint, types

From the repository root after poetry install:

poetry run pre-commit run --all-files

Tests

cd user-management-service
poetry install
poetry run pytest

Tests live under user-management-service/tests/ (unit + integration; integration may require Compose — see docker-compose.test.yaml).


License

This project is licensed under the Apache License 2.0 — see LICENSE.


About

Backend for AI-assisted technical interview practice real-time guided dialogue, resume-aware sessions and reliable async CV insights with notifications

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages