Rutam Bhagat

Full Stack AI Engineer

Intelligent Resume Assistant

AI-powered hiring assistant that extracts structured resume facts and answers candidate-specific questions with guardrails against fabrication.

The app supports text and PDF resume intake, stores extracted resume data, keeps chat history, runs a small internal skill matcher, and returns every assistant answer in the required structured format.

{
  answer: string;
  confidence: number;
  source: "resume" | "inference";
  missing_data: string[];
}

Features

  • Paste resume text or upload a PDF/text file and extract candidate name, skills, experience, and education.
  • Chat with a strict hiring-assistant role.
  • Persist resumes and chat history in SQLite/libSQL through Drizzle.
  • Use an internal skill_matcher tool for skill-specific questions.
  • Return structured JSON with confidence, source, and missing data.
  • Say Not mentioned in resume when the resume does not contain the requested fact.
  • Stream browser microphone audio to the server over WebRTC for the optional voice bonus, without STT/TTS.

Tech Stack

  • Bun workspaces
  • React + Vite
  • Elysia server
  • Vercel AI SDK with an OpenAI-compatible provider
  • Drizzle ORM + SQLite/libSQL
  • Zod for response validation

Project Structure

apps/web        React chat UI
apps/server     API routes, LLM calls, resume/chat orchestration
packages/db     Drizzle schema and database client
packages/env    Typed environment variables
packages/ui     Shared UI components
assignment       Original task brief

Setup

Install dependencies:

bun install

Create .env in the repo root:

DATABASE_URL=file:packages/db/local.db
CORS_ORIGIN=http://localhost:5173
OPENAI_API_KEY=your-api-key
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4.1-mini
VITE_SERVER_URL=http://localhost:3000

Push the schema:

bun run db:push

Run the app:

bun run dev

Open the web app at http://localhost:5173. The API runs at http://localhost:3000.

API

POST /resumes/text

{
  "rawText": "Resume text...",
  "fileName": "resume.txt"
}

Returns a resumeId plus extracted resume fields.

POST /resumes/file

Send multipart/form-data with a file field containing a PDF or plain text resume.

Returns a resumeId plus extracted resume fields.

POST /chat

{
  "resumeId": 1,
  "sessionId": 1,
  "question": "Does this candidate know React?"
}

Returns sessionId, answer, confidence, source, and missing_data.

WS /voice/signal

WebSocket signaling for the WebRTC bonus. The browser sends microphone audio to a server-side peer and uses a data channel for RTT and packet-count metrics. There is intentionally no STT or TTS.

Architecture

  1. The web app submits raw resume text or a PDF/text file to the server.
  2. The server asks the LLM to extract only explicit resume facts into a Zod schema.
  3. Parsed resume sections are stored in normalized database tables.
  4. Chat requests load resume context, previous messages, and skill matcher output.
  5. The LLM responds as a strict hiring assistant using the mandatory JSON shape.
  6. The exchange is saved so follow-up questions keep conversation context.

Design Decisions

  • The skill matcher is intentionally small: it checks whether the question mentions stored resume skills and passes those matches to the LLM.
  • The WebRTC bonus is one-way microphone transport because the assignment asks for audio streaming, not voice AI.
  • Guardrails live in both prompts and schemas: prompts restrict behavior, while Zod validates the response shape before the UI accepts it.
  • Missing information is surfaced explicitly instead of guessed.

Demo Checklist

  1. Paste a resume into the intake dialog.
  2. Confirm extracted name, skills, experience, and education appear in the sidebar.
  3. Ask for a summary or candidate evaluation.
  4. Ask about a skill present in the resume.
  5. Ask about a fact not present in the resume and confirm the answer says Not mentioned in resume.