Minimal AI Job Search Agent

Automate job filtering and cover letter drafting locally with Python and SQLite.

Welcome to another official UVF IT beginner build. Today we are constructing a lightweight, local-first clone of an AI Job Search Agent, inspired by the workflow of MadsLorentzen's popular open-source framework.

Instead of relying on complex cloud pipelines or paid APIs, we will build a minimal working prototype using Python and SQLite. This guide focuses on the core logic: ingesting job data, filtering it based on criteria, and generating tailored application materials.

Prerequisites & Simplifications

Local AI Job Search Architecture
LOCAL RUNTIME EXTERNAL SERVICES Read/Write Job Data Generate Cover Letter Python Script main.py SQLite DB jobs.db LLM API OpenAI/Anthropic
1

Phase 1: Scaffold and Database Setup

Create the project directory and initialize the SQLite database with a schema for storing job postings. We will define a simple table with fields for title, company, and description.

Write the initialization script that creates the database file and inserts sample data so we have something to work with immediately.

setup.sh

#!/bin/bash
mkdir -p uvf_ai_job_search
cd uvf_ai_job_search
pip install requests
python -c "import sqlite3; print('SQLite3 ready')"

db_init.py

import sqlite3

DB_NAME = 'jobs.db'

def init_db():
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    # Create table
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS jobs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            company TEXT NOT NULL,
            description TEXT NOT NULL,
            keywords TEXT
        )
    ''')
    
    # Insert sample data if empty
    cursor.execute('SELECT count(*) FROM jobs')
    if cursor.fetchone()[0] == 0:
        sample_jobs = [
            ('Senior Python Developer', 'TechCorp', 'We are looking for an experienced Python developer to join our remote team. Must have 5+ years experience.', 'python,remote,senior'),
            ('Junior Web Developer', 'StartupInc', 'Looking for a junior developer to help build our web platform. Knowledge of JavaScript and React required.', 'javascript,react,junior'),
            ('Data Scientist', 'DataDriven', 'Seeking a data scientist with strong Python and SQL skills to analyze large datasets.', 'python,sql,data')
        ]
        cursor.executemany('INSERT INTO jobs (title, company, description, keywords) VALUES (?, ?, ?, ?)', sample_jobs)
        conn.commit()
        print(f'Inserted {len(sample_jobs)} sample jobs.')
    else:
        print('Database already populated.')
    
    conn.close()

if __name__ == '__main__':
    init_db()
    print('Database initialized successfully.')
2

Phase 2: Core Logic - Job Filtering

Implement the core filtering logic. We will create a function that queries the SQLite database for jobs matching specific keywords (e.g., 'Python', 'Remote').

This step replaces the complex scraping engine with a simple data retrieval mechanism, allowing us to focus on the AI integration later.

job_filter.py

import sqlite3

DB_NAME = 'jobs.db'

def filter_jobs(keywords):
    """
    Query the database for jobs matching the provided keywords.
    Keywords is a list of strings.
    """
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    # Simple LIKE query for each keyword
    # Note: This is a basic implementation. For production, consider full-text search.
    conditions = " OR ".join(["description LIKE ?" for _ in keywords])
    params = [f'%{kw.lower()}%' for kw in keywords]
    
    query = f"SELECT id, title, company, description FROM jobs WHERE {conditions}"
    
    try:
        cursor.execute(query, params)
        results = cursor.fetchall()
        return results
    except Exception as e:
        print(f"Error querying database: {e}")
        return []
    finally:
        conn.close()

if __name__ == '__main__':
    # Test the filter
    keywords = ['Python']
    jobs = filter_jobs(keywords)
    print(f"Found {len(jobs)} jobs matching '{keywords}':")
    for job in jobs:
        print(f"- {job[1]} at {job[2]}")
3

Phase 3: AI Integration - Cover Letter Generator

Integrate an LLM API to generate tailored cover letters. We will use the requests library to send the job description and a static user profile to the API.

Parse the JSON response from the AI and format it into a readable string. This mimics the 'tailor your CV' feature of the original project.

ai_agent.py

import requests
import os
import json

# Use environment variable for API key, or set a default for testing
API_KEY = os.getenv('OPENAI_API_KEY', 'sk-your-api-key-here')
API_URL = 'https://api.openai.com/v1/chat/completions'

def generate_cover_letter(job_title, company, job_description, user_profile):
    """
    Generate a cover letter using the OpenAI API.
    """
    if API_KEY == 'sk-your-api-key-here':
        return "[API Key not set. Set OPENAI_API_KEY environment variable.]"
    
    prompt = f"""
    Write a professional cover letter for the following job application:
    
    Job Title: {job_title}
    Company: {company}
    Job Description: {job_description}
    
    User Profile: {user_profile}
    
    Please keep the tone professional and enthusiastic.
    """
    
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {API_KEY}'
    }
    
    payload = {
        'model': 'gpt-3.5-turbo',
        'messages': [
            {'role': 'system', 'content': 'You are a helpful career assistant.'},
            {'role': 'user', 'content': prompt}
        ],
        'max_tokens': 500
    }
    
    try:
        response = requests.post(API_URL, headers=headers, json=payload)
        response.raise_for_status()
        data = response.json()
        return data['choices'][0]['message']['content']
    except requests.exceptions.RequestException as e:
        print(f"API Error: {e}")
        return None
    except (KeyError, IndexError) as e:
        print(f"Response parsing error: {e}")
        return None

if __name__ == '__main__':
    # Test the AI agent
    test_job = {
        'title': 'Python Developer',
        'company': 'TechCorp',
        'description': 'Looking for a Python expert.'
    }
    user_profile = "I am a passionate Python developer with 3 years of experience."
    
    letter = generate_cover_letter(
        test_job['title'],
        test_job['company'],
        test_job['description'],
        user_profile
    )
    print("Generated Cover Letter:")
    print(letter)
4

Phase 4: Main Orchestration

Combine the database query and AI generation into a single main script. This script will find matching jobs and generate a cover letter for the first match.

Add basic error handling for API failures and database connections to ensure the script runs smoothly locally.

main.py

import sqlite3
import json
import os
from job_filter import get_matching_jobs
from ai_agent import generate_cover_letter

def main():
    db_path = "jobs.db"
    if not os.path.exists(db_path):
        print("Error: Database not found. Run db_init.py first.")
        return

    try:
        # 1. Filter jobs
        keywords = ["Python", "Remote"]
        jobs = get_matching_jobs(db_path, keywords)
        
        if not jobs:
            print("No matching jobs found.")
            return

        print(f"Found {len(jobs)} matching job(s).")
        
        # 2. Generate cover letter for the first match
        target_job = jobs[0]
        print(f"\nGenerating cover letter for: {target_job['title']} at {target_job['company']}...")
        
        # Mock user profile (static for this minimal clone)
        user_profile = {
            "name": "Alex Dev",
            "experience": "3 years in Python backend development",
            "skills": ["Python", "SQL", "Docker"]
        }
        
        cover_letter = generate_cover_letter(target_job, user_profile)
        
        # 3. Output result
        print("\n--- Generated Cover Letter ---")
        print(cover_letter)
        print("--- End of Letter ---")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    main()
5

Phase 5: Run and Verify

Execute the main script to verify the end-to-end flow. You should see a job title printed followed by a generated cover letter.

Check the SQLite database to ensure the job data persists and can be queried again.

python main.py

Verifying the Stack

CheckCommand / Why it matters
Database created with sample datasqlite3 jobs.db 'SELECT count(*) FROM jobs;' should return > 0
Filtering works correctlyRunning python job_filter.py should print job titles matching keywords
AI generates textRunning python main.py should output a cover letter string
Error handling is robustRunning python main.py with an invalid API key should print a clear error message instead of crashing

Next Steps

To extend this clone, consider adding a simple CLI using argparse to allow users to input their own keywords, or integrate a real resume parser using pdfplumber.