- CRITICAL FIX: a real HF Space build was OOMKilled (exit 137) because
--setup started calling run_preprocess(limit=None) to pre-build the
candidates Parquet cache during the Docker build step. Build environments
are commonly more memory-constrained than the runtime container, and
loading+flattening 100K nested JSON records is exactly the kind of
Python-object-heavy operation that can spike past the build's memory
ceiling. OOMKilled is an OS-level SIGKILL, unrecoverable from Python, so
--setup no longer touches candidates at all -- it goes back to warming
only the models. The cache still gets built, just lazily on first real
use inside the already-running container (Streamlit's _warm_candidates,
or the first `rank.py --out`), in the runtime container's own budget.
- FIX: bm25_prefilter was wired into pipeline.py's run_full() but never
into streamlit_app.py's _rank_stages() -- the Streamlit phase explorer
was silently skipping it, meaning potion_broad there would…
- New Step 04.5 bm25_prefilter: cheap lexical BM25 pre-cut right after
hard_filter, before potion_broad's expensive embedding pass. Shrinks
potion's input from ~74K to 10K rows using JD-keyword overlap alone.
composite_narrow still runs its full potion+soft+bm25 blend later, once
those signals exist, so ranking quality is unchanged. Falls back to a
full passthrough on an empty/failed JD spec rather than an arbitrary cut.
- broad_retrieval/composite_narrow/precision all use fixed top_n counts
now (500/200/125) instead of a mix of top_pct and top_n.
- Moved the candidates Parquet cache down into load_candidates() itself
(was one layer up in run_preprocess, which Streamlit's per-phase
explorer bypasses). Now both the CLI and Streamlit share one cache —
verified Streamlit's eager warm-up dropped from 14.6s to 0.27s.
- Streamlit's _load_phase_input switched to st.cache_resource (avoids
copying the 100K-row frame on every cache hit; safe since every
downstream Polars st…
Move JD extraction from data/artifacts/ (gitignored) to data/ so the demo-critical artifact ships with the repo/image. Increase n_ctx from 4096→6144 to handle longer JDs safely. Reduce parse_attempts from 2→1 (mitigated via improved prompt clarity). Retire target_industries/exclude_companies from LLM prompt to free budget for skill/title recall, but keep as [] in schema for stability. Major prompt refinements: simplify role/task structure, emphasize skill recall priority with explicit scan guidance, consolidate constraints. Add data/jd_extraction.json fixture (parsed from sample job_description.md) and empty secrets file. Update tests & docs to reflect retired prompt fields.
Switch JD-parse/T1 polish to Qwen3-1.7B
Hemprasad Badgujar
baeef08
Replace references and defaults for the JD-parse / T1 reasoning polish from Qwen3.5-2B to Qwen3-1.7B across docs, config, Dockerfile and code. Make T1 LLM polish conditional on reasoning.mode == "llm" (default "template" keeps full NLG, no LLM). Update model repo/gguf names, README benchmarking notes, prefetch logging, and explanatory comments to reflect the new preferred model and behavior.
Respect --limit; LLM streaming heartbeat
Hemprasad Badgujar
b24d6e4
Fixes a silent bug where CLI --limit was ignored by piping the limit through to pipeline.run_full/run_preprocess and recording it in metrics. Adds a streaming LLM generator with periodic heartbeat logs to distinguish slow-but-progressing generations from hung calls and integrates it into LlmClient (preserves timeout behavior). Streamlit: eagerly warms models once per server process to avoid first-request cold loads. Config: reasoning mode default switched to "template". README and job_description adjusted to reflect usage and content changes.
Add LLM call timeout and streaming pipeline phases
Hemprasad Badgujar
26f86bb
Implement hard per-call timeout (45s) via threading to prevent runaway LLM generations that were observed taking 1996s with no way to interrupt. Convert pipeline phases to generators to yield results as they complete instead of batching at the end, enabling live status updates on slow hardware. Add per-call timing metrics to JD parse artifact. Introduce post-hoc reasoning audit to catch empty/duplicate/leaked-name issues for hackathon grading. Update polish acceptance gate with sentence-count bounds (2-4) and contradiction markers to ensure top-10 notes read as strengths-only.
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.
Reuse Qwen3.5 for T2; drop LFM2.5 & factor ONNX
Hemprasad Badgujar
190f0fc
Remove the LFM2.5/T2 polish and related SmolLM2 fallback; T1 and T2 now reuse the Qwen3.5-2B JD-parse model. Update configs, comments and README to reflect the smaller prefetch set and adjusted baked size (~2.3 GB). Factor ONNX session loading into a new src/retrieve/onnx.py and update granite/reranker to use it. Remove orjson-backed JSON/text atomic writers and their exports, simplify models/llm API (drop polish-related helpers), and clean up imports/logging. Add presentation (docs/Redrob_Submission_SoloLeveling.pptx) and various doc/UI text updates to match the new model policy.
Streamlit: write submission.csv as the final pipeline stage
Hemprasad Badgujar
bc9e92d
The explorer's pipeline stopped at `explain`, so running it in the app
never refreshed the deliverable. Add a `submission` stage so every run
(app or CLI) rewrites submission.csv with the current top-100.
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…
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.
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…
JD prompts:
- Restructure objective/subjective prompts to Role-Task-Context-Constraints-Output;
pin the persona + Mode constraint so Qwen3 skips its thinking loop.
- Make the objective prompt GENERIC (no leaked JD-specific skill/title examples) after
experiments showed listed examples don't drive recall and can anchor/echo.
- Compress the schema 12->7 keys: merge nice_to_have into must_have_skills, merge the
two location fields, drop certifications/target_industries/exclude_companies from the
prompt (they default to [] in _coerce_objective, so the funnel is unaffected). The
leaner prompt freed model capacity -> ~30 skills recall.
- job_titles: keep the verbatim posted title first, add the role family, and always
include the JD's core-activity roles (Search/Ranking/IR Engineer). Generic framing.
- acceptable_countries inferred from cities/"outside <country>"; capture every city.
Recall controls (config):
- Best-of-N objective extraction: parse_temperature (0.5), parse_attem…
Add field-scoped BM25 and composite blending
Hemprasad Badgujar
99deeee
Implement field-scoped BM25 matching (match each JD keyword bag only against its candidate text section), z-normalize per-field scores, and blend them into the composite bm25_score used in composite_narrow.
Changes include:
- Rank logic: introduce field_scoped_bm25, _znorm, and helpers in src/rank/composite.py; define _BM25_FIELDS and use per-field bm25 weights to compute bm25_score and bm25_matched metrics; keep composite as z-blend of potion/soft/bm25.
- Config: expose per-field BM25 weights and document field-scoped behavior in src/config.py and config/settings.yaml.
- Streamlit: update explorer labels and inputs to show BM25 query terms by field, reuse field_scoped_bm25/_BM25_FIELDS for display, and add an option to regenerate JD extraction (live parse) before running the pipeline.
- README: document the field-scoped BM25 approach and show field→JD mappings and blending behavior.
- Tests: update tests/test_composite.py to exercise per-section text columns and adjust expectations f…
Streamlit explorer: inputs, metrics & report
Hemprasad Badgujar
11c79e6
Enhance the Streamlit phase explorer and docs: expand README with a detailed funnel overview and run instructions; enable additional hard_filter integrity toggles in config/settings.yaml. Extend src/streamlit_app.py to surface each phase's driving inputs (filters, JD signals, embedding query), compute engine-accurate per-rule metrics by reusing internal filter helpers, and show score spreads. Add report generation (JSON + Markdown), download buttons and optional persistence to data/reports/, plus helper functions for stats/previewing and richer UI layout. Goal: make the explorer single-sourced with the engine so per-phase inputs, drop counts and score summaries are reproducible and exportable.
Drop disqualifiers field — remove dead JD signal
Hemprasad Badgujar
86788de
disqualifiers was stored on JDSpec and displayed but never consumed by
any filter (hard_filter doesn't read it). Removed it from the OBJECTIVE
prompt, _coerce_objective, JDSpec + _spec_from_mapping, the Streamlit JD
panel, and the test assertion, so there's no orphaned always-empty field.
Add reasoning LLM, perf tweaks, and UI timing
Hemprasad Badgujar
f85f907
Reduce ONNX truncation lengths to 128 for precision and rerank to cut O(n^2) cost. Add a configurable reasoning step (reasoning.mode: template|llm) with small Gemma GGUF client and token cap; implement safe LLM rephrase with cleaning and deterministic fallback. Parallelize JD parse and preprocessing to overlap model load / polars work. Make candidate location visible to BM25 and include JD locations in composite BM25 queries. Update JD parse prompt wording and add a Streamlit timing summary; tests updated to use template reasoning settings.
Add full pipeline, submission & reasoning support
Hemprasad Badgujar
e73e29e
Introduce an end-to-end pipeline and deliverable handling: add pipeline.run_full and a CLI --out flag to run the full funnel and write submission.csv. Add a deterministic reasoning step (reasoning.add_reasoning) to generate one-line recruiter blurbs and a submission writer (rank.write_submission) that normalizes scores, enforces the 100-row validator contract, and atomically writes the CSV. Move JD country hard-drop from soft_filter into composite_narrow and replace the legacy accept_outside_india boolean with an acceptable_countries list across jd.parse, jd.spec, config models, and tests. Add ReasoningSettings and SubmissionSettings to config and update settings.yaml and .gitignore. Wire reasoning into the Streamlit app, export write_submission from rank.__init__, and add tests for reasoning and submission; adjust tests that relied on the old location behavior.
Add ensemble ranking step
Hemprasad Badgujar
9fb0e9b
Introduce a new ensemble ranking stage that z-normalizes and fuses reranker, granite, and composite signals into a weighted final_score, then selects top_n and assigns a 1-based rank. Adds EnsembleSettings (defaults: top_n=100, weights 0.5/0.3/0.2, z_clip=3.0) and YAML config block, implements src/rank/ensemble.py with empty/missing-signal guards, exports ensemble from rank package, integrates it into streamlit_app pipeline, and includes unit tests covering scoring, ranking, missing signals, and empty input.
Add ONNX granite/reranker and compute config
Hemprasad Badgujar
66cf40d
Introduce ONNX-based precision embedding (granite) and cross-encoder reranker with CPU-tuned runtime settings. Adds src/common/runtime.py to set thread-pool env vars and provide onnx session options, and new retrieve modules (granite.py, reranker.py) implementing embedding and pair-scoring via onnxruntime + tokenizers. Wire up configure_compute() in entrypoints (rank.py, src/cli.py, src/streamlit_app.py), expose new retrieve functions in src/retrieve/__init__.py, and extend config (src/config.py and config/settings.yaml) with precision/rerank settings and model defaults. Reduce composite_narrow.top_n to 500 and add precision.top_n=300. Add lightweight tests for passthrough guards (tests/test_granite.py, tests/test_reranker.py) and update pyproject.toml to include onnxruntime and tokenizers dependencies.
Add retrieval & soft/composite ranking stages
Hemprasad Badgujar
08c3203
Introduce broad retrieval (potion Model2Vec) + soft_filter scoring + composite_narrow (BM25 + z-blend) pipeline steps. Adds retrieval implementations (potion static embedder, BM25 tokenizer/scorer), ranking modules (soft_filter, composite_narrow), and tests for bm25, potion, composite, soft_filter and hard_filter behavior. Update config/settings to expose new tuning blocks (broad_retrieval, soft_filter, composite_narrow) and default embedder, refine hard_filter inactive logic and integrity toggles, and export new rank/retrieve APIs. Streamlit explorer updated to include rank stages and show JD spec; pyproject dependencies added model2vec and numpy. These changes narrow the candidate pool deterministically before expensive embedding and blend semantic + lexical + quality signals for the top-N cut.
Add JD parsing, central config, checkpoints & LLM
Hemprasad Badgujar
700e896
Introduce a central Settings system (config/settings.yaml + src/config.py) and enable live JD extraction (problem/job_description.md) via a new JD parsing pipeline (src/jd/*) that emits a unified jd_extraction.json. Add a local GGUF LLM client and helper (src/models/llm.py, src/models/__init__.py) to drive the JD calls and model prefetching. Implement safe atomic file IO and per-phase Parquet checkpoints (src/common/io.py, src/common/checkpoint.py) and wire them into common exports. Expand preprocess functionality (schema overrides, date helper, pipeline exports) and update the CLI to run preprocess and accept a --checkpoints flag. Update Dockerfile to include central config and data, and refresh pyproject dependencies for the new modules. Add a suite of tests for the new components.
Updated
Hemprasad Badgujar
0d4730e
update
Hemprasad Badgujar
28c12aa
Duplicate from streamlit/streamlit-template-space