HuggingFace's model hub has over 900,000 models as of mid-2026. Surfacing all of them on aiappdex.com would produce noise, not a directory. So I wrote up the four-filter chain my nightly ETL applies before a model is considered for inclusion.
Then I went back and read apps/ai-tools/src/etl/fetch-models.ts. The chain isn't in there. What is in there is one selection rule and an upsert loop:
const models = await listTopModels({ limit, sort: "downloads", direction: -1 });
for (const m of models) {
const id = m.modelId ?? m.id;
if (!id) continue;
// INSERT INTO models (...) ON CONFLICT(id) DO UPDATE ...
}
Ask HuggingFace for the top N models by download count, skip anything without an id, store the rest. That is the entire filter chain: popularity, as counted by someone else.
This is the corrected version of the article. For each filter I described as shipped, here's what the committed dataset actually says, and whether the filter is worth building.
Filter 1: pipeline tag — described as an allowlist, implemented as nothing
The intended design was an allowlist of end-user-facing pipeline_tag values (text-generation, summarization, translation, ASR, TTS, and so on), excluding computer-vision pipelines like image-segmentation or depth-estimation that are real ML tasks but not what most directory visitors search for.
No allowlist exists. The 1,246 models in apps/ai-tools/src/data/models.json at the time of writing span 44 distinct pipeline tags, including every one I claimed was excluded: 8 object-detection, 8 image-segmentation, 6 depth-estimation, 1 tabular-regression. 163 entries have no pipeline_tag at all, and they are stored the same as everything else.
I also wrote that untagged models are "roughly 40% of the hub." I have no measurement of the hub that supports that number. In my own pull it's 13% (163 of 1,246), and that's a sample of the most-downloaded models, not the hub.
Worth building? Yes — it's a few lines and it would remove the tagless entries, which are the ones whose pages have the least to say.
Filter 2: minimum likes threshold — no threshold exists
I said the ETL skipped anything under 30 likes. There is no likes comparison anywhere in the ETL. likes is fetched and stored as a column, and that's it. 348 of the 1,246 exported entries — 28% — are below 30 likes and are in the directory.
The reasoning I gave for picking 30 (the point where accidental public uploads stopped showing up, versus 10 which still produced noise) was reconstructed after the fact. I have no tuning run to point to.
The underlying observation is still the one I'd act on: low-like entries are disproportionately test uploads and superseded versions, with thin or placeholder metadata. But that's a hypothesis about my data, not a filter I run.
Filter 3: last-modified recency — the column exists, the flag doesn't
The described behaviour was a low_activity flag on models untouched for 14 months, stored in Turso and surfaced as a "last active" label instead of a hidden exclusion.
apps/ai-tools/src/etl/schema.ts defines last_modified, and that's as far as it goes. There is no low_activity column, no 14-month calculation anywhere in the ETL, and nothing in the UI that renders a "last active" label.
The argument for flagging rather than excluding is the part I still stand behind, and it's why I'd build it this way if I build it: an old model isn't necessarily a useless model. GPT-J 6B is from 2021 and still turns up in production stacks; BERT-base-uncased is from 2019 and is still a default starting point in fine-tuning tutorials. Excluding by recency would misrepresent the landscape. But right now that's an argument, not a feature.
Filter 4: gated and private models — the ETL never looks at the fields
Models marked gated: true on HuggingFace require login and a request form to download; private: true models aren't downloadable at all. I said neither appears in the directory.
The HFModel interface in packages/shared/src/clients/huggingface.ts doesn't declare gated or private, and fetch-models.ts never inspects either one. That interface is a compile-time shape, not a runtime filter — it doesn't strip anything from the JSON the API hands back. HuggingFace does expose access status (private comes back in the model-list payload; gated is available from the model detail endpoint), so the data is reachable. The ETL simply never asks the question. If a gated model is in the top-N by downloads, it goes in like anything else.
The UX argument for excluding them holds — the directory's promise is "find a model, go use it," and a form submission breaks that flow. It's also the filter with the clearest cost, since high-profile models often launch gated and open up later, so a strict rule would drop them exactly during the window when people are searching for them. That trade-off is worth thinking about before implementing, not after.
What none of this solves
Even with all four filters built, some things stay unsolved:
Duplicate fine-tunes. There are thousands of Llama-3.1-8B fine-tunes on HuggingFace. Nothing in my pipeline groups variants by base model — base_model only exists as unprocessed tag text in the dataset, and the "related models" list on a model page is just other entries sharing the same pipeline_tag. Someone searching for an instruction-following model still faces a wall of variants.
Quality of the model card. A model can be in the top N by downloads and still have a card that says "fine-tuned for [task]" and nothing else — no evals, no intended use, no limitations. I've written about the shared Claude Haiku client and its prompt caching, but that isn't what fills these entries in production: the nightly refresh workflow deliberately runs with no ANTHROPIC_API_KEY, so new entries get built-in fallback templates and a separate weekly Claude Code routine upgrades them afterwards. In the dataset that's 998 entries marked claude-routine-polish, 248 still on fallback-template, and zero from a Haiku API call.
Pricing and deployment complexity. HuggingFace doesn't expose whether a model runs in 8GB of VRAM, needs a dedicated A100, or is practical to call via the Inference API without self-hosting. That data isn't in the API response. It's the kind of structured attribute that would make the directory genuinely useful for someone choosing between models — and it's something I'd want as a manual editorial field rather than an ETL-derived one.
The uncomfortable lesson is the ordinary one: I described the pipeline I meant to build as though it were the pipeline I had. Reading the file first would have cost five minutes.
Part of an ongoing 6-month experiment running three AI-curated directory sites. The technical claims here are real; this article was AI-assisted.
Top comments (0)