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_matchertool for skill-specific questions. - Return structured JSON with confidence, source, and missing data.
- Say
Not mentioned in resumewhen 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
- The web app submits raw resume text or a PDF/text file to the server.
- The server asks the LLM to extract only explicit resume facts into a Zod schema.
- Parsed resume sections are stored in normalized database tables.
- Chat requests load resume context, previous messages, and skill matcher output.
- The LLM responds as a strict hiring assistant using the mandatory JSON shape.
- 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
- Paste a resume into the intake dialog.
- Confirm extracted name, skills, experience, and education appear in the sidebar.
- Ask for a summary or candidate evaluation.
- Ask about a skill present in the resume.
- Ask about a fact not present in the resume and confirm the answer says
Not mentioned in resume.