Back to Projects
Demo

LLM Resume Agent

Apr – Jul 2026Feature complete, v2 plannedSolo build, alongside coursework

A privacy-preserving resume review agent running fully locally on Ollama. It grades resume-to-job fit and proposes traceable edits, and is structurally prevented from inventing experience.

PythonFastAPIOllamaLLM
40s demo

By the numbers

40
tests
3
matching layers
5
traceability tags
4
JD extraction layers
0
bytes leaving the machine
3.5k
lines of source

Architecture

Parse
PDF / DOCX
Extract
canonical JSON
Verify
verbatim guard
Match
exact→fuzzy→LLM
Grade
local Qwen3
Trace
tagged edits

The model reads, judges, and reasons. Everything else is deterministic code that can overrule it.

Overview

The LLM Resume Agent ingests a resume and a target job description, grades the fit, and proposes phrasing-level edits. It runs entirely locally on Ollama, so a document containing a full name, phone number, home city, employment history, and sometimes visa status never leaves the machine.

The defining constraint is that the agent may reorganize and rephrase what is there, and may not add anything that is not. That is enforced structurally rather than by prompt instruction: the resume is parsed into a canonical Pydantic model where fields are marked VERBATIM or MUTABLE, and a model validator checks every VERBATIM field against the immutable source text after the model has spoken. A hallucinated skill is stripped and logged before any later stage can see it.

Skill matching runs in three layers, cheapest first: exact matching after alias normalization, fuzzy matching with rapidfuzz, then LLM semantic adjudication on only the terms that survived both. The semantic layer must return a verdict for every unmatched term, and each verdict is validated against the input lists, so a match naming a skill the candidate does not have is discarded rather than reported. The layer degrades to fuzzy-only if the model is unavailable, so matching never fails a request.

Grading receives the pre-computed match results with the matched sets explicitly labeled, which narrows the model's job to the part that genuinely needs judgment: which gaps matter for this role. Every proposed edit carries one of five traceability tags, and a post-processor overrules the model's self-tagging in the direction that fails safe. If you have no quantified outcome for a bullet, the agent is forbidden from inventing one and must ask you to supply it.

A FastAPI backend serves a Streamlit frontend that streams pipeline progress over server-sent events. Job descriptions can be pasted or fetched from a URL, behind an SSRF guard and a streaming 5MB payload cap, with a four-layer HTML extraction pipeline that reconciles JSON-LD, Trafilatura, and a heading walker. A 40-test pytest suite covers the domain heuristics, parser flows, SSRF protections, schema integrity, and the matching guardrails, and most of it runs without a model loaded.

Why It Matters

Applicant tracking systems filter the majority of resumes before a human reads them, so tailoring each application is both necessary and repetitive. It is exactly the kind of task worth automating, and exactly the kind that most automation makes worse.

Ask a capable model to improve a resume and it will hand back a stronger candidate than the one who uploaded it: an invented metric here, a framework never touched there, all fluent and all a liability in an interview that can no longer be passed. A tool that quietly inflates your resume is not saving you work, it is creating a problem you will discover in the room.

Keeping the model local solves the privacy half, and confining it to three narrow jobs solves the honesty half. Everything else, whether a file is safe, whether a term matches, whether a claim has evidence, is deterministic code that runs before or after the model and can overrule it.

Stack

Python · FastAPI · Streamlit · Pydantic v2 · Ollama (Qwen3 30B-A3B) · pdfplumber · python-docx · trafilatura · BeautifulSoup · rapidfuzz · pytest

View on GitHub