# How the Hiring-Agent System Extracts and Evaluates GitHub Repository Data from Resumes

> Learn how the Hiring-Agent system extracts and evaluates GitHub repository data from resumes. It enriches profiles, scores repos, and uses LLMs to curate top projects for recruiters.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: deep-dive
- Published: 2026-07-22

---

**The Hiring-Agent system normalizes GitHub URLs into canonical usernames, enriches profile data via the GitHub API, filters and scores repositories by contribution metrics, and employs a large language model to curate exactly 7 top projects for recruiter review.**

The `interviewstreet/hiring-agent` repository automates technical candidate screening by transforming raw GitHub links found in résumés into structured, ranked project portfolios. The extraction pipeline lives primarily in [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) and orchestrates API calls, caching layers, and LLM-based evaluation to extract and evaluate GitHub repository data from resumes with minimal manual intervention.

## Username Extraction and Profile Retrieval

The pipeline begins by sanitizing unstructured input from résumés. The `extract_github_username()` function in [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) (lines 16-27) handles multiple URL patterns—including `https://github.com/user`, `@user`, and bare `user` strings—and returns a canonical username.

Once normalized, `fetch_github_profile()` constructs the endpoint `https://api.github.com/users/<username>` and invokes the internal `_fetch_github_api()` helper. The resulting JSON maps to a **Pydantic** `GitHubProfile` model defined in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), providing typed access to fields like bio, location, and public repository count.

## Repository Fetching and Filtering

The `fetch_all_github_repos()` function (lines 18-89) retrieves the user’s repositories from `/users/<username>/repos`, sorted by recent activity and capped by the `max_repos` parameter. The system applies an immediate quality filter: **forked repositories with fewer than five forks are discarded** to eliminate low-effort copies.

For each retained repository, the pipeline calls `fetch_repo_contributors()` to retrieve the full contributor list. This data feeds into `fetch_contributions_count()` (lines 87-99), which calculates two critical metrics:
- `author_commit_count`: Total commits made by the repository owner
- `total_commit_count`: Aggregate commits across all contributors

These statistics reveal the candidate’s actual ownership stake versus minor participation.

## Enriched Project Payload Assembly

Before LLM evaluation, the system assembles a comprehensive dictionary for every repository. This payload includes the project name, description, URLs, primary language, classification (open-source vs. self-project), the computed contribution metrics, and a nested `github_details` object containing stars, forks, and topics. The entire list is **sorted by star count in descending order** to prioritize high-impact work.

## LLM-Driven Project Selection

The `generate_projects_json()` function (lines 34-73) handles the core evaluation logic. It serializes the enriched project list into JSON and injects it into the `github_project_selection.jinja` template located in `prompts/templates/`. The rendered prompt is sent to the LLM provider initialized via `initialize_llm_provider()` from [`llm_utils.py`](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py).

The system message strictly constrains the model to return **exactly 7 unique projects**. The raw LLM response passes through `extract_json_from_response()` to strip markdown fences and parse valid JSON back into Python objects.

## Fallback Handling and Validation

If the LLM response is malformed, contains duplicate entries, or fails to provide 7 distinct projects, the pipeline executes a deterministic fallback. It selects the first 7 entries from the pre-sorted star-count list, ensuring the recruiter always receives a complete portfolio even when the LLM misbehaves. The `fetch_and_display_github_info()` function (lines 59-78) orchestrates this end-to-end flow, returning a final structure containing the `profile`, curated `projects`, and `total_projects` count.

## Caching and Rate-Limit Handling

The internal `_fetch_github_api()` implementation (lines 29-84) includes production-ready resilience features. When `DEVELOPMENT_MODE` is enabled in [`config.py`](https://github.com/interviewstreet/hiring-agent/blob/main/config.py), responses cache to `cache/gh_githubcache_…json`, eliminating redundant API calls during iterative development.

For production deployments, the function monitors the `X-RateLimit-Remaining` header. When fewer than 10 requests remain, the system sleeps until the reset timestamp (capped at one hour) and logs a warning prompting the operator to set `GITHUB_TOKEN` to upgrade from the unauthenticated limit of **60 requests/hour** to **5,000 requests/hour**.

## Implementation Code Examples

### Basic Profile and Repository Extraction

```python
from github import fetch_github_profile, fetch_all_github_repos

profile = fetch_github_profile("https://github.com/awesome-dev")
print(profile)                         # → GitHubProfile Pydantic object

repos = fetch_all_github_repos("awesome-dev", max_repos=20)
print(f"Found {len(repos)} repos")    # → list of enriched repo dicts

```

### End-to-End Pipeline Invocation

```python
from github import fetch_and_display_github_info

result = fetch_and_display_github_info("https://github.com/awesome-dev")

# `result` structure:

# {

#   "profile": {...},          # basic user fields

#   "projects": [...],         # 7 best projects selected by the LLM

#   "total_projects": 7

# }

print(result["projects"])           # display the curated projects

```

### Direct LLM Selector Testing

```python
from github import generate_projects_json, fetch_all_github_repos

all_projects = fetch_all_github_repos("awesome-dev")
top_projects = generate_projects_json(all_projects)   # returns list of 7 dicts

for p in top_projects:
    print(f"{p['name']} – ⭐ {p['github_details']['stars']}")

```

## Core Files and Architecture

| File | Role |
|------|------|
| **[`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py)** | Central logic for username extraction, API fetching, contribution metrics, caching, rate-limit handling, and LLM project selection. |
| **`prompts/templates/github_project_selection.jinja`** | Jinja template formatting repository data and enforcing the 7-project selection constraint for the LLM. |
| **[`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)** | Runtime template loading and rendering utilities used by the GitHub module. |
| **[`llm_utils.py`](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py)** | Provider initialization (`initialize_llm_provider`) and response sanitization (`extract_json_from_response`). |
| **[`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py)** | Pydantic schemas including `GitHubProfile` for type-safe API responses. |
| **[`config.py`](https://github.com/interviewstreet/hiring-agent/blob/main/config.py)** | Feature flags such as `DEVELOPMENT_MODE` that toggle caching behavior. |

## Summary

- **Username normalization** via `extract_github_username()` handles diverse URL formats found in résumés.
- **Quality filtering** removes forked repositories with fewer than five forks before heavy processing.
- **Contribution metrics** distinguish between project ownership and minor contributions using commit statistics.
- **LLM curation** enforces exactly 7 unique projects, with deterministic fallback to star-sorted results if the model fails.
- **Rate-limit protection** includes caching for development and automatic backoff with token-upgrade hints for production.
- **Modular architecture** separates concerns across [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py), Jinja templates, and Pydantic models for maintainability.

## Frequently Asked Questions

### How does the system handle GitHub API rate limits?

The `_fetch_github_api()` helper checks the `X-RateLimit-Remaining` header on every request. When fewer than 10 requests remain, it pauses execution until the rate limit resets (maximum one hour). In development mode, responses cache to disk under `cache/gh_githubcache_…json`, while production deployments can set `GITHUB_TOKEN` to increase the quota from 60 to 5,000 requests per hour.

### What criteria does the LLM use to select the top 7 projects?

The LLM receives a structured JSON payload containing repository names, descriptions, languages, star counts, fork counts, and contribution statistics via the `github_project_selection.jinja` template. The system prompt instructs the model to identify the most technically impressive and impactful work, forcing the return of exactly 7 unique entries. If the LLM hallucinates or returns invalid JSON, the system falls back to the top 7 repositories sorted by star count.

### How does the system extract GitHub usernames from messy résumé text?

The `extract_github_username()` function in [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) (lines 16-27) uses pattern matching to parse multiple input formats, including full URLs (`https://github.com/user`), prefixed handles (`@user`), and bare usernames. It validates the extracted string against GitHub’s username constraints and returns a canonical identifier suitable for API calls.

### What happens if a candidate has fewer than 7 public repositories?

If the total number of available repositories after filtering (removing low-fork forks) is fewer than 7, the LLM receives all available projects and the system returns that smaller set. The fallback logic only triggers when the LLM fails to parse or when it returns duplicates; it does not invent additional repositories. The `total_projects` field in the response accurately reflects the actual count returned.