From 4c44c6eabf50ea6ee3d4b0cf3c65c4109267db29 Mon Sep 17 00:00:00 2001 From: Laurent-Philippe Albou <24249870+lpalbou@users.noreply.github.com> Date: Thu, 29 Oct 2026 01:50:03 +0100 Subject: [PATCH 1/2] 0863: a host's installed engine is offered first; GGUF builds for every curated chat row --- CHANGELOG.md | 9 + .../assets/model_downloads_catalog.json | 507 ++++++++++++++++++ abstractcore/config/model_catalog.py | 23 +- ...e_first_gguf_for_every_curated_chat_row.md | 91 ++++ docs/models.md | 6 +- tests/config/test_model_catalog.py | 53 +- tests/config/test_recommended_text_tiers.py | 44 ++ 7 files changed, 729 insertions(+), 4 deletions(-) create mode 100644 docs/backlog/completed/0863_installed_engine_first_gguf_for_every_curated_chat_row.md diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5763df..62fe557c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **A host's installed engine is offered first in the download catalog** (backlog 0863). On a Linux server with + llama.cpp and neither LM Studio nor Ollama, the Qwen3.5 9B row listed and pre-selected the LM Studio build. + 39 curated chat rows had no GGUF build. Each now has one: a Q4_K_M, read from the Hugging Face API on + 2026-10-29, with its size and revision. A curated pick for an absent engine yields to an artifact an installed + engine serves. Every artifact carries `engine_installed`, and a row lists the pre-selection first, then + installed engines' artifacts. + ## [2.31.1] - 2026-10-28 ### Added diff --git a/abstractcore/assets/model_downloads_catalog.json b/abstractcore/assets/model_downloads_catalog.json index 7e7ffd97..df7a6048 100644 --- a/abstractcore/assets/model_downloads_catalog.json +++ b/abstractcore/assets/model_downloads_catalog.json @@ -784,6 +784,19 @@ "checked": "2026-09-24", "revision": "0943db6e15185b86be368d3cf0704aec740b142b" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3-VL-4B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 2497282336, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "00c00da0690c4b14b5539b02c4ea5d7c9102b35e" + } } ] }, @@ -838,6 +851,19 @@ "checked": "2026-09-24", "revision": "a0093b9b5fda6f76ddd4a462c6830ae7c4fe47ec" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3-VL-8B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 5027785568, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "b93a7ee713758252c555be4210c00540df954dc2" + } } ] }, @@ -893,6 +919,19 @@ "checked": "2026-09-24", "revision": "8794b4f0bce099a0ca7c40980ba7081527321e59" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3-VL-30B-A3B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 18556688096, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "0af19e7479857aa7f3246466a4ad16c7e7299639" + } } ] }, @@ -926,6 +965,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.5-0.8B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 532517120, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "6ab461498e2023f6e3c1baea90a8f0fe38ab64d0" + } } ] }, @@ -1175,6 +1227,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.5-9B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 5680522464, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "3885219b6810b007914f3a7950a8d1b469d598a5" + } } ], "notes": "The portable fresh-install text/vision default (RECOMMENDED_MODEL_DOWNLOADS input.text) and the Apple silicon tier below 24 GiB of unified memory (model_catalog.APPLE_TEXT_TIERS). mlx-community/Qwen3.5-9B-4bit and -MLX-4bit hold byte-identical weights upstream (checked 2026-09-24); the tier names -MLX-4bit.", @@ -1228,6 +1293,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.5-27B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 16740812704, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "3221f178a6b842d04f1fb42f1c413534adcc0a6a" + } } ] }, @@ -1262,6 +1340,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 22016023168, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "bc014a17be43adabd7066b7a86075ff935c6a4e2" + } } ] }, @@ -1317,6 +1408,19 @@ "download_bytes": 55586024670, "size_source": "catalog", "verified": true + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.6-27B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 16817244384, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "82d411acf4a06cfb8d9b073a5211bf410bfc29bf" + } } ] }, @@ -1372,6 +1476,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/Qwen3.6-35B-A3B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 22134528992, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "a483e9e6cbd595906af30beda3187c2663a1118c" + } } ] }, @@ -1839,6 +1956,19 @@ "checked": "2026-09-24", "revision": "c5bfd839cd4cda0e5a39a97e00218d9c56e468af" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Llama-3.3-70B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 42520398432, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "8f14c5c5d06fca109ff16f94496147a8479711de" + } } ] }, @@ -1887,6 +2017,19 @@ "checked": "2026-09-24", "revision": "71083c312662cf2c4665cc72702e0d7f59dc1a35" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 65359900352, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "72a6853f56a66dc13a3a4b6bdc9cf7ee4c364b47" + } } ] }, @@ -1953,6 +2096,19 @@ "checked": "2026-09-24", "revision": "21864e21e7b1130657eb082f68bf1ebd22dc402e" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-3-1b-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 806058272, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "f0b45be0aac41bd6a100a4b5734cad5f67255bfb" + } } ] }, @@ -2019,6 +2175,19 @@ "checked": "2026-09-24", "revision": "566828691bf4c43f8336b62eb9d6bd9a09d74042" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-3-4b-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 2489894016, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "5a3566e716d80f709ed7b79817eaf7733d2a1fce" + } } ] }, @@ -2085,6 +2254,19 @@ "checked": "2026-09-24", "revision": "ffc426b1a214ba5d11f4b580464d42c544b47948" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-3-12b-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 7300778336, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "d15e4c7dc21dc55d56bf8549db57a71ad8a2a35d" + } } ] }, @@ -2152,6 +2334,19 @@ "checked": "2026-09-24", "revision": "c408904bc9a0b7e996173d3cbe1bfe30e04e678d" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-3-27b-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 16546688736, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "7cd0121f2530b00e42c4df952d4cad4418c0b3c1" + } } ] }, @@ -2198,6 +2393,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-3n-E4B-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 4539054208, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "90fa8b0e431faeae50c305828bc260d6f71720e1" + } } ], "notes": "E4B = ~4B effective parameters; ~8B raw parameters are resident." @@ -2277,6 +2485,19 @@ "checked": "2026-09-24", "revision": "bedc5c51905d796b192de24567f358b0b1653d2e" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/gemma-4-26B-A4B-it-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 16947541728, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "c099eb48e663fd284577b04978a94ffccb261841" + } } ] }, @@ -2390,6 +2611,19 @@ "checked": "2026-09-24", "revision": "1db835463eb0f953019af1048077b90403b70424" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 14333922848, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "b750ec2299225e492f1bd27cab88a0a595fa848f" + } } ] }, @@ -2424,6 +2658,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 14334446752, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "6e458b8add42681bfd023de5eab93637694aaf82" + } } ] }, @@ -2458,6 +2705,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/Magistral-Small-2509-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 14333913280, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "77fe877ee8e44eadf97235a1ad77471678eed310" + } } ] }, @@ -2505,6 +2765,19 @@ "checked": "2026-09-24", "revision": "5605fbb7f1f514acf386453211831d5151db8210" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 1117321312, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "3cb4d15544a2a5e07439592b9a0965b6445fbd34" + } } ] }, @@ -2626,6 +2899,19 @@ "checked": "2026-09-24", "revision": "301d65149e35d8f5b2772d84d53ede3adb62c894" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/DeepSeek-R1-0528-Qwen3-8B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 5027785216, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "eb48357c179d34dbf515983f798dfb8752a0f261" + } } ] }, @@ -2672,6 +2958,19 @@ "checked": "2026-09-24", "revision": "2fbbaeaa759a51ea55b6baba8574e340a0a71200" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/DeepSeek-R1-Distill-Qwen-14B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 8988109984, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "7b05b58b41f623e66fc74cd27b35475267b2f3e3" + } } ] }, @@ -2706,6 +3005,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 19851335584, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "1938d05cc893a60f37be1dc16e7465038f4fca63" + } } ] }, @@ -2794,6 +3106,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "ggml-org/gpt-oss-120b-GGUF:MXFP4", + "quant": "mxfp4", + "download_bytes": 63387346208, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "7200b74bea78b0a2964ef9f3897eefa401e890ff" + } } ] }, @@ -2931,6 +3256,19 @@ "checked": "2026-09-24", "revision": "93b273a5497a4fcdcfb7ac00143565506b4f72e4" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 2491874272, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "78eb92a46fc37e6b524df991ed9aca9bc6aa7b80" + } } ] }, @@ -2964,6 +3302,19 @@ "download_bytes": null, "size_source": "unknown", "verified": true + }, + { + "provider": "huggingface", + "artifact": "unsloth/SmolLM2-135M-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 105454144, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "9e6855bc4be717fca1ef21360a1db4b29d5c559a" + } } ] }, @@ -2997,6 +3348,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/SmolLM2-1.7B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 1055609504, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "e933f1cdf73cc87cb67915bf5dd6ea81d36080ca" + } } ] }, @@ -3092,6 +3456,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/granite-4.0-h-tiny-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 4254815392, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "56b37fdc52891304726c4a0a8b140dd1fc1582d5" + } } ] }, @@ -3126,6 +3503,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/GLM-4.7-Flash-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 18312339808, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "0d32489ecb9db6d2a4fc93bd27ef01519f95474d" + } } ] }, @@ -4605,6 +4995,19 @@ "checked": "2026-09-24", "revision": "9a877259e77a2bee9b71dbed3fb0745b94396187" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/QwQ-32B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 19851336288, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "d5faedaaaae5cdacdc487b53d90dcc7210ca218c" + } } ] }, @@ -4642,6 +5045,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "unsloth/granite-4.1-8b-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 5347915136, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "6f9671f73eb03273bc09319194b8a4e810e03a8f" + } } ] }, @@ -4677,6 +5093,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "unsloth/MiniMax-M2-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 138342385120, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "06e952eab9e8e136847e9df067a0032582696778" + } } ] }, @@ -4702,6 +5131,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 10364416768, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "8f248fa2072348f77a8bc37754e470de1f61866e" + } } ] }, @@ -4740,6 +5182,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "second-state/StarCoder2-3B-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 1848976448, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "7fca3e2da2ce31df411461e2cb9cae2d2b492f35" + } } ] }, @@ -4825,6 +5280,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "tiiuae/Falcon3-7B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 4569726368, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "b5eb297a52350fa68f8b88be3ce16a57578ba114" + } } ] }, @@ -4851,6 +5319,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "bartowski/c4ai-command-r7b-12-2024-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 5057009792, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "bfc7a934c45cb839d84c8ca01d87f1cfa51aaa3f" + } } ] }, @@ -4888,6 +5369,19 @@ "method": "ollama_registry", "checked": "2026-09-24" } + }, + { + "provider": "huggingface", + "artifact": "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 4770649728, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "c618bf67338171760c72c3f109f2900cb7d79855" + } } ] }, @@ -4913,6 +5407,19 @@ "download_bytes": null, "size_source": "unknown", "verified": false + }, + { + "provider": "huggingface", + "artifact": "allenai/OLMo-2-1124-7B-Instruct-GGUF:Q4_K_M", + "quant": "q4_k_m", + "download_bytes": 4472020256, + "size_source": "catalog", + "verified": false, + "upstream": { + "method": "hf_api", + "checked": "2026-10-29", + "revision": "410e0069f64869e4b1d17d8de04810b881fd824b" + } } ] }, diff --git a/abstractcore/config/model_catalog.py b/abstractcore/config/model_catalog.py index fce96ce7..4b8cde1f 100644 --- a/abstractcore/config/model_catalog.py +++ b/abstractcore/config/model_catalog.py @@ -1557,6 +1557,12 @@ def _pick_recommended( if not forced: raise LookupError(f"tier artifact {tier_artifact} missing from catalog row {row.get('id')!r}") curated = forced + elif curated and not installed_engines.get(str(curated[0].get("engine"))) and any( + installed_engines.get(str(a.get("engine"))) for a in arts if a.get("supported_on_host") and a.get("downloadable") + ): + # A curated pick for an engine that is NOT installed never beats an artifact an installed + # engine serves (a Linux server with llama.cpp: the HF GGUF, not the LM Studio build). + curated = [] for a in arts: a["recommended"] = False if curated: @@ -1565,17 +1571,28 @@ def _pick_recommended( order = _HOST_PREFERENCE.get(accelerator, _HOST_PREFERENCE["none"]) verdict_rank = {"fits": 0, "tight": 1, "partial_offload": 2, "needs_gpu_limit": 2, "unknown": 3, "too_large": 4} - def rank(a: Mapping[str, Any]) -> Tuple[int, int, int, int]: + def rank(a: Mapping[str, Any]) -> Tuple[int, int, int, int, int]: installed = 0 if (a.get("presence") or {}).get("status") == "installed" else 1 engine_ok = 0 if installed_engines.get(str(a.get("engine"))) else 1 pos = order.index(a["provider"]) if a["provider"] in order else len(order) - return (verdict_rank.get((a.get("fit") or {}).get("verdict", "unknown"), 3), installed, engine_ok, pos) + verdict = verdict_rank.get((a.get("fit") or {}).get("verdict", "unknown"), 3) + # An installed engine first, unless its artifact is too large for this host. + return (1 if verdict >= 4 else 0, engine_ok, verdict, installed, pos) candidates = [a for a in arts if a.get("supported_on_host") and a.get("downloadable")] if candidates: min(candidates, key=rank)["recommended"] = True + +def _order_for_installed_engines(row: Dict[str, Any], installed_engines: Mapping[str, bool]) -> None: + """Mark each artifact `engine_installed` and list the pre-selection first, then what an + INSTALLED engine serves, then the rest (stable): a host with only llama.cpp sees the HF GGUF + before the LM Studio and Ollama builds it cannot run yet.""" + for a in row["artifacts"]: + a["engine_installed"] = bool(installed_engines.get(str(a.get("engine")))) + row["artifacts"].sort(key=lambda a: (0 if a.get("recommended") else 1, 0 if a["engine_installed"] else 1)) + def _matches_query(row: Mapping[str, Any], query: str) -> bool: if not query: return True @@ -1752,6 +1769,7 @@ def catalog( if forced is not None and not any((a["provider"], a["artifact"]) == forced for a in arts): forced = None # an engine filter (`engine=ollama`) removed the tier artifact _pick_recommended(row, accelerator, installed_engines, forced) + _order_for_installed_engines(row, installed_engines) rows_out.append(row) seen_repos = { @@ -1809,6 +1827,7 @@ def catalog( "artifacts": [art], } _pick_recommended(row, accelerator, installed_engines) + _order_for_installed_engines(row, installed_engines) rows_out.append(row) if fits: diff --git a/docs/backlog/completed/0863_installed_engine_first_gguf_for_every_curated_chat_row.md b/docs/backlog/completed/0863_installed_engine_first_gguf_for_every_curated_chat_row.md new file mode 100644 index 00000000..1abb671c --- /dev/null +++ b/docs/backlog/completed/0863_installed_engine_first_gguf_for_every_curated_chat_row.md @@ -0,0 +1,91 @@ +# 0863 — A host's installed engine is offered first: GGUF builds for every curated chat row + +**Status**: completed (2026-10-29, unreleased) · **Priority**: P1 · **Created**: 2026-10-29 +**Cross-package**: operator QA on the OVH release-test server (AbstractGateway console, Models catalog, front item 3). + +## Why +On the Linux server llama.cpp is Ready and neither LM Studio nor Ollama is installed. The console's Models catalog +showed "Hugging Face 0", and the row for the recommended model, Qwen3.5 9B, listed only LM Studio and Ollama +builds, with the LM Studio build pre-selected. Two causes: +- 39 curated chat/coding rows had no GGUF artifact (`huggingface` `repo:QUANT`, served by llama.cpp), among them + every Qwen3.5 row, Qwen3.6 27B/35B-A3B, Gemma 3/3n/4, Llama 3.3/4, Mistral Small 3.2, Devstral, Magistral, the + DeepSeek R1 distills, gpt-oss-120b, Phi-4 mini, Granite 4.x, GLM-4.7 Flash and MiniMax M2. +- `_pick_recommended` let a row's curated pick (the LM Studio starter build) win whenever the host *could* run + its engine, even when that engine was absent and another artifact's engine was installed. Artifacts were listed + in seed order, with no regard to which engine is installed. + +## What shipped +- `abstractcore/assets/model_downloads_catalog.json`: 39 GGUF artifacts, one per row: Q4_K_M (gpt-oss-120b: the + upstream MXFP4, its only full-model quant), mostly unsloth. Each is `upstream: {method: hf_api, checked: + 2026-10-29, revision}`, and its `download_bytes` is the sum of the files the `*QUANT*.gguf` download pattern + selects: one file, or the shards of that one quant (Llama 4 Scout 2, MiniMax M2 3). Not added: Qwen3.8 + Flash-Next, whose hub repo has no plain Q4_K_M (only UD quants and the MTP drafter's Q4_K_M files, which the + pattern would fetch on their own). +- `abstractcore/config/model_catalog.py`: + - `_pick_recommended`: a curated pick whose engine is not installed yields when a downloadable artifact on the + row is served by an installed engine. The fallback rank puts an installed engine before the fit verdict, + unless that artifact is `too_large`. + - `_order_for_installed_engines` (new): every artifact carries `engine_installed`, and a row lists the + pre-selection first, then the artifacts of installed engines, then the rest (a stable sort). + - The Apple text tiers and the forced engine-fallback picks are unchanged. +- `docs/models.md`: the pre-selection and ordering rule. + +## Evidence +- `tests/config/test_model_catalog.py::test_a_host_with_only_llamacpp_is_offered_the_gguf_first`: on a CUDA host + where only llama.cpp is installed, qwen3.5-9b pre-selects and lists first `unsloth/Qwen3.5-9B-GGUF:Q4_K_M`, and + every row lists installed-engine artifacts first. Mutation-checked: it goes red without the curated yield, and + red without the sort. +- `test_every_curated_text_row_has_a_build_llamacpp_serves`: the only chat/coding row without a llama.cpp build + is qwen3.8-flash-next. +- `test_the_portable_starter_is_preselected_off_apple_silicon` now runs with no engine installed. The LM Studio + starter still leads there. +- `tests/config`: 1025 passed. +- Repo ids, sizes and revisions were read with one Hugging Face API request each + (`GET /api/models/?blobs=true`; probe scripts and output in the framework root + `untracked/release-closing/design/catalog-scratch/gguf-0863/`): + `unsloth/Qwen3-VL-4B-Instruct-GGUF:Q4_K_M` (qwen3-vl-4b, revision `00c00da0690c`), + `unsloth/Qwen3-VL-8B-Instruct-GGUF:Q4_K_M` (qwen3-vl-8b, revision `b93a7ee71375`), + `unsloth/Qwen3-VL-30B-A3B-Instruct-GGUF:Q4_K_M` (qwen3-vl-30b-a3b, revision `0af19e747985`), + `unsloth/Qwen3.5-0.8B-GGUF:Q4_K_M` (qwen3.5-0.8b, revision `6ab461498e20`), + `unsloth/Qwen3.5-9B-GGUF:Q4_K_M` (qwen3.5-9b, revision `3885219b6810`), + `unsloth/Qwen3.5-27B-GGUF:Q4_K_M` (qwen3.5-27b, revision `3221f178a6b8`), + `unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M` (qwen3.5-35b-a3b, revision `bc014a17be43`), + `unsloth/Qwen3.6-27B-GGUF:Q4_K_M` (qwen3.6-27b, revision `82d411acf4a0`), + `unsloth/Qwen3.6-35B-A3B-GGUF:Q4_K_M` (qwen3.6-35b-a3b, revision `a483e9e6cbd5`), + `unsloth/Llama-3.3-70B-Instruct-GGUF:Q4_K_M` (llama-3.3-70b, revision `8f14c5c5d06f`), + `unsloth/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M` (llama-4-scout, revision `72a6853f56a6`), + `unsloth/gemma-3-1b-it-GGUF:Q4_K_M` (gemma-3-1b, revision `f0b45be0aac4`), + `unsloth/gemma-3-4b-it-GGUF:Q4_K_M` (gemma-3-4b, revision `5a3566e716d8`), + `unsloth/gemma-3-12b-it-GGUF:Q4_K_M` (gemma-3-12b, revision `d15e4c7dc21d`), + `unsloth/gemma-3-27b-it-GGUF:Q4_K_M` (gemma-3-27b, revision `7cd0121f2530`), + `unsloth/gemma-3n-E4B-it-GGUF:Q4_K_M` (gemma-3n-e4b, revision `90fa8b0e431f`), + `unsloth/gemma-4-26B-A4B-it-GGUF:Q4_K_M` (gemma-4-26b-a4b, revision `c099eb48e663`), + `unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF:Q4_K_M` (mistral-small-3.2-24b, revision `b750ec229922`), + `unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF:Q4_K_M` (devstral-small-2-24b, revision `6e458b8add42`), + `unsloth/Magistral-Small-2509-GGUF:Q4_K_M` (magistral-small-2509, revision `77fe877ee8e4`), + `unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF:Q4_K_M` (deepseek-r1-distill-qwen-1.5b, revision `3cb4d15544a2`), + `unsloth/DeepSeek-R1-0528-Qwen3-8B-GGUF:Q4_K_M` (deepseek-r1-0528-qwen3-8b, revision `eb48357c179d`), + `unsloth/DeepSeek-R1-Distill-Qwen-14B-GGUF:Q4_K_M` (deepseek-r1-distill-qwen-14b, revision `7b05b58b41f6`), + `unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF:Q4_K_M` (deepseek-r1-distill-qwen-32b, revision `1938d05cc893`), + `ggml-org/gpt-oss-120b-GGUF:MXFP4` (gpt-oss-120b, revision `7200b74bea78`), + `unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M` (phi-4-mini, revision `78eb92a46fc3`), + `unsloth/SmolLM2-135M-Instruct-GGUF:Q4_K_M` (smollm2-135m, revision `9e6855bc4be7`), + `unsloth/SmolLM2-1.7B-Instruct-GGUF:Q4_K_M` (smollm2-1.7b, revision `e933f1cdf73c`), + `unsloth/granite-4.0-h-tiny-GGUF:Q4_K_M` (granite-4.0-h-tiny, revision `56b37fdc5289`), + `unsloth/GLM-4.7-Flash-GGUF:Q4_K_M` (glm-4.7-flash, revision `0d32489ecb9d`), + `unsloth/QwQ-32B-GGUF:Q4_K_M` (qwq-32b, revision `d5faedaaaae5`), + `unsloth/granite-4.1-8b-GGUF:Q4_K_M` (granite-4.1-8b, revision `6f9671f73eb0`), + `unsloth/MiniMax-M2-GGUF:Q4_K_M` (minimax-m2, revision `06e952eab9e8`), + `bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF:Q4_K_M` (deepseek-coder-v2-lite, revision `8f248fa20723`), + `second-state/StarCoder2-3B-GGUF:Q4_K_M` (starcoder2-3b, revision `7fca3e2da2ce`), + `tiiuae/Falcon3-7B-Instruct-GGUF:Q4_K_M` (falcon3-7b, revision `b5eb297a5235`), + `bartowski/c4ai-command-r7b-12-2024-GGUF:Q4_K_M` (command-r7b, revision `bfc7a934c45c`), + `LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-GGUF:Q4_K_M` (exaone-3.5-7.8b, revision `c618bf673381`), + `allenai/OLMo-2-1124-7B-Instruct-GGUF:Q4_K_M` (olmo-2-7b, revision `410e0069f648`). + +## Follow-ups +- Not every installed llama-cpp-python build loads every architecture (Qwen3.5's hybrid attention, Gemma 3n, + Granite 4.0-H, GLM-4.7). The architecture-support check before download or selection is batch item (f) of the + same QA. These artifacts are `verified: false` until a real load on the server. +- Vision on llama.cpp needs the repo's `mmproj` file, which the Q4_K_M pattern does not fetch. Text works without + it. diff --git a/docs/models.md b/docs/models.md index 8bc0168a..80baddc8 100644 --- a/docs/models.md +++ b/docs/models.md @@ -147,7 +147,11 @@ joined from AbstractCore's model capability registry, and one entry per artifact the three text-tier rows pre-select their tier build (below), and otherwise the order is MLX, LM Studio, Ollama, Hugging Face. Elsewhere the curated starter wins when the host can run it, then the order is Ollama, LM Studio, Hugging Face. Both prefer artifacts that fit and engines - that are installed. LM Studio and Ollama builds stay listed and downloadable on a Mac; + that are installed, and a curated pick whose engine is absent yields to an artifact an installed + engine serves (a Linux server with only llama.cpp pre-selects the row's GGUF). LM Studio and + Ollama builds stay listed and downloadable on a Mac; +- `engine_installed`: whether the engine that serves this artifact is installed. A row lists its + pre-selection first, then installed engines' artifacts, then the rest; - `starter` (per row): part of this machine's recommended fresh-install set. Exactly one text row is the starter: the row of the recommended text model. diff --git a/tests/config/test_model_catalog.py b/tests/config/test_model_catalog.py index 5346fe90..9e992a52 100644 --- a/tests/config/test_model_catalog.py +++ b/tests/config/test_model_catalog.py @@ -174,7 +174,8 @@ def test_the_starter_text_model_is_preselected_on_apple_silicon(host): assert lms["recommended"] is False and lms["supported_on_host"] is True -def test_the_portable_starter_is_preselected_off_apple_silicon(host): +def test_the_portable_starter_is_preselected_off_apple_silicon(host, monkeypatch): + _no_local_engines(monkeypatch) # with no engine installed the curated LM Studio build leads row = next(r for r in mc.catalog(host=synthetic_host("cuda24"))["rows"] if r["id"] == "qwen3.5-9b") assert [a["artifact"] for a in row["artifacts"] if a["recommended"]] == ["qwen/qwen3.5-9b@q4_k_m"] assert row["starter"] is True @@ -198,6 +199,56 @@ def inventory(*args, **kwargs): monkeypatch.setattr(engines, "engine_inventory", inventory) +def _only_engines(monkeypatch, *installed): + from abstractcore.config import engines + + real = engines.engine_inventory + + def inventory(*args, **kwargs): + out = real(*args, **kwargs) + for e in out["engines"]: + e["installed"] = e["id"] in installed + return out + + monkeypatch.setattr(engines, "engine_inventory", inventory) + + +def test_a_host_with_only_llamacpp_is_offered_the_gguf_first(host, monkeypatch): + """Backlog 0863 (the OVH server): llama.cpp Ready, LM Studio and Ollama absent. The row's + pre-selection and first artifact is the HF GGUF llama.cpp serves, not the curated LM Studio + build; the absent engines' builds stay listed after it.""" + _only_engines(monkeypatch, "llamacpp") + rows = {r["id"]: r for r in mc.catalog(host=synthetic_host("cuda24"))["rows"]} + arts = rows["qwen3.5-9b"]["artifacts"] + first = arts[0] + assert (first["provider"], first["artifact"], first["engine"]) == ( + "huggingface", "unsloth/Qwen3.5-9B-GGUF:Q4_K_M", "llamacpp" + ) + assert first["recommended"] is True and first["engine_installed"] is True + assert [a["provider"] for a in arts if a["recommended"]] == ["huggingface"] + assert {"lmstudio", "ollama"} <= {a["provider"] for a in arts[1:]} + assert not any(a["engine_installed"] for a in arts if a["provider"] in ("lmstudio", "ollama")) + # Installed-engine artifacts are listed before any artifact of an absent engine, on every row. + for row in rows.values(): + flags = [a["engine_installed"] for a in row["artifacts"] if not a["recommended"]] + assert flags == sorted(flags, reverse=True), row["id"] + + +def test_every_curated_text_row_has_a_build_llamacpp_serves(): + """Backlog 0863: no curated chat row offers only engines a llama.cpp-only host lacks. + (Qwen3.8 Flash-Next: the hub has no plain GGUF quant, only its MTP drafter files.)""" + missing = [] + for r in mc.load_seed()["rows"]: + if "chat" not in (r.get("tags") or []) and "coding" not in (r.get("tags") or []): + continue + provs = {a["provider"] for a in r["artifacts"]} + if not provs & {"lmstudio", "ollama", "mlx"}: + continue + if not any(mc._engine_for(a["provider"], a.get("quant")) == "llamacpp" for a in r["artifacts"]): + missing.append(r["id"]) + assert missing == ["qwen3.8-flash-next"] + + def test_mlx_artifacts_are_not_downloadable_off_apple_silicon(host, monkeypatch): _no_local_engines(monkeypatch) rows = mc.catalog(host=synthetic_host("cuda24"))["rows"] diff --git a/tests/config/test_recommended_text_tiers.py b/tests/config/test_recommended_text_tiers.py index ca7ce888..08193769 100644 --- a/tests/config/test_recommended_text_tiers.py +++ b/tests/config/test_recommended_text_tiers.py @@ -591,6 +591,50 @@ def test_every_upstream_artifact_is_eight_bit_or_a_tier_or_a_reverified_id(): "unsloth/LFM2.5-1.2B-Instruct-GGUF:Q4_K_M", "unsloth/LFM2.5-1.2B-Thinking-GGUF:Q4_K_M", } + # GGUF builds for the curated rows that had none (backlog 0863, 2026-10-29): a host whose only + # engine is llama.cpp is offered a build it runs. Every repo id, size and revision read from + # the Hugging Face API (`?blobs=true`, the files the `*QUANT*.gguf` download pattern fetches). + reverified |= { + "LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-GGUF:Q4_K_M", + "allenai/OLMo-2-1124-7B-Instruct-GGUF:Q4_K_M", + "bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF:Q4_K_M", + "bartowski/c4ai-command-r7b-12-2024-GGUF:Q4_K_M", + "ggml-org/gpt-oss-120b-GGUF:MXFP4", + "second-state/StarCoder2-3B-GGUF:Q4_K_M", + "tiiuae/Falcon3-7B-Instruct-GGUF:Q4_K_M", + "unsloth/DeepSeek-R1-0528-Qwen3-8B-GGUF:Q4_K_M", + "unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF:Q4_K_M", + "unsloth/DeepSeek-R1-Distill-Qwen-14B-GGUF:Q4_K_M", + "unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF:Q4_K_M", + "unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF:Q4_K_M", + "unsloth/GLM-4.7-Flash-GGUF:Q4_K_M", + "unsloth/Llama-3.3-70B-Instruct-GGUF:Q4_K_M", + "unsloth/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M", + "unsloth/Magistral-Small-2509-GGUF:Q4_K_M", + "unsloth/MiniMax-M2-GGUF:Q4_K_M", + "unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF:Q4_K_M", + "unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M", + "unsloth/QwQ-32B-GGUF:Q4_K_M", + "unsloth/Qwen3-VL-30B-A3B-Instruct-GGUF:Q4_K_M", + "unsloth/Qwen3-VL-4B-Instruct-GGUF:Q4_K_M", + "unsloth/Qwen3-VL-8B-Instruct-GGUF:Q4_K_M", + "unsloth/Qwen3.5-0.8B-GGUF:Q4_K_M", + "unsloth/Qwen3.5-27B-GGUF:Q4_K_M", + "unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M", + "unsloth/Qwen3.5-9B-GGUF:Q4_K_M", + "unsloth/Qwen3.6-27B-GGUF:Q4_K_M", + "unsloth/Qwen3.6-35B-A3B-GGUF:Q4_K_M", + "unsloth/SmolLM2-1.7B-Instruct-GGUF:Q4_K_M", + "unsloth/SmolLM2-135M-Instruct-GGUF:Q4_K_M", + "unsloth/gemma-3-12b-it-GGUF:Q4_K_M", + "unsloth/gemma-3-1b-it-GGUF:Q4_K_M", + "unsloth/gemma-3-27b-it-GGUF:Q4_K_M", + "unsloth/gemma-3-4b-it-GGUF:Q4_K_M", + "unsloth/gemma-3n-E4B-it-GGUF:Q4_K_M", + "unsloth/gemma-4-26B-A4B-it-GGUF:Q4_K_M", + "unsloth/granite-4.0-h-tiny-GGUF:Q4_K_M", + "unsloth/granite-4.1-8b-GGUF:Q4_K_M", + } reverified |= {r.download["artifact"] for r in RECOMMENDED_MODELS.values()} reverified |= {p.download["artifact"] for r in RECOMMENDED_MODELS.values() for p in r.by_accelerator.values()} # Speech input models offered next to the recommended large-v3 (round 16): Whisper base From 626e901200fa0ac401589e0f5db08298e4c881fe Mon Sep 17 00:00:00 2001 From: Laurent-Philippe Albou <24249870+lpalbou@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:28:09 +0100 Subject: [PATCH 2/2] Regenerate llms-full.txt (scripts/update_llms.py) --- llms-full.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/llms-full.txt b/llms-full.txt index 97a30b9c..b6bb49e6 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -15927,7 +15927,11 @@ joined from AbstractCore's model capability registry, and one entry per artifact the three text-tier rows pre-select their tier build (below), and otherwise the order is MLX, LM Studio, Ollama, Hugging Face. Elsewhere the curated starter wins when the host can run it, then the order is Ollama, LM Studio, Hugging Face. Both prefer artifacts that fit and engines - that are installed. LM Studio and Ollama builds stay listed and downloadable on a Mac; + that are installed, and a curated pick whose engine is absent yields to an artifact an installed + engine serves (a Linux server with only llama.cpp pre-selects the row's GGUF). LM Studio and + Ollama builds stay listed and downloadable on a Mac; +- `engine_installed`: whether the engine that serves this artifact is installed. A row lists its + pre-selection first, then installed engines' artifacts, then the rest; - `starter` (per row): part of this machine's recommended fresh-install set. Exactly one text row is the starter: the row of the recommended text model.