talentry-ai / docs /architecture.md
williyam's picture
deploy: sync from talentry-ai @ f4ea7a8
c008e60
|
Raw
History Blame Contribute Delete
10.3 kB
# Talentry AI - System Architecture
This document is the **engineer's guide** to Talentry AI. If you are reviewing
the codebase at Stage 3 (reproduction) or Stage 5 (defend-your-work) of the
Redrob × Hack2Skill - India Runs challenge, start here.
---
## 1. Goals and constraints
The challenge ([`submission_spec.md` §3](../configs/submission_spec.txt))
imposes a hard envelope on the ranking step:
| Constraint | Limit |
| --------------------------- | --------------------------- |
| Wall-clock runtime | **≤ 5 minutes** |
| RAM | **≤ 16 GB** |
| Compute | **CPU only** |
| Network | **Off** (no LLM API calls) |
| Disk (intermediate state) | **≤ 5 GB** |
We additionally treat these *product* requirements as first-class:
* **Explainability** - every score must be defensible at Stage 4 manual review.
* **Reproducibility** - bit-for-bit reproducibility in a sandboxed Docker.
* **Anti-trap robustness** - the dataset deliberately contains keyword
stuffers, plain-language Tier-5s, behavioural twins, and ~80 honeypots.
* **No hallucination in reasoning** - every reasoning string must reference
only facts present in the candidate's own profile.
---
## 2. High-level architecture
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Talentry AI │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ JD text │───▶│ JD Parser │───▶│ JobRequirements │ │
│ └──────────┘ │ (rules+lex) │ │ • role family, seniority │ │
│ └──────────────┘ │ • must / nice / disqual. │ │
│ │ • locations, behaviour │ │
│ └──────────────┬─────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ candidates.jsonl │──▶│ Loader + models │──▶│ Feature builder │ │
│ └──────────────────┘ └──────────────────┘ │ (text_blob, │ │
│ │ role signals) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────┴───────┐ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌────────────────────┐│
│ │ BM25 + TF-IDF │ hybrid semantic score │ Skill Evidence ││
│ │ hybrid index │─────────────────┐ │ (cluster, stuffer ││
│ └─────────────────┘ │ │ detection) ││
│ ▼ └────────┬───────────┘│
│ ┌────────────────────────┐ │ │
│ │ Scorer (5 weighted) │◀───────┘ │
│ │ + Behavioural × mul. │ │
│ │ − Honeypot penalty │ │
│ └──────────┬─────────────┘ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Sort + Reasoning + CSV │ │
│ └──────────┬─────────────┘ │
│ ▼ │
│ submission.csv │
└──────────────────────────────────────────────────────────────────────────┘
```
### Module → file mapping
| Module | File |
| ---------------------------- | ---------------------------------------------------------- |
| Domain models | `src/talentry/core/models.py` |
| Tokeniser + synonyms | `src/talentry/nlp/tokenize.py` |
| Domain lexicons | `src/talentry/nlp/lexicons.py` |
| Candidate I/O | `src/talentry/io/candidates.py` |
| Submission CSV writer | `src/talentry/io/submission.py` |
| Per-candidate features | `src/talentry/features/builder.py` |
| Skill evidence scoring | `src/talentry/features/skill_match.py` |
| Behavioural multiplier | `src/talentry/signals/behavioural.py` |
| Honeypot penalty | `src/talentry/signals/honeypot.py` |
| Hybrid BM25+TF-IDF index | `src/talentry/ranker/semantic.py` |
| JD parser | `src/talentry/ranker/jd_parser.py` |
| Scoring formulae | `src/talentry/ranker/scorer.py` |
| Reasoning composer | `src/talentry/ranker/reasoning.py` |
| End-to-end pipeline | `src/talentry/ranker/engine.py` |
| CLI | `src/talentry/cli/rank.py` |
| HTTP API (FastAPI) | `src/talentry/api/server.py` |
| React UI | `ui/talentry-space/` |
---
## 3. Why not dense embeddings?
Reviewers will rightly ask: "this is a retrieval challenge - why not a
sentence-transformer?" Three reasons:
1. **Budget.** Loading a 90 MB MiniLM and encoding 100K text blobs is right at
the edge of the 5-minute CPU budget; once you add the per-row scoring,
feature building, sort, and CSV write, you have no headroom for slow
I/O on the Stage 3 sandbox.
2. **Dependency surface.** A serialised PyTorch model artifact is one more
thing that can break reproduction; pure BM25 + TF-IDF reproduces from
`pip install`-able libraries alone.
3. **Signal saturation.** BM25 saturates on rare-term overlap and TF-IDF
smooths over phrasing - and that is exactly what this dataset rewards.
Adding a dense model marginally improves recall on prose-only profiles
but adds noise on the keyword surface where stuffers live.
If we had two hours and a GPU per ranking call we would absolutely add a
domain-fine-tuned reranker on the top 1000. We do not.
---
## 4. Composition formula
```
linear = 0.32·title_alignment
+ 0.22·semantic_fit
+ 0.28·skill_evidence
+ 0.12·experience_band
+ 0.06·location
final = linear × behavioural_multiplier ∈ [0.55, 1.20]
− honeypot_penalty ∈ [0, 0.50]
(clipped to [-0.5, 1.5])
```
Every constant is grounded in a specific line of the JD; see
[`methodology.md`](methodology.md) for the rationale.
---
## 5. Determinism
* Hot path uses no random state.
* `reference_date` for behavioural recency is overridable for tests.
* Sort tie-break: `candidate_id` ascending - matches the validator.
* CSV writer enforces the exact validator invariants at write-time so any
drift fails loudly in the CLI, never silently at upload.
---
## 6. Threat model - the four traps
| Trap | Defence |
| --------------------- | -------------------------------------------------------------- |
| Keyword stuffer | Skill *evidence* (endorsements × duration × proficiency) |
| Plain-language Tier 5 | BM25 over career-description text + role-family trajectory |
| Behavioural twin | Behavioural multiplier in [0.55, 1.20] |
| Honeypot | Honeypot penalty subtracts up to 0.50 |
---
## 7. Operational notes
* CLI: `python -m talentry.cli.rank ...` (see `make submission`).
* API: `talentry-serve` (or `make serve`) → `http://localhost:7860`.
* UI: `make ui-dev` (`http://localhost:5173` with `/api` proxied).
* Container: `make docker-build && make docker-run` - same image is pushed to
the HuggingFace Space.