ltcai 12.3.0 → 12.5.0
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.
- package/README.md +25 -39
- package/docs/CHANGELOG.md +44 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +11 -11
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/ROADMAP.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/models.py +21 -1
- package/latticeai/api/search.py +8 -1
- package/latticeai/api/worker_compute.py +9 -1
- package/latticeai/core/embedding_providers/__init__.py +14 -0
- package/latticeai/core/embedding_providers/autodetect.py +91 -24
- package/latticeai/core/embedding_providers/base.py +10 -2
- package/latticeai/core/embedding_providers/profiles.py +44 -0
- package/latticeai/core/embedding_providers/text.py +325 -2
- package/latticeai/models/model_providers.py +2 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/embedding_recommendation.py +229 -0
- package/latticeai/services/model_capability_registry.py +114 -0
- package/latticeai/services/model_catalog.py +148 -0
- package/latticeai/services/model_engines.py +3 -1
- package/latticeai/services/product_readiness.py +1 -1
- package/package.json +3 -3
- package/requirements.txt +3 -1
- package/scripts/bump_version.py +106 -6
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_screenshot_pixel_delta.py +6 -2
- package/scripts/release_screen_claims.json +11 -0
- package/src-tauri/Cargo.lock +10 -10
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +46 -47
- package/static/app/assets/Act-DQ2Ldwo8.js +2 -0
- package/static/app/assets/AdminConsole-y6EjiXeH.js +1 -0
- package/static/app/assets/Brain-CRWWw-h9.js +2 -0
- package/static/app/assets/BrainHome-cmvTu7Ea.js +2 -0
- package/static/app/assets/BrainSignals-gaNn5E4-.js +1 -0
- package/static/app/assets/Capture-BJK7in1j.js +1 -0
- package/static/app/assets/Chronicle-BuYZ9lMt.js +1 -0
- package/static/app/assets/CommandPalette-BjFwuKaA.js +1 -0
- package/static/app/assets/DigitalBrainExplorer-DTBRPYJ3.js +321 -0
- package/static/app/assets/Library-BsmZ-YVh.js +1 -0
- package/static/app/assets/LivingBrain-aT9hgkPV.js +1 -0
- package/static/app/assets/ProductFlow-Ct0QLLY5.js +1 -0
- package/static/app/assets/{QueryClientProvider-B3OjqSyJ.js → QueryClientProvider-CTiSBeU6.js} +1 -1
- package/static/app/assets/ReviewCard-CdooYtWr.js +3 -0
- package/static/app/assets/RunsListPanel-Btf2DIrG.js +1 -0
- package/static/app/assets/System-B77M4t-M.js +1 -0
- package/static/app/assets/WorkflowGraph-u3ZKLvZe.js +1 -0
- package/static/app/assets/WorkflowsPanel--nTFq3Cs.js +2 -0
- package/static/app/assets/actHelpers-xNnCfUeC.js +1 -0
- package/static/app/assets/arrow-left-Cp4NHbfX.js +1 -0
- package/static/app/assets/{bot-CAfN5QD1.js → bot-CXwvpODC.js} +1 -1
- package/static/app/assets/{brain-DNNLFQgq.js → brain-BzjoGgor.js} +1 -1
- package/static/app/assets/{button-D0k7_k6e.js → button-_hHOhp7g.js} +1 -1
- package/static/app/assets/circle-check-qG5dMahn.js +1 -0
- package/static/app/assets/{circle-pause-Dxw6SJuA.js → circle-pause-DBOqYYMh.js} +1 -1
- package/static/app/assets/{circle-play-DuoLDLEg.js → circle-play-DA0Vf2NH.js} +1 -1
- package/static/app/assets/{cpu-BGOpEdGX.js → cpu-DwsE0_o3.js} +1 -1
- package/static/app/assets/{download-wXD9L5vN.js → download-iCoe2f2Q.js} +1 -1
- package/static/app/assets/{folder-open-CittV8Ey.js → folder-open-C1vNOMhy.js} +1 -1
- package/static/app/assets/{hard-drive-CLQPYYBF.js → hard-drive-a73KaYth.js} +1 -1
- package/static/app/assets/index-Dbb2YhYw.css +2 -0
- package/static/app/assets/index-DpuEeYmP.js +13 -0
- package/static/app/assets/{input-RZ8aKj6d.js → input-BclQNHyj.js} +1 -1
- package/static/app/assets/jsx-runtime-CU_pEDDh.js +1 -0
- package/static/app/assets/{link-2-B7QYeNdJ.js → link-2-D23wihBe.js} +1 -1
- package/static/app/assets/{permissionCopy-DFGEuU2d.js → permissionCopy-DiGZRKAY.js} +1 -1
- package/static/app/assets/primitives-1S-XCgmz.js +1 -0
- package/static/app/assets/search-Cl2k5uhD.js +1 -0
- package/static/app/assets/{share-2-Drg0MPgx.js → share-2-47_av_aF.js} +1 -1
- package/static/app/assets/{shield-alert-CFKdCGM8.js → shield-alert-BvKq2P-G.js} +1 -1
- package/static/app/assets/{textarea-n_69mQP_.js → textarea-DcLO52bu.js} +1 -1
- package/static/app/assets/useFocusTrap-CpqVPzJ_.js +1 -0
- package/static/app/assets/{useMutation-Dvcq6J6b.js → useMutation-DhI3jqCN.js} +1 -1
- package/static/app/assets/workspace-DwNHbqaX.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Act-CZXyMcfJ.js +0 -2
- package/static/app/assets/AdminConsole-CAkZk0BI.js +0 -1
- package/static/app/assets/Brain-DN_7_HSV.js +0 -2
- package/static/app/assets/BrainHome-BBP0ouU-.js +0 -2
- package/static/app/assets/BrainSignals-nyRNxvQ3.js +0 -1
- package/static/app/assets/Capture-DBvFoZKL.js +0 -1
- package/static/app/assets/Chronicle-BC5zmR44.js +0 -1
- package/static/app/assets/CommandPalette-Bs-Fk6HT.js +0 -1
- package/static/app/assets/DigitalBrainExplorer-CNcCktPP.js +0 -321
- package/static/app/assets/Library-CxUA4xai.js +0 -1
- package/static/app/assets/LivingBrain-CiyjvwKy.js +0 -1
- package/static/app/assets/ProductFlow-DccERGr3.js +0 -1
- package/static/app/assets/ReviewCard-lawM1khV.js +0 -3
- package/static/app/assets/RunsListPanel-DQC-zQ7m.js +0 -1
- package/static/app/assets/System-DwqYJQaj.js +0 -1
- package/static/app/assets/WorkflowGraph-pJ2eFrhy.js +0 -1
- package/static/app/assets/WorkflowsPanel-BIGjfM7h.js +0 -2
- package/static/app/assets/actHelpers-DfIqS92u.js +0 -1
- package/static/app/assets/arrow-left-DvwQxqcD.js +0 -1
- package/static/app/assets/circle-check-hH6updC-.js +0 -1
- package/static/app/assets/index-C0VEkbAp.js +0 -13
- package/static/app/assets/index-C8gwOf0C.css +0 -2
- package/static/app/assets/jsx-runtime-bzQ4Vb5N.js +0 -1
- package/static/app/assets/primitives-EKxh12KR.js +0 -1
- package/static/app/assets/search-BA5Ge197.js +0 -1
- package/static/app/assets/sourceMeta-BzqJiEPI.js +0 -1
- package/static/app/assets/useFocusTrap-B0w4AxK1.js +0 -1
- package/static/app/assets/workspace-BgBHikYl.js +0 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Embedding model capability registry and PC-tailored hardware recommendations.
|
|
2
|
+
|
|
3
|
+
Dedicated module to keep `model_capability_registry.py` under the 1000-line cap
|
|
4
|
+
while providing first-class Google EmbeddingGemma 2 / 300M recommendation logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_embedding_capabilities(
|
|
13
|
+
model_capability_cls: type,
|
|
14
|
+
hardware_profile_cls: type,
|
|
15
|
+
verification_status_cls: type,
|
|
16
|
+
) -> List[Any]:
|
|
17
|
+
"""Construct ModelCapability instances for EmbeddingGemma 2 / 300M."""
|
|
18
|
+
return [
|
|
19
|
+
model_capability_cls(
|
|
20
|
+
id="google/embeddinggemma-2",
|
|
21
|
+
hf_repo_id="google/embeddinggemma-2",
|
|
22
|
+
name="Google EmbeddingGemma 2",
|
|
23
|
+
family="EmbeddingGemma",
|
|
24
|
+
tag="local-embedding",
|
|
25
|
+
size="1.5GB",
|
|
26
|
+
modality="text",
|
|
27
|
+
architecture="gemma3",
|
|
28
|
+
download_size_gb=1.48,
|
|
29
|
+
quantization="FP16",
|
|
30
|
+
provider_hints=["local_mlx", "ollama", "vllm", "lmstudio", "llamacpp"],
|
|
31
|
+
download_strategy="hf_hub",
|
|
32
|
+
load_strategy="sentence_transformers",
|
|
33
|
+
hardware=hardware_profile_cls(
|
|
34
|
+
min_ram_gb=4.0, recommended_ram_gb=8.0, apple_silicon_pref=True, cuda_pref=True,
|
|
35
|
+
notes="Google DeepMind 740M multilingual & code semantic embedding model (768d). Uses ~1.5GB in FP16 HF or ~0.5GB in 4-bit MLX.",
|
|
36
|
+
),
|
|
37
|
+
source_country="미국", source_company="Google",
|
|
38
|
+
verification=verification_status_cls(
|
|
39
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
40
|
+
has_weights_hint=True, pipeline_tag="feature-extraction", likes=120, license="apache-2.0",
|
|
41
|
+
notes="Hugging Face google/embeddinggemma-2, 768d semantic embeddings.", verified_by="hf-api-light",
|
|
42
|
+
),
|
|
43
|
+
recommended_default=True,
|
|
44
|
+
display_priority=70,
|
|
45
|
+
category="embedding",
|
|
46
|
+
task="embedding",
|
|
47
|
+
dimensions=768,
|
|
48
|
+
),
|
|
49
|
+
model_capability_cls(
|
|
50
|
+
id="mlx-community/embeddinggemma-2-4bit",
|
|
51
|
+
hf_repo_id="mlx-community/embeddinggemma-2-4bit",
|
|
52
|
+
name="Google EmbeddingGemma 2 4-bit",
|
|
53
|
+
family="EmbeddingGemma",
|
|
54
|
+
tag="local-embedding",
|
|
55
|
+
size="0.4GB",
|
|
56
|
+
modality="text",
|
|
57
|
+
architecture="gemma3",
|
|
58
|
+
download_size_gb=0.42,
|
|
59
|
+
quantization="4bit",
|
|
60
|
+
provider_hints=["local_mlx"],
|
|
61
|
+
download_strategy="hf_hub",
|
|
62
|
+
load_strategy="mlx_embeddings",
|
|
63
|
+
hardware=hardware_profile_cls(
|
|
64
|
+
min_ram_gb=4.0, recommended_ram_gb=8.0, apple_silicon_pref=True,
|
|
65
|
+
notes="Apple Silicon MLX 4-bit quantized EmbeddingGemma 2 (768d, 0.42GB on disk, ~0.5GB RAM).",
|
|
66
|
+
),
|
|
67
|
+
source_country="미국", source_company="Google",
|
|
68
|
+
verification=verification_status_cls(
|
|
69
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
70
|
+
has_weights_hint=True, pipeline_tag="feature-extraction", likes=45, license="apache-2.0",
|
|
71
|
+
notes="library_name=mlx, ungated, 4-bit quantized EmbeddingGemma 2.", verified_by="hf-api-light",
|
|
72
|
+
),
|
|
73
|
+
recommended_default=True,
|
|
74
|
+
display_priority=71,
|
|
75
|
+
category="embedding",
|
|
76
|
+
task="embedding",
|
|
77
|
+
dimensions=768,
|
|
78
|
+
),
|
|
79
|
+
model_capability_cls(
|
|
80
|
+
id="google/embeddinggemma-300m",
|
|
81
|
+
hf_repo_id="google/embeddinggemma-300m",
|
|
82
|
+
name="Google EmbeddingGemma 300M",
|
|
83
|
+
family="EmbeddingGemma",
|
|
84
|
+
tag="local-embedding",
|
|
85
|
+
size="0.6GB",
|
|
86
|
+
modality="text",
|
|
87
|
+
architecture="gemma3",
|
|
88
|
+
download_size_gb=0.60,
|
|
89
|
+
quantization="FP16",
|
|
90
|
+
provider_hints=["local_mlx", "ollama", "vllm", "lmstudio", "llamacpp"],
|
|
91
|
+
download_strategy="hf_hub",
|
|
92
|
+
load_strategy="sentence_transformers",
|
|
93
|
+
hardware=hardware_profile_cls(
|
|
94
|
+
min_ram_gb=2.0, recommended_ram_gb=4.0, apple_silicon_pref=True, cuda_pref=True,
|
|
95
|
+
notes="Compact 300M multilingual semantic embedding model (768d) for constrained-RAM PCs (<8GB).",
|
|
96
|
+
),
|
|
97
|
+
source_country="미국", source_company="Google",
|
|
98
|
+
verification=verification_status_cls(
|
|
99
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
100
|
+
has_weights_hint=True, pipeline_tag="feature-extraction", likes=85, license="apache-2.0",
|
|
101
|
+
notes="Hugging Face google/embeddinggemma-300m, 768d semantic embeddings.", verified_by="hf-api-light",
|
|
102
|
+
),
|
|
103
|
+
recommended_default=False,
|
|
104
|
+
display_priority=72,
|
|
105
|
+
category="embedding",
|
|
106
|
+
task="embedding",
|
|
107
|
+
dimensions=768,
|
|
108
|
+
),
|
|
109
|
+
model_capability_cls(
|
|
110
|
+
id="mlx-community/embeddinggemma-300m-4bit",
|
|
111
|
+
hf_repo_id="mlx-community/embeddinggemma-300m-4bit",
|
|
112
|
+
name="Google EmbeddingGemma 300M 4-bit",
|
|
113
|
+
family="EmbeddingGemma",
|
|
114
|
+
tag="local-embedding",
|
|
115
|
+
size="0.3GB",
|
|
116
|
+
modality="text",
|
|
117
|
+
architecture="gemma3",
|
|
118
|
+
download_size_gb=0.30,
|
|
119
|
+
quantization="4bit",
|
|
120
|
+
provider_hints=["local_mlx"],
|
|
121
|
+
download_strategy="hf_hub",
|
|
122
|
+
load_strategy="mlx_embeddings",
|
|
123
|
+
hardware=hardware_profile_cls(
|
|
124
|
+
min_ram_gb=2.0, recommended_ram_gb=4.0, apple_silicon_pref=True,
|
|
125
|
+
notes="Ultralight 4-bit MLX EmbeddingGemma 300M (~0.3GB RAM) for low-memory Macs.",
|
|
126
|
+
),
|
|
127
|
+
source_country="미국", source_company="Google",
|
|
128
|
+
verification=verification_status_cls(
|
|
129
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
130
|
+
has_weights_hint=True, pipeline_tag="feature-extraction", likes=30, license="apache-2.0",
|
|
131
|
+
notes="library_name=mlx, ungated, 4-bit quantized EmbeddingGemma 300M.", verified_by="hf-api-light",
|
|
132
|
+
),
|
|
133
|
+
recommended_default=False,
|
|
134
|
+
display_priority=73,
|
|
135
|
+
category="embedding",
|
|
136
|
+
task="embedding",
|
|
137
|
+
dimensions=768,
|
|
138
|
+
),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def recommend_embedding_for_hardware(
|
|
143
|
+
*,
|
|
144
|
+
ram_gb: Optional[float] = None,
|
|
145
|
+
apple_silicon: bool = False,
|
|
146
|
+
cuda: bool = False,
|
|
147
|
+
get_capability_fn: Optional[Callable[[str], Any]] = None,
|
|
148
|
+
) -> Dict[str, Any]:
|
|
149
|
+
"""Return a PC-tailored embedding model recommendation accounting for RAM & accelerator."""
|
|
150
|
+
effective_ram = 16.0 if ram_gb is None or ram_gb <= 0 else float(ram_gb)
|
|
151
|
+
cap = get_capability_fn("google/embeddinggemma-2") if get_capability_fn else None
|
|
152
|
+
mlx_cap = get_capability_fn("mlx-community/embeddinggemma-2-4bit") if get_capability_fn else None
|
|
153
|
+
|
|
154
|
+
if effective_ram >= 8.0:
|
|
155
|
+
use_mlx = bool(apple_silicon and mlx_cap is not None)
|
|
156
|
+
footprint_gb = 0.5 if use_mlx else 1.5
|
|
157
|
+
chosen_cap = cap or mlx_cap
|
|
158
|
+
base = chosen_cap.to_legacy_dict() if chosen_cap is not None else {}
|
|
159
|
+
return {
|
|
160
|
+
**base,
|
|
161
|
+
"id": "google/embeddinggemma-2",
|
|
162
|
+
"model_id": "google/embeddinggemma-2",
|
|
163
|
+
"hf_repo_id": "google/embeddinggemma-2",
|
|
164
|
+
"mlx_repo_id": "mlx-community/embeddinggemma-2-4bit",
|
|
165
|
+
"recommended_load_id": "mlx-community/embeddinggemma-2-4bit" if use_mlx else "google/embeddinggemma-2",
|
|
166
|
+
"recommended_engine": "local_mlx" if use_mlx else "huggingface",
|
|
167
|
+
"provider": "mlx" if use_mlx else "huggingface",
|
|
168
|
+
"profile_id": "mlx:embeddinggemma-2" if use_mlx else "local:embeddinggemma-2",
|
|
169
|
+
"name": "Google EmbeddingGemma 2",
|
|
170
|
+
"display_name": "Google EmbeddingGemma 2",
|
|
171
|
+
"family": "EmbeddingGemma",
|
|
172
|
+
"category": "embedding",
|
|
173
|
+
"role": "embedding",
|
|
174
|
+
"task": "embedding",
|
|
175
|
+
"tag": "local-embedding",
|
|
176
|
+
"modality": "text",
|
|
177
|
+
"dimensions": 768,
|
|
178
|
+
"size": "0.4GB" if use_mlx else "1.5GB",
|
|
179
|
+
"download_size": "0.4GB" if use_mlx else "1.5GB",
|
|
180
|
+
"download_size_gb": 0.42 if use_mlx else 1.48,
|
|
181
|
+
"memory_footprint_gb": footprint_gb,
|
|
182
|
+
"effective_llm_ram_gb": max(0.0, round(effective_ram - footprint_gb, 2)),
|
|
183
|
+
"status": "recommended",
|
|
184
|
+
"load_status": "ready",
|
|
185
|
+
"supported": True,
|
|
186
|
+
"recommended_default": True,
|
|
187
|
+
"display_priority": 70,
|
|
188
|
+
"reason": (
|
|
189
|
+
f"Apple Silicon ({int(round(effective_ram))}GB RAM) 맞춤 4-bit MLX / Hugging Face 시맨틱 임베딩 모델 (Google EmbeddingGemma 2, 768차원)"
|
|
190
|
+
if apple_silicon
|
|
191
|
+
else f"{int(round(effective_ram))}GB RAM PC 맞춤 Hugging Face 시맨틱 임베딩 모델 (Google EmbeddingGemma 2, 768차원)"
|
|
192
|
+
),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
cap_300m = get_capability_fn("google/embeddinggemma-300m") if get_capability_fn else None
|
|
196
|
+
use_mlx = bool(apple_silicon)
|
|
197
|
+
footprint_gb = 0.3
|
|
198
|
+
base = cap_300m.to_legacy_dict() if cap_300m else {}
|
|
199
|
+
return {
|
|
200
|
+
**base,
|
|
201
|
+
"id": "google/embeddinggemma-300m",
|
|
202
|
+
"model_id": "google/embeddinggemma-300m",
|
|
203
|
+
"hf_repo_id": "google/embeddinggemma-300m",
|
|
204
|
+
"mlx_repo_id": "mlx-community/embeddinggemma-300m-4bit",
|
|
205
|
+
"recommended_load_id": "mlx-community/embeddinggemma-300m-4bit" if use_mlx else "google/embeddinggemma-300m",
|
|
206
|
+
"recommended_engine": "local_mlx" if use_mlx else "huggingface",
|
|
207
|
+
"provider": "mlx" if use_mlx else "huggingface",
|
|
208
|
+
"profile_id": "local:embeddinggemma-300m",
|
|
209
|
+
"name": "Google EmbeddingGemma 300M",
|
|
210
|
+
"display_name": "Google EmbeddingGemma 300M",
|
|
211
|
+
"family": "EmbeddingGemma",
|
|
212
|
+
"category": "embedding",
|
|
213
|
+
"role": "embedding",
|
|
214
|
+
"task": "embedding",
|
|
215
|
+
"tag": "local-embedding",
|
|
216
|
+
"modality": "text",
|
|
217
|
+
"dimensions": 768,
|
|
218
|
+
"size": "0.3GB" if use_mlx else "0.6GB",
|
|
219
|
+
"download_size": "0.3GB" if use_mlx else "0.6GB",
|
|
220
|
+
"download_size_gb": 0.30 if use_mlx else 0.60,
|
|
221
|
+
"memory_footprint_gb": footprint_gb,
|
|
222
|
+
"effective_llm_ram_gb": max(0.0, round(effective_ram - footprint_gb, 2)),
|
|
223
|
+
"status": "recommended",
|
|
224
|
+
"load_status": "ready",
|
|
225
|
+
"supported": True,
|
|
226
|
+
"recommended_default": True,
|
|
227
|
+
"display_priority": 72,
|
|
228
|
+
"reason": f"저사양 PC ({int(round(effective_ram))}GB RAM) 맞춤 초경량 시맨틱 임베딩 모델 (Google EmbeddingGemma 300M, 768차원)",
|
|
229
|
+
}
|
|
@@ -43,6 +43,13 @@ from __future__ import annotations
|
|
|
43
43
|
from dataclasses import dataclass, field
|
|
44
44
|
from typing import Any, Dict, List, Optional
|
|
45
45
|
|
|
46
|
+
from .embedding_recommendation import (
|
|
47
|
+
build_embedding_capabilities,
|
|
48
|
+
)
|
|
49
|
+
from .embedding_recommendation import (
|
|
50
|
+
recommend_embedding_for_hardware as _rec_embedding_for_hardware,
|
|
51
|
+
)
|
|
52
|
+
|
|
46
53
|
|
|
47
54
|
@dataclass(frozen=True)
|
|
48
55
|
class HardwareProfile:
|
|
@@ -109,6 +116,9 @@ class ModelCapability:
|
|
|
109
116
|
# UI / rec hints
|
|
110
117
|
recommended_default: bool = False
|
|
111
118
|
display_priority: int = 100
|
|
119
|
+
category: str = "chat"
|
|
120
|
+
task: str = "chat"
|
|
121
|
+
dimensions: Optional[int] = None
|
|
112
122
|
|
|
113
123
|
def to_legacy_dict(self) -> Dict[str, Any]:
|
|
114
124
|
"""Exact shape expected by older ENGINE_MODEL_CATALOG consumers + extra 5.2 fields."""
|
|
@@ -123,6 +133,9 @@ class ModelCapability:
|
|
|
123
133
|
"modality": self.modality,
|
|
124
134
|
"architecture": self.architecture,
|
|
125
135
|
"lifecycle": self.lifecycle,
|
|
136
|
+
"category": self.category,
|
|
137
|
+
"task": self.task,
|
|
138
|
+
"role": "embedding" if self.category == "embedding" else "chat",
|
|
126
139
|
"source_country": self.source_country,
|
|
127
140
|
"source_company": self.source_company,
|
|
128
141
|
"execution_method": self.execution_method,
|
|
@@ -162,6 +175,8 @@ class ModelCapability:
|
|
|
162
175
|
},
|
|
163
176
|
"recommended_default": self.recommended_default,
|
|
164
177
|
}
|
|
178
|
+
if self.dimensions is not None:
|
|
179
|
+
base["dimensions"] = self.dimensions
|
|
165
180
|
return base
|
|
166
181
|
|
|
167
182
|
|
|
@@ -346,6 +361,32 @@ _REGISTRY: List[ModelCapability] = [
|
|
|
346
361
|
),
|
|
347
362
|
|
|
348
363
|
# ── MoE tier (32GB+ RAM) ─────────────────────────────────────────────────
|
|
364
|
+
ModelCapability(
|
|
365
|
+
id="mlx-community/gemma-4-26B-A4B-it-qat-4bit",
|
|
366
|
+
hf_repo_id="mlx-community/gemma-4-26B-A4B-it-qat-4bit",
|
|
367
|
+
name="Gemma 4 26B A4B QAT Instruct",
|
|
368
|
+
family="Gemma 4",
|
|
369
|
+
tag="local-vlm",
|
|
370
|
+
size="15.6GB",
|
|
371
|
+
architecture="gemma4",
|
|
372
|
+
download_size_gb=15.64,
|
|
373
|
+
quantization="4bit",
|
|
374
|
+
provider_hints=["local_mlx", "ollama", "vllm", "lmstudio", "llamacpp"],
|
|
375
|
+
download_strategy="hf_hub",
|
|
376
|
+
load_strategy="mlx_vlm",
|
|
377
|
+
hardware=HardwareProfile(
|
|
378
|
+
min_ram_gb=22.0, recommended_ram_gb=32.0, apple_silicon_pref=True,
|
|
379
|
+
notes="Quantization-Aware Trained (QAT) 4-bit MoE with 8-bit shared MLP and router layers; higher accuracy than standard 4-bit PTQ on 32GB+ Apple Silicon.",
|
|
380
|
+
),
|
|
381
|
+
source_country="미국", source_company="Google",
|
|
382
|
+
verification=VerificationStatus(
|
|
383
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
384
|
+
has_weights_hint=True, pipeline_tag="image-text-to-text", likes=9, license="apache-2.0",
|
|
385
|
+
notes="library_name=mlx, ungated, 3 safetensors shards, official Google QAT base.",
|
|
386
|
+
verified_by="hf-api-light",
|
|
387
|
+
),
|
|
388
|
+
display_priority=48,
|
|
389
|
+
),
|
|
349
390
|
ModelCapability(
|
|
350
391
|
id="mlx-community/gemma-4-26b-a4b-it-4bit",
|
|
351
392
|
hf_repo_id="mlx-community/gemma-4-26b-a4b-it-4bit",
|
|
@@ -371,6 +412,32 @@ _REGISTRY: List[ModelCapability] = [
|
|
|
371
412
|
),
|
|
372
413
|
display_priority=50,
|
|
373
414
|
),
|
|
415
|
+
ModelCapability(
|
|
416
|
+
id="mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit",
|
|
417
|
+
hf_repo_id="mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit",
|
|
418
|
+
name="Gemma 4 26B A4B QAT OptiQ Instruct",
|
|
419
|
+
family="Gemma 4",
|
|
420
|
+
tag="local-vlm",
|
|
421
|
+
size="21.9GB",
|
|
422
|
+
architecture="gemma4",
|
|
423
|
+
download_size_gb=21.89,
|
|
424
|
+
quantization="OptiQ-4bit",
|
|
425
|
+
provider_hints=["local_mlx"],
|
|
426
|
+
download_strategy="hf_hub",
|
|
427
|
+
load_strategy="mlx_vlm",
|
|
428
|
+
hardware=HardwareProfile(
|
|
429
|
+
min_ram_gb=28.0, recommended_ram_gb=48.0, apple_silicon_pref=True,
|
|
430
|
+
notes="Sensitivity-guided mixed 4-bit/8-bit OptiQ quantization built on Google's QAT Gemma 4 26B A4B base for higher reasoning and tool-calling fidelity.",
|
|
431
|
+
),
|
|
432
|
+
source_country="미국", source_company="Google",
|
|
433
|
+
verification=VerificationStatus(
|
|
434
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
435
|
+
has_weights_hint=True, pipeline_tag="text-generation", likes=4, license="apache-2.0",
|
|
436
|
+
notes="library_name=mlx, ungated, 5 safetensors shards, mlx-optiq sensitivity-guided mixed 4/8-bit.",
|
|
437
|
+
verified_by="hf-api-light",
|
|
438
|
+
),
|
|
439
|
+
display_priority=51,
|
|
440
|
+
),
|
|
374
441
|
ModelCapability(
|
|
375
442
|
id="mlx-community/Qwen3.6-35B-A3B-4bit",
|
|
376
443
|
hf_repo_id="mlx-community/Qwen3.6-35B-A3B-4bit",
|
|
@@ -398,6 +465,31 @@ _REGISTRY: List[ModelCapability] = [
|
|
|
398
465
|
),
|
|
399
466
|
|
|
400
467
|
# ── Large tier (48GB+ RAM) ───────────────────────────────────────────────
|
|
468
|
+
ModelCapability(
|
|
469
|
+
id="mlx-community/Qwen3.8-27B-4bit",
|
|
470
|
+
hf_repo_id="mlx-community/Qwen3.8-27B-4bit",
|
|
471
|
+
name="Qwen3.8 27B",
|
|
472
|
+
family="Qwen3.8",
|
|
473
|
+
tag="local-vlm",
|
|
474
|
+
size="16.1GB",
|
|
475
|
+
architecture="qwen3_5",
|
|
476
|
+
download_size_gb=16.08,
|
|
477
|
+
quantization="4bit",
|
|
478
|
+
provider_hints=["local_mlx", "ollama", "vllm", "lmstudio", "llamacpp"],
|
|
479
|
+
download_strategy="hf_hub",
|
|
480
|
+
load_strategy="mlx_vlm",
|
|
481
|
+
hardware=HardwareProfile(
|
|
482
|
+
min_ram_gb=24.0, recommended_ram_gb=48.0, apple_silicon_pref=True,
|
|
483
|
+
notes="Latest Qwen 3.8 27B dense multimodal model; higher coding, reasoning, and agentic tool accuracy than Qwen 3.6 27B at identical 16.1GB 4-bit footprint.",
|
|
484
|
+
),
|
|
485
|
+
source_country="중국", source_company="Alibaba",
|
|
486
|
+
verification=VerificationStatus(
|
|
487
|
+
hf_exists=True, hf_last_checked="2026-11-11", has_config=True, has_tokenizer=True,
|
|
488
|
+
has_weights_hint=True, pipeline_tag="image-text-to-text", likes=237, license="apache-2.0",
|
|
489
|
+
notes="library_name=mlx, ungated, 3 safetensors shards.", verified_by="hf-api-light",
|
|
490
|
+
),
|
|
491
|
+
display_priority=52,
|
|
492
|
+
),
|
|
401
493
|
ModelCapability(
|
|
402
494
|
id="mlx-community/Qwen3.6-27B-4bit",
|
|
403
495
|
hf_repo_id="mlx-community/Qwen3.6-27B-4bit",
|
|
@@ -449,6 +541,7 @@ _REGISTRY: List[ModelCapability] = [
|
|
|
449
541
|
display_priority=60,
|
|
450
542
|
),
|
|
451
543
|
]
|
|
544
|
+
_REGISTRY.extend(build_embedding_capabilities(ModelCapability, HardwareProfile, VerificationStatus))
|
|
452
545
|
|
|
453
546
|
|
|
454
547
|
# ── Recognised-only entries (LEGACY) ──────────────────────────────────────────
|
|
@@ -706,3 +799,24 @@ def get_verified_models() -> List[Dict[str, Any]]:
|
|
|
706
799
|
if c.verification.hf_exists and c.verification.has_config and c.verification.has_tokenizer
|
|
707
800
|
]
|
|
708
801
|
|
|
802
|
+
|
|
803
|
+
def get_embedding_capabilities() -> List[ModelCapability]:
|
|
804
|
+
"""Return recommended embedding model capabilities (Google EmbeddingGemma 2 / 300M)."""
|
|
805
|
+
return [c for c in _REGISTRY if c.category == "embedding"]
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def recommend_embedding_for_hardware(
|
|
809
|
+
*,
|
|
810
|
+
ram_gb: Optional[float] = None,
|
|
811
|
+
apple_silicon: bool = False,
|
|
812
|
+
cuda: bool = False,
|
|
813
|
+
) -> Dict[str, Any]:
|
|
814
|
+
"""Return a PC-tailored embedding model recommendation accounting for RAM & accelerator."""
|
|
815
|
+
return _rec_embedding_for_hardware(
|
|
816
|
+
ram_gb=ram_gb,
|
|
817
|
+
apple_silicon=apple_silicon,
|
|
818
|
+
cuda=cuda,
|
|
819
|
+
get_capability_fn=get_capability,
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
|
|
@@ -26,9 +26,15 @@ from latticeai.services.model_capability_registry import (
|
|
|
26
26
|
from latticeai.services.model_capability_registry import (
|
|
27
27
|
get_capability as _get_capability,
|
|
28
28
|
)
|
|
29
|
+
from latticeai.services.model_capability_registry import (
|
|
30
|
+
get_embedding_capabilities as _get_embedding_capabilities,
|
|
31
|
+
)
|
|
29
32
|
from latticeai.services.model_capability_registry import (
|
|
30
33
|
get_verified_models as _get_verified_models,
|
|
31
34
|
)
|
|
35
|
+
from latticeai.services.model_capability_registry import (
|
|
36
|
+
recommend_embedding_for_hardware as _recommend_embedding_for_hardware,
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
ENGINE_INSTALLERS: Dict[str, Dict[str, Any]] = {
|
|
34
40
|
"local_mlx": {
|
|
@@ -135,15 +141,28 @@ _ENGINE_ROUTES: Dict[str, Dict[str, str]] = {
|
|
|
135
141
|
"mlx-community/gemma-4-12B-it-4bit", "ggml-org/gemma-4-12B-it-GGUF", "google/gemma-4-12B-it",
|
|
136
142
|
ollama_tag="Q4_0",
|
|
137
143
|
),
|
|
144
|
+
"mlx-community/gemma-4-26B-A4B-it-qat-4bit": _gguf_routes(
|
|
145
|
+
"mlx-community/gemma-4-26B-A4B-it-qat-4bit", "google/gemma-4-26B-A4B-it-qat-q4_0-gguf",
|
|
146
|
+
"google/gemma-4-26B-A4B-it-qat-q4_0-unquantized",
|
|
147
|
+
ollama_tag="Q4_0",
|
|
148
|
+
),
|
|
138
149
|
"mlx-community/gemma-4-26b-a4b-it-4bit": _gguf_routes(
|
|
139
150
|
"mlx-community/gemma-4-26b-a4b-it-4bit", "ggml-org/gemma-4-26B-A4B-it-GGUF",
|
|
140
151
|
"google/gemma-4-26B-A4B-it",
|
|
141
152
|
ollama_tag="Q4_0",
|
|
142
153
|
),
|
|
154
|
+
"mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit": _gguf_routes(
|
|
155
|
+
"mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit", "unsloth/gemma-4-26B-A4B-it-GGUF",
|
|
156
|
+
"google/gemma-4-26B-A4B-it-qat-q4_0-unquantized",
|
|
157
|
+
ollama_tag="Q4_0",
|
|
158
|
+
),
|
|
143
159
|
"mlx-community/gemma-4-31b-it-4bit": _gguf_routes(
|
|
144
160
|
"mlx-community/gemma-4-31b-it-4bit", "ggml-org/gemma-4-31B-it-GGUF", "google/gemma-4-31B-it",
|
|
145
161
|
ollama_tag="Q4_0",
|
|
146
162
|
),
|
|
163
|
+
"mlx-community/Qwen3.8-27B-4bit": _gguf_routes(
|
|
164
|
+
"mlx-community/Qwen3.8-27B-4bit", "ggml-org/Qwen3.8-27B-GGUF", "Qwen/Qwen3.8-27B",
|
|
165
|
+
),
|
|
147
166
|
"mlx-community/Qwen3.6-27B-4bit": _gguf_routes(
|
|
148
167
|
"mlx-community/Qwen3.6-27B-4bit", "ggml-org/Qwen3.6-27B-GGUF", "Qwen/Qwen3.6-27B",
|
|
149
168
|
),
|
|
@@ -157,6 +176,18 @@ _ENGINE_ROUTES: Dict[str, Dict[str, str]] = {
|
|
|
157
176
|
"mlx-community/Qwen3.6-35B-A3B-4bit": _gguf_routes(
|
|
158
177
|
"mlx-community/Qwen3.6-35B-A3B-4bit", "ggml-org/Qwen3.6-35B-A3B-GGUF", "Qwen/Qwen3.6-35B-A3B",
|
|
159
178
|
),
|
|
179
|
+
"google/embeddinggemma-2": _gguf_routes(
|
|
180
|
+
"google/embeddinggemma-2", "ggml-org/embeddinggemma-2-GGUF", "google/embeddinggemma-2",
|
|
181
|
+
),
|
|
182
|
+
"mlx-community/embeddinggemma-2-4bit": _gguf_routes(
|
|
183
|
+
"mlx-community/embeddinggemma-2-4bit", "mlx-community/embeddinggemma-2-4bit-GGUF", "mlx-community/embeddinggemma-2-4bit",
|
|
184
|
+
),
|
|
185
|
+
"google/embeddinggemma-300m": _gguf_routes(
|
|
186
|
+
"google/embeddinggemma-300m", "ggml-org/embeddinggemma-300m-GGUF", "google/embeddinggemma-300m",
|
|
187
|
+
),
|
|
188
|
+
"mlx-community/embeddinggemma-300m-4bit": _gguf_routes(
|
|
189
|
+
"mlx-community/embeddinggemma-300m-4bit", "mlx-community/embeddinggemma-300m-4bit-GGUF", "mlx-community/embeddinggemma-300m-4bit",
|
|
190
|
+
),
|
|
160
191
|
}
|
|
161
192
|
|
|
162
193
|
# Names people actually type that are not the repo's own short name. Kept
|
|
@@ -164,6 +195,13 @@ _ENGINE_ROUTES: Dict[str, Dict[str, str]] = {
|
|
|
164
195
|
# is no other Gemma 4 26B — and resolving that to nothing would be pedantry.
|
|
165
196
|
_COMMON_MISNAMES: Dict[str, str] = {
|
|
166
197
|
"gemma-4-26b-it-4bit": "mlx-community/gemma-4-26b-a4b-it-4bit",
|
|
198
|
+
"gemma-4-26b-qat-4bit": "mlx-community/gemma-4-26B-A4B-it-qat-4bit",
|
|
199
|
+
"gemma-4-26b-a4b-qat-4bit": "mlx-community/gemma-4-26B-A4B-it-qat-4bit",
|
|
200
|
+
"gemma-4-26b-optiq-4bit": "mlx-community/gemma-4-26B-A4B-it-qat-OptiQ-4bit",
|
|
201
|
+
"qwen-3.8-27b-4bit": "mlx-community/Qwen3.8-27B-4bit",
|
|
202
|
+
"qwen3.8-27b": "mlx-community/Qwen3.8-27B-4bit",
|
|
203
|
+
"embeddinggemma2": "google/embeddinggemma-2",
|
|
204
|
+
"embeddinggemma-2-hf": "google/embeddinggemma-2",
|
|
167
205
|
}
|
|
168
206
|
|
|
169
207
|
# Keyed by both the lowercase short name ("gemma-4-12b-it-4bit") and the
|
|
@@ -180,7 +218,9 @@ MODEL_ENGINE_ALIASES: Dict[str, Dict[str, str]] = {
|
|
|
180
218
|
# Also expose registry helpers directly from here for consumers who want the rich objects
|
|
181
219
|
get_all_capabilities = _get_all_capabilities
|
|
182
220
|
get_capability = _get_capability
|
|
221
|
+
get_embedding_capabilities = _get_embedding_capabilities
|
|
183
222
|
get_verified_models = _get_verified_models
|
|
223
|
+
recommend_embedding_for_hardware = _recommend_embedding_for_hardware
|
|
184
224
|
|
|
185
225
|
_VERSIONED_MODEL_PATTERNS = (
|
|
186
226
|
("gemma", re.compile(r"\bgemma[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
|
|
@@ -195,6 +235,8 @@ def _version_tuple(raw: str) -> tuple[int, ...]:
|
|
|
195
235
|
|
|
196
236
|
def _model_family_version(model: Dict[str, Any]) -> Optional[tuple[str, tuple[int, ...]]]:
|
|
197
237
|
text = " ".join(str(model.get(key) or "") for key in ("family", "name", "id"))
|
|
238
|
+
if "embeddinggemma" in text.lower() or model.get("category") == "embedding" or model.get("task") == "embedding":
|
|
239
|
+
return None
|
|
198
240
|
for family, pattern in _VERSIONED_MODEL_PATTERNS:
|
|
199
241
|
match = pattern.search(text)
|
|
200
242
|
if match:
|
|
@@ -252,6 +294,8 @@ _BLOCKED_CATALOG_FRAGMENTS = (
|
|
|
252
294
|
|
|
253
295
|
def _is_blocked_catalog_id(model: Dict[str, Any]) -> bool:
|
|
254
296
|
ident = str(model.get("id") or "").lower()
|
|
297
|
+
if "embeddinggemma" in ident:
|
|
298
|
+
return False
|
|
255
299
|
return any(fragment in ident for fragment in _BLOCKED_CATALOG_FRAGMENTS)
|
|
256
300
|
|
|
257
301
|
|
|
@@ -293,3 +337,107 @@ def _finalize_engine_catalog(
|
|
|
293
337
|
|
|
294
338
|
|
|
295
339
|
ENGINE_MODEL_CATALOG = _finalize_engine_catalog(_RAW_ENGINE_MODEL_CATALOG)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def recommend_models_for_pc(
|
|
343
|
+
*,
|
|
344
|
+
ram_gb: float = 16.0,
|
|
345
|
+
apple_silicon: bool = True,
|
|
346
|
+
cuda: bool = False,
|
|
347
|
+
engine: str = "local_mlx",
|
|
348
|
+
) -> Dict[str, Any]:
|
|
349
|
+
"""Compute PC-tailored model recommendations including EmbeddingGemma 2.
|
|
350
|
+
|
|
351
|
+
Evaluates the system RAM/VRAM budget while accounting for the embedding
|
|
352
|
+
model's memory footprint (``google/embeddinggemma-2`` uses ~0.5 GB in 4-bit
|
|
353
|
+
MLX / ~1.5 GB in FP16 HF; ``google/embeddinggemma-300m`` uses ~0.3 GB) so
|
|
354
|
+
the recommended LLM + embedding model setup fits comfortably on the host.
|
|
355
|
+
"""
|
|
356
|
+
embedding_rec = recommend_embedding_for_hardware(
|
|
357
|
+
ram_gb=ram_gb,
|
|
358
|
+
apple_silicon=apple_silicon,
|
|
359
|
+
cuda=cuda,
|
|
360
|
+
)
|
|
361
|
+
embedding_ram_gb = float(embedding_rec.get("memory_footprint_gb") or 0.5)
|
|
362
|
+
effective_llm_ram_gb = max(0.0, float(ram_gb) - embedding_ram_gb)
|
|
363
|
+
|
|
364
|
+
catalog_rows = list(ENGINE_MODEL_CATALOG.get(engine) or ENGINE_MODEL_CATALOG.get("local_mlx") or [])
|
|
365
|
+
models_out: List[Dict[str, Any]] = []
|
|
366
|
+
chat_candidates: List[Dict[str, Any]] = []
|
|
367
|
+
|
|
368
|
+
for row in catalog_rows:
|
|
369
|
+
entry = dict(row)
|
|
370
|
+
category = str(entry.get("category") or "chat")
|
|
371
|
+
min_ram = float(entry.get("min_ram_gb") or 4.0)
|
|
372
|
+
rec_ram = float(entry.get("recommended_ram_gb") or max(min_ram, 8.0))
|
|
373
|
+
dl_gb = float(entry.get("download_size_gb") or 0.0)
|
|
374
|
+
if category == "embedding":
|
|
375
|
+
fits = float(ram_gb) + 0.01 >= min_ram
|
|
376
|
+
comfortable = float(ram_gb) + 0.01 >= rec_ram
|
|
377
|
+
status = "recommended" if comfortable else ("compatible" if fits else "not_recommended")
|
|
378
|
+
entry["compatibility"] = {
|
|
379
|
+
"status": status,
|
|
380
|
+
"reason": embedding_rec.get("reason") if status != "not_recommended" else f"최소 {min_ram:.0f}GB RAM이 필요합니다.",
|
|
381
|
+
"min_ram_gb": min_ram,
|
|
382
|
+
"recommended_ram_gb": rec_ram,
|
|
383
|
+
}
|
|
384
|
+
models_out.append(entry)
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
# Account for the embedding model footprint when evaluating LLM fit.
|
|
388
|
+
exceeds_combined = dl_gb > 0.0 and (dl_gb + embedding_ram_gb) > float(ram_gb)
|
|
389
|
+
if exceeds_combined or effective_llm_ram_gb + 0.01 < min_ram:
|
|
390
|
+
status = "not_recommended"
|
|
391
|
+
reason = (
|
|
392
|
+
f"임베딩 모델({embedding_rec['id']}, ~{embedding_ram_gb:.1f}GB)과 함께 실행 시 "
|
|
393
|
+
f"최소 {min_ram + embedding_ram_gb:.1f}GB RAM이 필요합니다 (현재 {ram_gb:.1f}GB)."
|
|
394
|
+
)
|
|
395
|
+
elif effective_llm_ram_gb + 0.01 >= rec_ram or float(ram_gb) + 0.01 >= rec_ram:
|
|
396
|
+
status = "recommended"
|
|
397
|
+
reason = (
|
|
398
|
+
f"{ram_gb:.0f}GB RAM 환경에서 {embedding_rec['name']} (~{embedding_ram_gb:.1f}GB) 임베딩 모델과 "
|
|
399
|
+
f"동시에 여유 있게 실행됩니다 (권장 {rec_ram:.0f}GB)."
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
status = "compatible"
|
|
403
|
+
reason = (
|
|
404
|
+
f"실행 가능하지만 임베딩 모델(~{embedding_ram_gb:.1f}GB) 병행 시 메모리가 빠듯할 수 있습니다 "
|
|
405
|
+
f"(최소 {min_ram:.0f}GB / 권장 {rec_ram:.0f}GB)."
|
|
406
|
+
)
|
|
407
|
+
entry["compatibility"] = {
|
|
408
|
+
"status": status,
|
|
409
|
+
"reason": reason,
|
|
410
|
+
"min_ram_gb": min_ram,
|
|
411
|
+
"recommended_ram_gb": rec_ram,
|
|
412
|
+
"embedding_reserved_ram_gb": embedding_ram_gb,
|
|
413
|
+
}
|
|
414
|
+
models_out.append(entry)
|
|
415
|
+
if status in {"recommended", "compatible"}:
|
|
416
|
+
chat_candidates.append(entry)
|
|
417
|
+
|
|
418
|
+
# Ensure the PC-tailored embedding recommendation is present in models_out
|
|
419
|
+
if not any(str(m.get("category") or "") == "embedding" for m in models_out):
|
|
420
|
+
models_out.append(dict(embedding_rec))
|
|
421
|
+
|
|
422
|
+
chat_candidates.sort(
|
|
423
|
+
key=lambda m: (
|
|
424
|
+
0 if m.get("compatibility", {}).get("status") == "recommended" else 1,
|
|
425
|
+
-float(m.get("recommended_ram_gb") or 0.0),
|
|
426
|
+
0 if m.get("recommended_default") else 1,
|
|
427
|
+
int(m.get("recommendation_priority") or 99),
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
top_pick = chat_candidates[0] if chat_candidates else None
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
"ram_gb": float(ram_gb),
|
|
434
|
+
"effective_ram_gb": round(effective_llm_ram_gb, 2),
|
|
435
|
+
"embedding_reserved_ram_gb": embedding_ram_gb,
|
|
436
|
+
"apple_silicon": bool(apple_silicon),
|
|
437
|
+
"cuda": bool(cuda),
|
|
438
|
+
"top_pick": top_pick,
|
|
439
|
+
"embedding_model": embedding_rec["id"],
|
|
440
|
+
"embedding_recommendation": embedding_rec,
|
|
441
|
+
"models": models_out,
|
|
442
|
+
}
|
|
443
|
+
|
|
@@ -112,6 +112,8 @@ def _json_request(
|
|
|
112
112
|
payload: Optional[Dict[str, Any]] = None,
|
|
113
113
|
timeout: float = 10.0,
|
|
114
114
|
) -> Dict[str, Any]:
|
|
115
|
+
if not str(url).startswith(("http://", "https://")):
|
|
116
|
+
raise ValueError(f"Unsupported URL scheme in {url!r}")
|
|
115
117
|
data = None
|
|
116
118
|
req_headers = dict(headers or {})
|
|
117
119
|
if payload is not None:
|
|
@@ -225,7 +227,7 @@ def get_openai_compatible_server_models(provider: str) -> List[str]:
|
|
|
225
227
|
config = OPENAI_COMPATIBLE_PROVIDERS.get(provider) or {}
|
|
226
228
|
base_url = os.getenv(config.get("base_url_env", "")) if config.get("base_url_env") else None
|
|
227
229
|
base_url = (base_url or config.get("base_url") or "").rstrip("/")
|
|
228
|
-
if not base_url:
|
|
230
|
+
if not base_url or not base_url.startswith(("http://", "https://")):
|
|
229
231
|
return []
|
|
230
232
|
|
|
231
233
|
api_key = os.getenv(config.get("env_key", "")) or config.get("api_key_fallback") or provider
|