Add CPU threading, T1-only LLM polish, experiment script
Hemprasad Badgujar
a41b023
Introduce process-level CPU thread budgeting and cgroup-aware resolution (config.compute.cpu_threads, resolve_cpu_threads, configure_compute, onnx_session_options). Wire cpu_threads through ONNX loaders and retrievers. Switch Step-11 LLM polish to T1-only (top 10) and update tiers/acceptance logic; add batched prompt handling and stability fixes. Add scripts/explain_prompt_experiment.py for fast prompt iteration. Improve settings loading so RERANKER_* env vars override YAML (Yaml-backed source). Minor UX/CI touches: CLI/Streamlit call order, hashing fixes in preprocess, model prefetch updates, README updates, and new runtime tests.
Explain: A+B sentence structure, behavioral framing, more variety
Hemprasad Badgujar
362838c
Reasoning rewrite per feedback — present strengths then behavior, no
template feel, no AI-looking em-dashes.
- Structure (A+B): S1 identity + fit verdict; S2 strengths (+<=1 soft gap
on T3/T4); S3 behavioral signals as a NARRATIVE (open to work, in
recruiter demand, active in open source) not a data dump; S4 why-ranked
(dominant signal + which JD fields matched lexically).
- Drop the rank ordinal ("top 10") — redundant with the rank column;
replace with concrete signal + BM25 field-hit detail.
- Variety so it doesn't read as a template: varied openers, connectives
(and / as well as), gap pivots (though / albeit / the main gap being),
and 5 behavior-sentence forms. Result: 100/100 distinct reasonings.
- No em-dashes anywhere (commas/periods/colons only).
- Grammar fixes: a/an before vowels, no doubled connectives, no
"...is that <noun phrase>", no doubled seniority ("Lead Staff").
- Length guard: trims keyword tail then strengths so every line <500.
- Location promoted in…
Rebuild Step 11 into a package (src/rank/explain/) with a three-layer
pipeline: FactEngine -> advanced rule-based NLG composer -> tiered LLM
polish. Carries ~90 of 100 finalists with zero LLM.
- bands.py: numbers -> subjective band labels (never raw floats)
- engine.py: JD-grounded FactEngine (missing must-haves, nice-to-have,
3-tier location, country, exclude-company, certs, notice/recency,
widened dominant-signal across all funnel legs)
- compose.py: clause aggregation + connectives (and/but/though), opener
variation, potion-base-32M grounded top-k variant selection, T1=4 /
others 3-4 sentences, >=6 variants/slot
- signals.py: redrob_signals behavioral bank, adaptive best-first,
sentinel skip, >=2-flag soft-pattern gate
- tiers.py: per-tier model (T1 Qwen3.5-2B reused, T2 LFM2.5-350M with
SmolLM2-360M fallback); grounding gate + over-compression guard;
Qwen3-1.7B dropped (-1.1 GB disk)
- Negatives gated to T3/T4 CSV only (top picks read positively);
explanation + exp…
Enhance JD extraction (Qwen3.5-2B + richer prompt) and Step-11 reasoning
Hemprasad Badgujar
eb18ae9
JD extraction:
- Switch JD parse to Qwen3.5-2B (standard-instruct GGUF); reasoning stays Qwen3-1.7B (separate handle)
- Gate Qwen's /no_think soft switch to Qwen models only (LlmClient._soft_no_think)
- Enrich the OBJECTIVE prompt with related skills (nice_to_have), target_industries,
exclude_companies, and all welcomed locations; guard out JD-rejected domains
- best-of-N now targets must_have recall only; _as_str_list dedups keywords case-insensitively
- City->country gazetteer fallback for acceptable_countries
Step-11 reasoning:
- Enrich grounded facts: industry / education / recency / title-fit + dominant-signal tail
- Revive the recent_days knob; LLM polish on Qwen3-1.7B
Config/cleanup: hard-filter graded checks off at funnel entry; doc fixes; remove stray files.
Tests: +dedup / related-field / explain coverage (93 pass, ruff + mypy clean).
Enrich the granite-embedded candidate text with familiar skills + pillar strengths
Hemprasad Badgujar
fb57bfa
The body granite embeds now folds in more of the parameters we compute:
- materialize adds "Also familiar with: <familiar skills>" (beginner/intermediate
skills were previously dropped from the embedded body).
- quality appends "Strengths: <strong pillars>." — the quality pillars (>=0.6) named
as words the embedder can use.
- precision.max_length 128 -> 192 so the enriched body actually lands in the embedding
window (texts are p50~308, so 128 truncated most of it).
Quality-neutral-to-better on the full 100K run (mean matched skills 4.3 vs 4.37; the
top-3 now surfaces a Search Engineer and a Pune ML Engineer — more JD-aligned for a
search/ranking role). Cost: granite 10s -> 18s, total ~77s (well under the 5-min budget).
Also harden two explain assertions against the true-random phrasing.
Enrich the embed/rerank query with extracted skills + roles
Hemprasad Badgujar
1cbb761
potion / granite / cross-encoder previously compared candidate text against only the
JD ideal_summary; the extracted must_have_skills and job_titles fed BM25 but not the
embedders. embed_query() now composes ideal_summary + "Core skills: ..." + "Relevant
roles: ..." (summary leads so it survives the embedders' 128-token truncation and
leaves the cross-encoder room for the doc; skills/titles capped 15/8). Wired into the
pipeline and the explorer (which now shows the enriched query per embed phase).
Quality-neutral on the full 100K run (mean matched skills 4.31 vs 4.37 baseline, within
temp-0.5 parse noise). Also fix a band assertion made flaky by the true-random phrasing.
Explainability: true-random phrasing + drop dead reasoning module
Hemprasad Badgujar
226492c
- Stat-bank variant selection now uses true randomness (random.choice): the subjective
explanation prose varies run to run. Ranking stays deterministic — only the text
changes, never candidate_id/rank/score — so the submission contract is unaffected.
- Delete src/reasoning.py and test_reasoning.py: add_reasoning is unreachable now that
the pipeline/explorer use add_explanation. The one reused helper (_clean_note) moves
into explain.py; a focused test covers it.
Add JD-grounded explainability for the top 100 (Step 11)
Hemprasad Badgujar
95e1a87
Replace the generic one-line reasoning with explain.py, which explains each finalist
AGAINST the JD using the spec + every score column:
- matched must_have_skills, title match, location-in-JD-cities, experience vs the
years band, and the dominant ranking signal (reranker/granite/composite/quality).
- Phrasing varies per candidate via a stat bank keyed by a candidate_id hash —
deterministic (reproducible), not random.
- Ranks 1..explain_llm_top_n get a Gemma polish when reasoning.mode == "llm"; the rest
(and any LLM failure) use the template.
Surfaces (both): submission.csv reasoning becomes JD-grounded (contract preserved), and
a full per-candidate breakdown is written to data/reports/explanations.json (matched
skills, title/location, band, score breakdown) and shown in the explorer.
Wire add_explanation into the pipeline (with the JD spec) and the explorer rank stages;
add config (explain_llm_top_n, explanations_path) and tests. reasoning.py is kept for
_clean_note/_facts_exp…