best-engine-ai-helper 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- best_engine_ai_helper-0.3.0/LICENSE +28 -0
- best_engine_ai_helper-0.3.0/PKG-INFO +136 -0
- best_engine_ai_helper-0.3.0/README.md +121 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/__init__.py +82 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/catalog.py +164 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/cli.py +420 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/config.py +149 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/detect.py +372 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/hardware.py +145 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/llm.py +570 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/pull.py +187 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/ralph.py +456 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/recommend.py +268 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/score.py +330 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/sources/__init__.py +14 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/sources/apxml.py +397 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/validate_llm.py +118 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper/validate_vlm.py +181 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/PKG-INFO +136 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/SOURCES.txt +32 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/dependency_links.txt +1 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/entry_points.txt +2 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/requires.txt +4 -0
- best_engine_ai_helper-0.3.0/best_engine_ai_helper.egg-info/top_level.txt +1 -0
- best_engine_ai_helper-0.3.0/pyproject.toml +43 -0
- best_engine_ai_helper-0.3.0/setup.cfg +4 -0
- best_engine_ai_helper-0.3.0/tests/test_catalog.py +140 -0
- best_engine_ai_helper-0.3.0/tests/test_cli.py +167 -0
- best_engine_ai_helper-0.3.0/tests/test_config.py +114 -0
- best_engine_ai_helper-0.3.0/tests/test_detect.py +82 -0
- best_engine_ai_helper-0.3.0/tests/test_hardware.py +107 -0
- best_engine_ai_helper-0.3.0/tests/test_phase0b.py +350 -0
- best_engine_ai_helper-0.3.0/tests/test_recommend.py +78 -0
- best_engine_ai_helper-0.3.0/tests/test_score.py +174 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Warith Harchaoui
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: best-engine-ai-helper
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Pick and pull the best local LLM/VLM for the current hardware.
|
|
5
|
+
Author-email: Warith Harchaoui <warith.harchaoui@gmail.com>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: click>=8.1
|
|
11
|
+
Requires-Dist: psutil>=5.9
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: requests>=2.31
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# best-engine-ai-helper
|
|
17
|
+
|
|
18
|
+
Pick and pull the best local large language model (LLM) or vision-language model (VLM) for the hardware in the current machine.
|
|
19
|
+
|
|
20
|
+
The tool detects available memory (Apple Silicon unified pool, NVIDIA VRAM, or system RAM), consults a bundled model catalog, and selects the highest-scoring model that fits within a configurable safety headroom. After selection, it pulls the model via Ollama, runs two quality gates (the Ralph Loop for prose and the Ralph Eyeball Loop for vision), and writes an environment file that downstream projects source to find the chosen model.
|
|
21
|
+
|
|
22
|
+
Phase 0a (this release) covers detection, catalog, scoring, and the CLI skeleton. Phase 0b adds the pull, validate, and env commands.
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.10 or later
|
|
27
|
+
- [Ollama](https://ollama.com) (Phase 0b only; not needed for `detect` and `recommend`)
|
|
28
|
+
- psutil, PyYAML, click, requests (installed automatically)
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install best-engine-ai-helper
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or from source:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
git clone https://github.com/warith-harchaoui/best-engine-ai-helper
|
|
40
|
+
cd best-engine-ai-helper
|
|
41
|
+
pip install -e .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
# See what hardware this machine has
|
|
48
|
+
best-engine-ai-helper detect
|
|
49
|
+
|
|
50
|
+
# See which models would be selected (no download)
|
|
51
|
+
best-engine-ai-helper recommend
|
|
52
|
+
|
|
53
|
+
# Recommend the best engine(s) for a task, as a report (Markdown + JSON)
|
|
54
|
+
best-engine-ai-helper report --task "product descriptions and image-quality checks" --out engine
|
|
55
|
+
|
|
56
|
+
# Browse the full catalog
|
|
57
|
+
best-engine-ai-helper catalog show
|
|
58
|
+
|
|
59
|
+
# Browse the hardware chip table
|
|
60
|
+
best-engine-ai-helper hardware show
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See [EXAMPLES.md](https://github.com/warith-harchaoui/best-engine-ai-helper/blob/main/EXAMPLES.md) for runnable recipes with expected output.
|
|
64
|
+
|
|
65
|
+
## How selection works
|
|
66
|
+
|
|
67
|
+
Selection weighs three factors, each made explicit in the `report` output so the
|
|
68
|
+
recommendation is reproducible and can be argued with.
|
|
69
|
+
|
|
70
|
+
### 1. Memory fit (will it run on the accelerator?)
|
|
71
|
+
|
|
72
|
+
Available memory comes from the highest-priority probe that succeeds: Apple
|
|
73
|
+
Silicon unified memory (`system_profiler`), NVIDIA VRAM (`nvidia-smi`), AMD VRAM
|
|
74
|
+
(`rocm-smi`), else half of system RAM as a conservative CPU-only estimate.
|
|
75
|
+
|
|
76
|
+
The usable budget is **not** the whole pool. On Apple Silicon, Metal caps GPU
|
|
77
|
+
allocations at about **66%** of unified memory at or below 36 GB and about
|
|
78
|
+
**75%** above it (`recommendedMaxWorkingSetSize`); past that, work spills to the
|
|
79
|
+
CPU and slows sharply. An extra safety `headroom` (default **0.85**) is applied
|
|
80
|
+
on top, reserving room for the OS, your own application, and KV-cache growth as
|
|
81
|
+
context fills. A catalog entry's `ram_gb` is already a peak-inference estimate
|
|
82
|
+
(weights plus a moderate KV cache; roughly weights ÷ 0.7, since weights are only
|
|
83
|
+
about 70% of runtime memory and can approach 2× at long context). A model fits
|
|
84
|
+
when that `ram_gb` is at most the budget.
|
|
85
|
+
|
|
86
|
+
### 2. Task fit (is it good at the job?)
|
|
87
|
+
|
|
88
|
+
A task — even a vague phrase like *"retail descriptions and image-quality
|
|
89
|
+
checks"* — maps to a benchmark axis (`generalist`, `code`, `math`, `ocr`,
|
|
90
|
+
`vision`) and to the model kinds it needs (text ⇒ LLM, anything visual ⇒ VLM).
|
|
91
|
+
Among fitting models, the highest benchmark on that axis wins.
|
|
92
|
+
|
|
93
|
+
### 3. Throughput (how fast will it generate?)
|
|
94
|
+
|
|
95
|
+
Token generation is **memory-bandwidth bound**: each token reads the active
|
|
96
|
+
model from memory once, so the ceiling is `bandwidth ÷ model-size`, derated to
|
|
97
|
+
about 65% for KV-cache reads and overhead. `report` estimates tokens/s per
|
|
98
|
+
candidate from the chip's memory bandwidth, and offers a lighter, faster
|
|
99
|
+
alternative when one is nearly as strong. Bigger is not automatically better: a
|
|
100
|
+
72B model that technically fits may run at a few tokens/s, while an 8–14B model
|
|
101
|
+
leaves headroom and runs several times faster.
|
|
102
|
+
|
|
103
|
+
When nothing fits, the tool falls back to the smallest model and says so.
|
|
104
|
+
|
|
105
|
+
**Sources.** Apple Metal working-set cap (Apple developer docs / apple-specs);
|
|
106
|
+
inference-memory breakdown weights + KV (15–20%) + overhead (5–10%) (local-LLM
|
|
107
|
+
sizing guides); bandwidth-bound decode `tok/s ≈ bandwidth ÷ active-bytes × 0.5–0.8`
|
|
108
|
+
(llama.cpp / MLX community benchmarks). The exact ratios live as documented
|
|
109
|
+
constants in `score.py`.
|
|
110
|
+
|
|
111
|
+
## Model catalog
|
|
112
|
+
|
|
113
|
+
The bundled seed catalog (`models.yaml`) covers 13 models from the Qwen 3, Qwen 2.5, and Gemma 3 families, from 3B to 72B parameters, across Q4_K_M and Q8_0 quantizations. The catalog tracks on-disk size, estimated peak RAM, and benchmark scores from the Open LLM Leaderboard v2 and the OpenVLM Leaderboard.
|
|
114
|
+
|
|
115
|
+
`catalog update` (Phase 0b) refreshes the cache from external sources: the Ollama registry API, the HuggingFace Hub API, the Open LLM Leaderboard v2 dataset, the OpenVLM Leaderboard dataset, and the [ApXML LLM directory](https://apxml.com/models?modelType=open_weight) (open-weight models with VRAM/compute needs and coding benchmarks, consulted regularly). The bundled seed is never modified by the refresh.
|
|
116
|
+
|
|
117
|
+
## Hardware table
|
|
118
|
+
|
|
119
|
+
`hardware.yaml` lists known GPU and Apple Silicon chip configurations with their usable memory (physical pool minus OS and driver overhead). `hardware update` (Phase 0b) refreshes NVIDIA entries from TechPowerUp.
|
|
120
|
+
|
|
121
|
+
## Downstream integration
|
|
122
|
+
|
|
123
|
+
After `best-engine-ai-helper pull` completes, it writes `~/.best-engine-ai-helper/env.sh`:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
export BEST_LLM_TEXT=qwen3-vl:72b
|
|
127
|
+
export BEST_LLM_VISION=qwen3-vl:72b
|
|
128
|
+
export BEST_LLM_BACKEND=ollama
|
|
129
|
+
export BEST_LLM_BASE_URL=http://localhost:11434
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Projects that consume the selected model source this file or read the companion `config.json`.
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
[BSD-3-Clause](https://github.com/warith-harchaoui/best-engine-ai-helper/blob/main/LICENSE). Copyright 2026 Warith Harchaoui.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# best-engine-ai-helper
|
|
2
|
+
|
|
3
|
+
Pick and pull the best local large language model (LLM) or vision-language model (VLM) for the hardware in the current machine.
|
|
4
|
+
|
|
5
|
+
The tool detects available memory (Apple Silicon unified pool, NVIDIA VRAM, or system RAM), consults a bundled model catalog, and selects the highest-scoring model that fits within a configurable safety headroom. After selection, it pulls the model via Ollama, runs two quality gates (the Ralph Loop for prose and the Ralph Eyeball Loop for vision), and writes an environment file that downstream projects source to find the chosen model.
|
|
6
|
+
|
|
7
|
+
Phase 0a (this release) covers detection, catalog, scoring, and the CLI skeleton. Phase 0b adds the pull, validate, and env commands.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Python 3.10 or later
|
|
12
|
+
- [Ollama](https://ollama.com) (Phase 0b only; not needed for `detect` and `recommend`)
|
|
13
|
+
- psutil, PyYAML, click, requests (installed automatically)
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pip install best-engine-ai-helper
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or from source:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
git clone https://github.com/warith-harchaoui/best-engine-ai-helper
|
|
25
|
+
cd best-engine-ai-helper
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
# See what hardware this machine has
|
|
33
|
+
best-engine-ai-helper detect
|
|
34
|
+
|
|
35
|
+
# See which models would be selected (no download)
|
|
36
|
+
best-engine-ai-helper recommend
|
|
37
|
+
|
|
38
|
+
# Recommend the best engine(s) for a task, as a report (Markdown + JSON)
|
|
39
|
+
best-engine-ai-helper report --task "product descriptions and image-quality checks" --out engine
|
|
40
|
+
|
|
41
|
+
# Browse the full catalog
|
|
42
|
+
best-engine-ai-helper catalog show
|
|
43
|
+
|
|
44
|
+
# Browse the hardware chip table
|
|
45
|
+
best-engine-ai-helper hardware show
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See [EXAMPLES.md](https://github.com/warith-harchaoui/best-engine-ai-helper/blob/main/EXAMPLES.md) for runnable recipes with expected output.
|
|
49
|
+
|
|
50
|
+
## How selection works
|
|
51
|
+
|
|
52
|
+
Selection weighs three factors, each made explicit in the `report` output so the
|
|
53
|
+
recommendation is reproducible and can be argued with.
|
|
54
|
+
|
|
55
|
+
### 1. Memory fit (will it run on the accelerator?)
|
|
56
|
+
|
|
57
|
+
Available memory comes from the highest-priority probe that succeeds: Apple
|
|
58
|
+
Silicon unified memory (`system_profiler`), NVIDIA VRAM (`nvidia-smi`), AMD VRAM
|
|
59
|
+
(`rocm-smi`), else half of system RAM as a conservative CPU-only estimate.
|
|
60
|
+
|
|
61
|
+
The usable budget is **not** the whole pool. On Apple Silicon, Metal caps GPU
|
|
62
|
+
allocations at about **66%** of unified memory at or below 36 GB and about
|
|
63
|
+
**75%** above it (`recommendedMaxWorkingSetSize`); past that, work spills to the
|
|
64
|
+
CPU and slows sharply. An extra safety `headroom` (default **0.85**) is applied
|
|
65
|
+
on top, reserving room for the OS, your own application, and KV-cache growth as
|
|
66
|
+
context fills. A catalog entry's `ram_gb` is already a peak-inference estimate
|
|
67
|
+
(weights plus a moderate KV cache; roughly weights ÷ 0.7, since weights are only
|
|
68
|
+
about 70% of runtime memory and can approach 2× at long context). A model fits
|
|
69
|
+
when that `ram_gb` is at most the budget.
|
|
70
|
+
|
|
71
|
+
### 2. Task fit (is it good at the job?)
|
|
72
|
+
|
|
73
|
+
A task — even a vague phrase like *"retail descriptions and image-quality
|
|
74
|
+
checks"* — maps to a benchmark axis (`generalist`, `code`, `math`, `ocr`,
|
|
75
|
+
`vision`) and to the model kinds it needs (text ⇒ LLM, anything visual ⇒ VLM).
|
|
76
|
+
Among fitting models, the highest benchmark on that axis wins.
|
|
77
|
+
|
|
78
|
+
### 3. Throughput (how fast will it generate?)
|
|
79
|
+
|
|
80
|
+
Token generation is **memory-bandwidth bound**: each token reads the active
|
|
81
|
+
model from memory once, so the ceiling is `bandwidth ÷ model-size`, derated to
|
|
82
|
+
about 65% for KV-cache reads and overhead. `report` estimates tokens/s per
|
|
83
|
+
candidate from the chip's memory bandwidth, and offers a lighter, faster
|
|
84
|
+
alternative when one is nearly as strong. Bigger is not automatically better: a
|
|
85
|
+
72B model that technically fits may run at a few tokens/s, while an 8–14B model
|
|
86
|
+
leaves headroom and runs several times faster.
|
|
87
|
+
|
|
88
|
+
When nothing fits, the tool falls back to the smallest model and says so.
|
|
89
|
+
|
|
90
|
+
**Sources.** Apple Metal working-set cap (Apple developer docs / apple-specs);
|
|
91
|
+
inference-memory breakdown weights + KV (15–20%) + overhead (5–10%) (local-LLM
|
|
92
|
+
sizing guides); bandwidth-bound decode `tok/s ≈ bandwidth ÷ active-bytes × 0.5–0.8`
|
|
93
|
+
(llama.cpp / MLX community benchmarks). The exact ratios live as documented
|
|
94
|
+
constants in `score.py`.
|
|
95
|
+
|
|
96
|
+
## Model catalog
|
|
97
|
+
|
|
98
|
+
The bundled seed catalog (`models.yaml`) covers 13 models from the Qwen 3, Qwen 2.5, and Gemma 3 families, from 3B to 72B parameters, across Q4_K_M and Q8_0 quantizations. The catalog tracks on-disk size, estimated peak RAM, and benchmark scores from the Open LLM Leaderboard v2 and the OpenVLM Leaderboard.
|
|
99
|
+
|
|
100
|
+
`catalog update` (Phase 0b) refreshes the cache from external sources: the Ollama registry API, the HuggingFace Hub API, the Open LLM Leaderboard v2 dataset, the OpenVLM Leaderboard dataset, and the [ApXML LLM directory](https://apxml.com/models?modelType=open_weight) (open-weight models with VRAM/compute needs and coding benchmarks, consulted regularly). The bundled seed is never modified by the refresh.
|
|
101
|
+
|
|
102
|
+
## Hardware table
|
|
103
|
+
|
|
104
|
+
`hardware.yaml` lists known GPU and Apple Silicon chip configurations with their usable memory (physical pool minus OS and driver overhead). `hardware update` (Phase 0b) refreshes NVIDIA entries from TechPowerUp.
|
|
105
|
+
|
|
106
|
+
## Downstream integration
|
|
107
|
+
|
|
108
|
+
After `best-engine-ai-helper pull` completes, it writes `~/.best-engine-ai-helper/env.sh`:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
export BEST_LLM_TEXT=qwen3-vl:72b
|
|
112
|
+
export BEST_LLM_VISION=qwen3-vl:72b
|
|
113
|
+
export BEST_LLM_BACKEND=ollama
|
|
114
|
+
export BEST_LLM_BASE_URL=http://localhost:11434
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Projects that consume the selected model source this file or read the companion `config.json`.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
[BSD-3-Clause](https://github.com/warith-harchaoui/best-engine-ai-helper/blob/main/LICENSE). Copyright 2026 Warith Harchaoui.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
best_engine_ai_helper — pick the best local LLM/VLM for the current hardware.
|
|
3
|
+
|
|
4
|
+
Public API (importable without invoking the CLI):
|
|
5
|
+
|
|
6
|
+
from best_engine_ai_helper.detect import platform_name, chip_vendor, available_memory
|
|
7
|
+
from best_engine_ai_helper.catalog import load_catalog, estimate_ram
|
|
8
|
+
from best_engine_ai_helper.hardware import load_hardware, lookup_chip
|
|
9
|
+
from best_engine_ai_helper.score import select, rank, effective_budget
|
|
10
|
+
from best_engine_ai_helper import text_model, vision_model # cheap tag resolvers
|
|
11
|
+
|
|
12
|
+
The CLI entry point is ``best-engine-ai-helper`` (see cli.py). Importing this
|
|
13
|
+
package does not trigger any CLI parsing or subprocess calls.
|
|
14
|
+
|
|
15
|
+
Downstream suite packages that just need "which model tag do I use?" should call
|
|
16
|
+
:func:`text_model` / :func:`vision_model` — they resolve an env override, then
|
|
17
|
+
the selection persisted by ``pull``, then a safe default, without ever probing
|
|
18
|
+
hardware, so they are cheap and deterministic (CI-safe).
|
|
19
|
+
|
|
20
|
+
Author
|
|
21
|
+
------
|
|
22
|
+
Warith Harchaoui <warith.harchaoui@gmail.com>
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from .catalog import estimate_ram, load_catalog
|
|
28
|
+
|
|
29
|
+
# Cheap, deterministic model-tag resolution (env -> persisted config -> default);
|
|
30
|
+
# the entry point downstream packages call to answer "which model do I use?".
|
|
31
|
+
from .config import (
|
|
32
|
+
DEFAULT_TEXT_MODEL,
|
|
33
|
+
DEFAULT_VISION_MODEL,
|
|
34
|
+
load_config,
|
|
35
|
+
resolved_models,
|
|
36
|
+
text_model,
|
|
37
|
+
vision_model,
|
|
38
|
+
)
|
|
39
|
+
from .detect import (
|
|
40
|
+
available_memory,
|
|
41
|
+
chip_name,
|
|
42
|
+
chip_vendor,
|
|
43
|
+
compute_profile,
|
|
44
|
+
platform_name,
|
|
45
|
+
)
|
|
46
|
+
from .hardware import load_hardware, lookup_chip
|
|
47
|
+
|
|
48
|
+
# Export the engine-recommendation function under a distinct name so it does
|
|
49
|
+
# not shadow the ``recommend`` submodule on attribute access.
|
|
50
|
+
from .recommend import parse_task, to_markdown, write_report
|
|
51
|
+
from .recommend import recommend as recommend_engines
|
|
52
|
+
from .score import effective_budget, estimated_tokens_per_second, rank, select
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"platform_name",
|
|
56
|
+
"chip_vendor",
|
|
57
|
+
"chip_name",
|
|
58
|
+
"available_memory",
|
|
59
|
+
"compute_profile",
|
|
60
|
+
"load_catalog",
|
|
61
|
+
"estimate_ram",
|
|
62
|
+
"load_hardware",
|
|
63
|
+
"lookup_chip",
|
|
64
|
+
"select",
|
|
65
|
+
"rank",
|
|
66
|
+
"effective_budget",
|
|
67
|
+
"estimated_tokens_per_second",
|
|
68
|
+
"recommend_engines",
|
|
69
|
+
"parse_task",
|
|
70
|
+
"to_markdown",
|
|
71
|
+
"write_report",
|
|
72
|
+
"text_model",
|
|
73
|
+
"vision_model",
|
|
74
|
+
"resolved_models",
|
|
75
|
+
"load_config",
|
|
76
|
+
"DEFAULT_TEXT_MODEL",
|
|
77
|
+
"DEFAULT_VISION_MODEL",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
__version__ = "0.3.0"
|
|
81
|
+
__author__ = "Warith Harchaoui"
|
|
82
|
+
__email__ = "warith.harchaoui@gmail.com"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
catalog — load and merge the bundled model catalog with the user's cache.
|
|
3
|
+
|
|
4
|
+
The catalog lives in two layers:
|
|
5
|
+
|
|
6
|
+
1. The **bundled seed** (`models.yaml` in the package root). Hand-maintained,
|
|
7
|
+
always present, never deleted by auto-refresh.
|
|
8
|
+
2. The **user cache** (`~/.best-engine-ai-helper/catalog_cache.yaml`). Written
|
|
9
|
+
by `catalog update`; entries keyed by `id` overwrite matching seed entries.
|
|
10
|
+
Absent on first run; load_catalog silently skips it in that case.
|
|
11
|
+
|
|
12
|
+
Keeping the seed immutable and the cache additive means offline machines always
|
|
13
|
+
have a usable catalog and updates never lose hand-curated data.
|
|
14
|
+
|
|
15
|
+
Author
|
|
16
|
+
------
|
|
17
|
+
Warith Harchaoui <warith.harchaoui@gmail.com>
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import yaml
|
|
26
|
+
|
|
27
|
+
# Root of the installed package; models.yaml sits next to pyproject.toml
|
|
28
|
+
_PACKAGE_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
|
|
30
|
+
# Default path for the bundled seed catalog
|
|
31
|
+
_SEED_PATH = _PACKAGE_ROOT / "models.yaml"
|
|
32
|
+
|
|
33
|
+
# User-writable runtime directory; created on first run
|
|
34
|
+
_USER_DIR = Path.home() / ".best-engine-ai-helper"
|
|
35
|
+
_CACHE_PATH = _USER_DIR / "catalog_cache.yaml"
|
|
36
|
+
|
|
37
|
+
# Quant-specific overhead factors for estimating peak inference RAM.
|
|
38
|
+
# Values are empirically derived: KV cache at 4K context adds ~10-20%
|
|
39
|
+
# beyond on-disk size depending on quantization precision.
|
|
40
|
+
_QUANT_OVERHEAD: dict[str, float] = {
|
|
41
|
+
"Q4_K_M": 1.12,
|
|
42
|
+
"Q8_0": 1.10,
|
|
43
|
+
"FP16": 1.05,
|
|
44
|
+
"Q2_K": 1.20,
|
|
45
|
+
}
|
|
46
|
+
_DEFAULT_OVERHEAD = 1.15 # fallback for unknown quant identifiers
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def estimate_ram(disk_gb: float, quant: str) -> float:
|
|
50
|
+
"""
|
|
51
|
+
Estimate peak inference RAM from on-disk model size.
|
|
52
|
+
|
|
53
|
+
The estimate covers the model weights plus KV cache at the default context
|
|
54
|
+
length (4K tokens). For models with very large context windows (256K+),
|
|
55
|
+
actual RAM may exceed this estimate significantly; treat it as a lower bound.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
disk_gb : float
|
|
60
|
+
On-disk footprint of the model in GB.
|
|
61
|
+
quant : str
|
|
62
|
+
Quantization identifier, e.g. 'Q4_K_M', 'Q8_0', 'FP16', 'Q2_K'.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
float
|
|
67
|
+
Estimated peak RAM in GB.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> estimate_ram(6.1, 'Q4_K_M')
|
|
72
|
+
6.832
|
|
73
|
+
>>> estimate_ram(10.0, 'FP16')
|
|
74
|
+
10.5
|
|
75
|
+
"""
|
|
76
|
+
overhead = _QUANT_OVERHEAD.get(quant, _DEFAULT_OVERHEAD)
|
|
77
|
+
return round(disk_gb * overhead, 3)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_yaml_file(path: Path) -> list[dict[str, Any]]:
|
|
81
|
+
"""
|
|
82
|
+
Load a YAML file and return its contents as a list of dicts.
|
|
83
|
+
|
|
84
|
+
Returns an empty list on FileNotFoundError so callers can treat the
|
|
85
|
+
absence of the cache file as a no-op rather than an error.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
path : Path
|
|
90
|
+
Absolute path to the YAML file.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
list[dict[str, Any]]
|
|
95
|
+
Parsed entries, or [] if the file is absent or empty.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
99
|
+
except FileNotFoundError:
|
|
100
|
+
return []
|
|
101
|
+
# yaml.safe_load returns None for empty files
|
|
102
|
+
if raw is None:
|
|
103
|
+
return []
|
|
104
|
+
return list(raw)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load_catalog(catalog_path: Path | None = None) -> list[dict[str, Any]]:
|
|
108
|
+
"""
|
|
109
|
+
Load the bundled seed catalog merged with the user's local cache.
|
|
110
|
+
|
|
111
|
+
Cache entries whose `id` matches a seed entry overwrite the seed entry.
|
|
112
|
+
New cache entries (no matching seed id) are appended. The seed is never
|
|
113
|
+
modified on disk.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
catalog_path : Path or None
|
|
118
|
+
Path to the seed `models.yaml`. Defaults to the bundled file next to
|
|
119
|
+
`pyproject.toml`. Pass an explicit path in tests to use a fixture.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
list[dict[str, Any]]
|
|
124
|
+
Merged model entries. Each entry is guaranteed to have at minimum:
|
|
125
|
+
``id``, ``kind``, ``ram_gb``, ``benchmarks``.
|
|
126
|
+
|
|
127
|
+
Raises
|
|
128
|
+
------
|
|
129
|
+
FileNotFoundError
|
|
130
|
+
If ``catalog_path`` is given explicitly and does not exist.
|
|
131
|
+
|
|
132
|
+
Examples
|
|
133
|
+
--------
|
|
134
|
+
>>> entries = load_catalog()
|
|
135
|
+
>>> len(entries) > 0
|
|
136
|
+
True
|
|
137
|
+
>>> all('id' in e for e in entries)
|
|
138
|
+
True
|
|
139
|
+
"""
|
|
140
|
+
seed_path = catalog_path if catalog_path is not None else _SEED_PATH
|
|
141
|
+
|
|
142
|
+
# Start with the bundled seed; this must exist
|
|
143
|
+
seed = _load_yaml_file(seed_path)
|
|
144
|
+
if not seed and catalog_path is not None:
|
|
145
|
+
raise FileNotFoundError(f"Catalog not found: {seed_path}")
|
|
146
|
+
|
|
147
|
+
# Build a lookup by id so cache entries can overwrite in O(1)
|
|
148
|
+
merged: dict[str, dict[str, Any]] = {e["id"]: e for e in seed}
|
|
149
|
+
|
|
150
|
+
# Overlay user cache entries; absent cache is silently ignored
|
|
151
|
+
for entry in _load_yaml_file(_CACHE_PATH):
|
|
152
|
+
merged[entry["id"]] = entry
|
|
153
|
+
|
|
154
|
+
# Preserve seed ordering; append cache-only entries at the end
|
|
155
|
+
result: list[dict[str, Any]] = []
|
|
156
|
+
seen: set[str] = set()
|
|
157
|
+
for e in seed:
|
|
158
|
+
result.append(merged[e["id"]])
|
|
159
|
+
seen.add(e["id"])
|
|
160
|
+
for eid, entry in merged.items():
|
|
161
|
+
if eid not in seen:
|
|
162
|
+
result.append(entry)
|
|
163
|
+
|
|
164
|
+
return result
|