DEV Community

Cover image for Building a Local-First Resume Analyzer Agent with Gemma 3 and LangExtract
Harish Kotra (he/him)
Harish Kotra (he/him)

Posted on

Building a Local-First Resume Analyzer Agent with Gemma 3 and LangExtract

In this deep dive, we build a production-quality, privacy-focused resume analysis tool that runs 100% on your local machine using Streamlit, Ollama, and Google's Gemma 3 models.


๐Ÿ”’ The Privacy Integrity Problem

Resume data is sensitive. PII (Personally Identifiable Information), career history, and contact details are not things you want to casually send to third-party APIs. Yet, most "AI Resume Analyzers" require uploading PDFs to cloud endpoints or pasting text into web interfaces that store data.

We can do better. With the rise of capable local LLMs like Gemma 3, we can build a powerful extraction engine that runs entirely on localhost.

๐Ÿ› ๏ธ The Local Stack

To build this, we need three core components:

  1. The Brain: Ollama running Google Gemma 3 (4b or 12b).
  2. The Engineer: LangExtract for structured data extraction.
  3. The Interface: Streamlit for a reactive, Python-native UI.

Architecture

Architecture Diagram

๐Ÿง  Structured Extraction with LangExtract

The hardest part of analyzing resumes is that they are unstructured. LLMs are good at chatting, but we need JSON. LangExtract solves this by enforcing a schema through example-driven extraction.

Instead of just asking "Extract skills", we define a Pydantic schema and provide few-shot examples.

1. Defining the Schema (schema.py)

We define classes for every entity we want to extract.

from pydantic import BaseModel

class ExperienceItem(BaseModel):
    company: str
    role: str
    duration: str
    skills_used: list[str]

class ResumeSchema(BaseModel):
    candidate_name: str
    emails: list[str]
    skills: list[str]
    experience: list[ExperienceItem]
    # ... education, projects, etc.
Enter fullscreen mode Exit fullscreen mode

2. The Extraction Engine (extractor.py)

We wrap LangExtract to handle large resumes (which can exceed context windows) by chunking the text.

import langextract as lx

def extract_resume(text: str, model_name: str) -> dict:
    # 1. Chunk text if too long
    chunks = chunk_text(text)

    all_extractions = []
    for chunk in chunks:
        # 2. Call LangExtract with schema and examples
        result = lx.extract(
            text_or_documents=chunk,
            prompt_description=get_resume_prompt(),
            examples=get_resume_examples(),
            model_id=model_name,
            model_url="http://localhost:11434"
        )
        all_extractions.extend(result.extractions)

    # 3. Consolidate and deduplicate results
    return extractions_to_schema(all_extractions)
Enter fullscreen mode Exit fullscreen mode

๐Ÿ” Visualizing Evidence (No Hallucinations)

A key feature of our agent is Evidence Highlighting. We don't just trust the LLM; we verify it. LangExtract returns char_interval (start/end positions) for every extraction.

We use this to render the original text with color-coded highlights, so you can see exactly where a skill or experience claim came from.

# span_highlighter.py
def highlight_spans(text: str, extractions: list) -> str:
    # ... logic to sort spans and handle overlaps ...
    for start, end, label, content in spans:
        color = ENTITY_COLORS[label]
        # Inject HTML span with colored background and tooltips
        html_parts.append(
            f'<span style="background:{color}22; border-bottom:2px solid {color};">'
            f'{content}</span>'
        )
    return "".join(html_parts)
Enter fullscreen mode Exit fullscreen mode

๐ŸŽจ The UI: Smart & Theme-Aware

We built a Streamlit dashboard that adapts to your system theme (Light/Dark mode) using CSS variables. It features:

  • Model Selection: Switch between gemma3:4b (fast) and gemma3:4b (detailed).
  • Skill Intelligence: Clusters extracted skills into domains (Frontend, Backend, DevOps, AI) using keyword analysis.
  • Job Match: Pasting a Job Description triggers a fuzzy-match algorithm (rapidfuzz) to calculate coverage percentage and identify missing skills.

๐Ÿš€ Running It Locally

  1. Clone the repo:

    git clone https://github.com/harishkotra/resume-analyzer-agent
    cd resume-analyzer-agent
    
  2. Install dependencies:

    pip install -r requirements.txt
    
  3. Start Ollama:

    ollama serve
    ollama pull gemma3:4b
    
  4. Launch the App:

    streamlit run app.py
    

By combining the structural guarantees of LangExtract with the reasoning power of Gemma 3, we've built a tool that rivals commercial SaaS offeringsโ€”without a single byte of data leaving your machine.

Checkout the Github repo here: https://github.com/harishkotra/resume-analyzer-agent

Top comments (0)