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.
Files changed (111) hide show
  1. package/README.md +25 -39
  2. package/docs/CHANGELOG.md +44 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +11 -11
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/lattice_brain/__init__.py +1 -1
  12. package/latticeai/__init__.py +1 -1
  13. package/latticeai/api/models.py +21 -1
  14. package/latticeai/api/search.py +8 -1
  15. package/latticeai/api/worker_compute.py +9 -1
  16. package/latticeai/core/embedding_providers/__init__.py +14 -0
  17. package/latticeai/core/embedding_providers/autodetect.py +91 -24
  18. package/latticeai/core/embedding_providers/base.py +10 -2
  19. package/latticeai/core/embedding_providers/profiles.py +44 -0
  20. package/latticeai/core/embedding_providers/text.py +325 -2
  21. package/latticeai/models/model_providers.py +2 -0
  22. package/latticeai/services/architecture_readiness.py +1 -1
  23. package/latticeai/services/embedding_recommendation.py +229 -0
  24. package/latticeai/services/model_capability_registry.py +114 -0
  25. package/latticeai/services/model_catalog.py +148 -0
  26. package/latticeai/services/model_engines.py +3 -1
  27. package/latticeai/services/product_readiness.py +1 -1
  28. package/package.json +3 -3
  29. package/requirements.txt +3 -1
  30. package/scripts/bump_version.py +106 -6
  31. package/scripts/check_current_release_docs.mjs +1 -1
  32. package/scripts/check_screenshot_pixel_delta.py +6 -2
  33. package/scripts/release_screen_claims.json +11 -0
  34. package/src-tauri/Cargo.lock +10 -10
  35. package/src-tauri/Cargo.toml +1 -1
  36. package/src-tauri/tauri.conf.json +1 -1
  37. package/static/app/asset-manifest.json +46 -47
  38. package/static/app/assets/Act-DQ2Ldwo8.js +2 -0
  39. package/static/app/assets/AdminConsole-y6EjiXeH.js +1 -0
  40. package/static/app/assets/Brain-CRWWw-h9.js +2 -0
  41. package/static/app/assets/BrainHome-cmvTu7Ea.js +2 -0
  42. package/static/app/assets/BrainSignals-gaNn5E4-.js +1 -0
  43. package/static/app/assets/Capture-BJK7in1j.js +1 -0
  44. package/static/app/assets/Chronicle-BuYZ9lMt.js +1 -0
  45. package/static/app/assets/CommandPalette-BjFwuKaA.js +1 -0
  46. package/static/app/assets/DigitalBrainExplorer-DTBRPYJ3.js +321 -0
  47. package/static/app/assets/Library-BsmZ-YVh.js +1 -0
  48. package/static/app/assets/LivingBrain-aT9hgkPV.js +1 -0
  49. package/static/app/assets/ProductFlow-Ct0QLLY5.js +1 -0
  50. package/static/app/assets/{QueryClientProvider-B3OjqSyJ.js → QueryClientProvider-CTiSBeU6.js} +1 -1
  51. package/static/app/assets/ReviewCard-CdooYtWr.js +3 -0
  52. package/static/app/assets/RunsListPanel-Btf2DIrG.js +1 -0
  53. package/static/app/assets/System-B77M4t-M.js +1 -0
  54. package/static/app/assets/WorkflowGraph-u3ZKLvZe.js +1 -0
  55. package/static/app/assets/WorkflowsPanel--nTFq3Cs.js +2 -0
  56. package/static/app/assets/actHelpers-xNnCfUeC.js +1 -0
  57. package/static/app/assets/arrow-left-Cp4NHbfX.js +1 -0
  58. package/static/app/assets/{bot-CAfN5QD1.js → bot-CXwvpODC.js} +1 -1
  59. package/static/app/assets/{brain-DNNLFQgq.js → brain-BzjoGgor.js} +1 -1
  60. package/static/app/assets/{button-D0k7_k6e.js → button-_hHOhp7g.js} +1 -1
  61. package/static/app/assets/circle-check-qG5dMahn.js +1 -0
  62. package/static/app/assets/{circle-pause-Dxw6SJuA.js → circle-pause-DBOqYYMh.js} +1 -1
  63. package/static/app/assets/{circle-play-DuoLDLEg.js → circle-play-DA0Vf2NH.js} +1 -1
  64. package/static/app/assets/{cpu-BGOpEdGX.js → cpu-DwsE0_o3.js} +1 -1
  65. package/static/app/assets/{download-wXD9L5vN.js → download-iCoe2f2Q.js} +1 -1
  66. package/static/app/assets/{folder-open-CittV8Ey.js → folder-open-C1vNOMhy.js} +1 -1
  67. package/static/app/assets/{hard-drive-CLQPYYBF.js → hard-drive-a73KaYth.js} +1 -1
  68. package/static/app/assets/index-Dbb2YhYw.css +2 -0
  69. package/static/app/assets/index-DpuEeYmP.js +13 -0
  70. package/static/app/assets/{input-RZ8aKj6d.js → input-BclQNHyj.js} +1 -1
  71. package/static/app/assets/jsx-runtime-CU_pEDDh.js +1 -0
  72. package/static/app/assets/{link-2-B7QYeNdJ.js → link-2-D23wihBe.js} +1 -1
  73. package/static/app/assets/{permissionCopy-DFGEuU2d.js → permissionCopy-DiGZRKAY.js} +1 -1
  74. package/static/app/assets/primitives-1S-XCgmz.js +1 -0
  75. package/static/app/assets/search-Cl2k5uhD.js +1 -0
  76. package/static/app/assets/{share-2-Drg0MPgx.js → share-2-47_av_aF.js} +1 -1
  77. package/static/app/assets/{shield-alert-CFKdCGM8.js → shield-alert-BvKq2P-G.js} +1 -1
  78. package/static/app/assets/{textarea-n_69mQP_.js → textarea-DcLO52bu.js} +1 -1
  79. package/static/app/assets/useFocusTrap-CpqVPzJ_.js +1 -0
  80. package/static/app/assets/{useMutation-Dvcq6J6b.js → useMutation-DhI3jqCN.js} +1 -1
  81. package/static/app/assets/workspace-DwNHbqaX.js +1 -0
  82. package/static/app/index.html +4 -4
  83. package/static/sw.js +1 -1
  84. package/static/app/assets/Act-CZXyMcfJ.js +0 -2
  85. package/static/app/assets/AdminConsole-CAkZk0BI.js +0 -1
  86. package/static/app/assets/Brain-DN_7_HSV.js +0 -2
  87. package/static/app/assets/BrainHome-BBP0ouU-.js +0 -2
  88. package/static/app/assets/BrainSignals-nyRNxvQ3.js +0 -1
  89. package/static/app/assets/Capture-DBvFoZKL.js +0 -1
  90. package/static/app/assets/Chronicle-BC5zmR44.js +0 -1
  91. package/static/app/assets/CommandPalette-Bs-Fk6HT.js +0 -1
  92. package/static/app/assets/DigitalBrainExplorer-CNcCktPP.js +0 -321
  93. package/static/app/assets/Library-CxUA4xai.js +0 -1
  94. package/static/app/assets/LivingBrain-CiyjvwKy.js +0 -1
  95. package/static/app/assets/ProductFlow-DccERGr3.js +0 -1
  96. package/static/app/assets/ReviewCard-lawM1khV.js +0 -3
  97. package/static/app/assets/RunsListPanel-DQC-zQ7m.js +0 -1
  98. package/static/app/assets/System-DwqYJQaj.js +0 -1
  99. package/static/app/assets/WorkflowGraph-pJ2eFrhy.js +0 -1
  100. package/static/app/assets/WorkflowsPanel-BIGjfM7h.js +0 -2
  101. package/static/app/assets/actHelpers-DfIqS92u.js +0 -1
  102. package/static/app/assets/arrow-left-DvwQxqcD.js +0 -1
  103. package/static/app/assets/circle-check-hH6updC-.js +0 -1
  104. package/static/app/assets/index-C0VEkbAp.js +0 -13
  105. package/static/app/assets/index-C8gwOf0C.css +0 -2
  106. package/static/app/assets/jsx-runtime-bzQ4Vb5N.js +0 -1
  107. package/static/app/assets/primitives-EKxh12KR.js +0 -1
  108. package/static/app/assets/search-BA5Ge197.js +0 -1
  109. package/static/app/assets/sourceMeta-BzqJiEPI.js +0 -1
  110. package/static/app/assets/useFocusTrap-B0w4AxK1.js +0 -1
  111. package/static/app/assets/workspace-BgBHikYl.js +0 -1
@@ -19,7 +19,7 @@ from dataclasses import dataclass
19
19
  from pathlib import Path
20
20
  from typing import Any, Dict, List
21
21
 
22
- ARCHITECTURE_VERSION_TARGET = "12.3.0"
22
+ ARCHITECTURE_VERSION_TARGET = "12.5.0"
23
23
 
24
24
  PREFERRED_REFACTORING_ORDER = [
25
25
  "agent-runtime",
@@ -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
@@ -28,7 +28,7 @@ from typing import Any, Dict, List
28
28
 
29
29
  from latticeai.services.architecture_readiness import architecture_readiness
30
30
 
31
- PRODUCT_VERSION_TARGET = "12.3.0"
31
+ PRODUCT_VERSION_TARGET = "12.5.0"
32
32
 
33
33
 
34
34
  @dataclass(frozen=True)