hiring-agent
CLI Toolinterviewstreet/hiring-agent
AI agent to evaluate and score resumes from PDFs.
Overview
Hiring Agent parses resume PDFs, extracts structured data using LLMs, enriches with GitHub signals, and outputs fair, explainable scores. Runs fully local with Ollama or uses Google Gemini.
README Preview
# Hiring Agent\n\nResume-to-Score pipeline that extracts structured data from PDFs, enriches with GitHub signals, and outputs a fair, explainable evaluation.\n\n\n \n \n \n \n \n \n \n \n \n\n\n---\n\n## Contents\n\n- [Overview](#overview)\n- [Architecture](#architecture)\n- [Installation and Setup](#installation-and-setup)\n - [Prerequisites](#prerequisites)\n - [Quick setup with pip](#quick-setup-with-pip)\n - [Ollama models](#ollama-models)\n- [Configuration](#configuration)\n- [How it works](#how-it-works)\n- [CLI usage](#cli-usage)\n- [Directory layout](#directory-layout)\n- [Provider details](#provider-details)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Overview\n\nHiring Agent parses a resume PDF to Markdown, extracts sectioned JSON using a local or hosted LLM, augments the data with GitHub profile and repository signals, then produces an objective evaluation with category scores, evidence, bonus points, and deductions. You can run fully local with Ollama or use Google Gemini.\n\n---\n\n## Architecture\n\n\n\n\n\n**Flow**\n\n1. `pymupdf_rag.py` converts PDF pages to Markdown-like text.\n2. `pdf.py` calls the LLM per section using Jinja templates under `prompts/templates`.\n3. `github.py` fetches profile and repos, classifies projects, and asks the LLM to select the top 7.\n4. `evaluator.py` runs a strict-scored evaluation with fairness constraints.\n5. `score.py` orchestrates everything end to end and writes CSV when development mode is on.\n\n\n\n\n**Key modules**\n\n- `models.py`\n Pydantic schemas and LLM provider interfaces.\n\n- `llm_utils.py`\n Provider initialization and response cleanup.\n\n- `transform.py`\n Normalization from loose LLM JSON to JSON Resume style.\n\n- `prompts/`\n All Jinja templates for extraction and scoring.\n\n\n\n\n\n---\n\n## Installation and Setup\n\n### Prerequisites\n\n- **Python 3.11+**\n\n The repository pins `.python-version` to 3.11.13.\n\n- **One LLM backend** (either of
FAQ (5)
TroubleshootingHow do I fix "NameError: name 'response' is not defined" in GitHub API error handling?
In fetch_all_github_repos in github.py, _fetch_github_api() returns (status_code, data), so do not access response.status_code or response.text. Replace the error-handling block with:
elif status_code == 404:
print(f"GitHub user not found: {username}")
return []
else:
print(f"GitHub API error: {status_code} - {repos_data}")
return []
This prevents the application from crashing with NameError when GitHub returns 404, rate-limit, network, or other API errors.
TroubleshootingHow do I fix a PyMuPDF memory leak when processing multiple PDF files in Python?
Close each PyMuPDF Document explicitly. In pdf.py, update extract_text_from_pdf() to use a context manager:
import fitz
def extract_text_from_pdf(path):
with fitz.open(path) as doc:
return "".join(page.get_text() for page in doc)
The with statement automatically closes the PDF after extraction, releasing memory. If you cannot use a context manager, call doc.close() in a finally block. This prevents memory from growing continuously when processing batches of 50+ PDFs with PyMuPDF 1.24.x on Python 3.11.
TroubleshootingHow do I fix "UnicodeEncodeError: 'charmap' codec can't encode" when writing JSON cache files on Windows with Python?
Explicitly set UTF-8 encoding in every Path.write_text() call that writes cache files. On Windows, the default encoding may be cp1252, which cannot encode emojis, accented names, or other Unicode characters.
Change:
Path(github_cache_filename).write_text(
json.dumps(github_data, indent=2, ensure_ascii=False)
)
To:
Path(github_cache_filename).write_text(
json.dumps(github_data, indent=2, ensure_ascii=False),
encoding="utf-8"
)
Apply the same encoding="utf-8" argument to all cache-writing calls in github.py and score.py, including the GitHub API cache around github.py line 49. For additional robustness, cache reads can handle empty or corrupted JSON by rebuilding the cache. The fix was verified on Windows 11 with Python 3.11.
TroubleshootingHow do I fix Windows console UnicodeEncodeError and Python 3.14 Protobuf TypeError when running score.py?
Add the following before importing Protobuf or LLM libraries:
import os
import sys
# Use UTF-8 for Windows console output
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
# Force the pure-Python Protobuf implementation
os.environ.setdefault("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python")
The environment variable must be set before protobuf is imported. Alternatively, run:
$env:PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION="python"
python score.py
You can also enable UTF-8 mode with set PYTHONUTF8=1 or run chcp 65001 before starting the script. A fix is provided in PR #340.
TroubleshootingWhy does resume scoring fail with 'List should have at least 1 item' after a Gemini 429 error or Ollama timeout?
A failed LLM extraction can write incomplete data, such as empty lists or null fields, to cache/resumecache_<resume>.json. On later runs, the corrupted cache is loaded instead of reprocessing the PDF, causing the Pydantic ValidationError.
Immediate workaround:
rm cache/resumecache_resume.json
python score.py data/resume.pdf
Delete the cache file matching the affected PDF if its name differs. You can also temporarily set DEVELOPMENT_MODE = False to bypass cache loading.
Permanent fix: validate the extracted JSONResume object before writing it to disk, never cache failed extractions, and validate cached data on load. If cache validation fails, delete or ignore the file and run a fresh PDF extraction. This behavior is addressed in PR #198; update to a release containing that PR if available.