nexaai 1.0.9__cp310-cp310-win_amd64.whl → 1.0.11rc1__cp310-cp310-win_amd64.whl

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.
Binary file
nexaai/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is generated by CMake from _version.py.in
2
2
  # Do not modify this file manually - it will be overwritten
3
3
 
4
- __version__ = "1.0.9"
4
+ __version__ = "1.0.11-rc1"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -3,7 +3,7 @@ import numpy as np
3
3
 
4
4
  from nexaai.common import PluginID
5
5
  from nexaai.embedder import Embedder, EmbeddingConfig
6
- from nexaai.mlx_backend.embedding.interface import create_embedder
6
+ from nexaai.mlx_backend.embedding.interface import Embedder as MLXEmbedderInterface
7
7
  from nexaai.mlx_backend.ml import ModelConfig as MLXModelConfig, SamplerConfig as MLXSamplerConfig, GenerationConfig as MLXGenerationConfig, EmbeddingConfig
8
8
 
9
9
 
@@ -27,12 +27,11 @@ class MLXEmbedderImpl(Embedder):
27
27
  MLXEmbedderImpl instance
28
28
  """
29
29
  try:
30
- # Create instance
31
- instance = cls()
30
+ # MLX interface is already imported
32
31
 
33
- # Use the factory function to create the appropriate embedder based on model type
34
- # This will automatically detect if it's JinaV2 or generic model and route correctly
35
- instance._mlx_embedder = create_embedder(
32
+ # Create instance and load MLX embedder
33
+ instance = cls()
34
+ instance._mlx_embedder = MLXEmbedderInterface(
36
35
  model_path=model_path,
37
36
  tokenizer_path=tokenizer_file
38
37
  )
@@ -5,22 +5,17 @@ from datetime import datetime
5
5
  from dataclasses import dataclass
6
6
  from typing import Optional, Callable, Dict, Any, List, Union
7
7
  import functools
8
- from enum import Enum
9
8
  from tqdm.auto import tqdm
10
9
  from huggingface_hub import HfApi
11
10
  from huggingface_hub.utils import HfHubHTTPError, RepositoryNotFoundError
12
11
 
13
12
  from .progress_tracker import CustomProgressTqdm, DownloadProgressTracker
14
13
  from .avatar_fetcher import get_avatar_url_for_repo
15
- from .manifest_utils import (
16
- load_download_metadata,
17
- save_download_metadata,
18
- save_manifest_with_files_metadata,
19
- )
20
14
 
21
15
  # Default path for model storage
22
16
  DEFAULT_MODEL_SAVING_PATH = "~/.cache/nexa.ai/nexa_sdk/models/"
23
17
 
18
+
24
19
  @dataclass
25
20
  class DownloadedModel:
26
21
  """Data class representing a downloaded model with all its metadata."""
@@ -93,6 +88,30 @@ def _check_for_incomplete_downloads(directory_path: str) -> bool:
93
88
  # If we can't access the directory, assume download is complete
94
89
  return True
95
90
 
91
+
92
+ def _load_download_metadata(directory_path: str) -> Dict[str, Any]:
93
+ """Load download metadata from download_metadata.json if it exists."""
94
+ metadata_path = os.path.join(directory_path, 'download_metadata.json')
95
+ if os.path.exists(metadata_path):
96
+ try:
97
+ with open(metadata_path, 'r', encoding='utf-8') as f:
98
+ return json.load(f)
99
+ except (json.JSONDecodeError, IOError):
100
+ pass
101
+ return {}
102
+
103
+
104
+ def _save_download_metadata(directory_path: str, metadata: Dict[str, Any]) -> None:
105
+ """Save download metadata to download_metadata.json."""
106
+ metadata_path = os.path.join(directory_path, 'download_metadata.json')
107
+ try:
108
+ with open(metadata_path, 'w', encoding='utf-8') as f:
109
+ json.dump(metadata, f, indent=2)
110
+ except IOError:
111
+ # If we can't save metadata, don't fail the download
112
+ pass
113
+
114
+
96
115
  def _get_directory_size_and_files(directory_path: str) -> tuple[int, List[str]]:
97
116
  """Get total size and list of files in a directory."""
98
117
  total_size = 0
@@ -118,13 +137,6 @@ def _get_directory_size_and_files(directory_path: str) -> tuple[int, List[str]]:
118
137
  return total_size, files
119
138
 
120
139
 
121
- def _has_valid_metadata(directory_path: str) -> bool:
122
- """Check if directory has either nexa.manifest or download_metadata.json (for backward compatibility)."""
123
- manifest_path = os.path.join(directory_path, 'nexa.manifest')
124
- old_metadata_path = os.path.join(directory_path, 'download_metadata.json')
125
- return os.path.exists(manifest_path) or os.path.exists(old_metadata_path)
126
-
127
-
128
140
  def _scan_for_repo_folders(base_path: str) -> List[DownloadedModel]:
129
141
  """Scan a directory for repository folders and return model information."""
130
142
  models = []
@@ -150,27 +162,24 @@ def _scan_for_repo_folders(base_path: str) -> List[DownloadedModel]:
150
162
  if os.path.isdir(subitem_path):
151
163
  has_subdirs = True
152
164
  # This looks like owner/repo structure
153
- # Only include if nexa.manifest or download_metadata.json exists (backward compatibility)
154
- if _has_valid_metadata(subitem_path):
155
- size_bytes, files = _get_directory_size_and_files(subitem_path)
156
- if files: # Only include if there are files
157
- # Check if the download is complete
158
- download_complete = _check_for_incomplete_downloads(subitem_path)
159
- # Load metadata if it exists
160
- repo_id = f"{item}/{subitem}"
161
- metadata = load_download_metadata(subitem_path, repo_id)
162
- models.append(DownloadedModel(
163
- repo_id=repo_id,
164
- files=files,
165
- folder_type='owner_repo',
166
- local_path=subitem_path,
167
- size_bytes=size_bytes,
168
- file_count=len(files),
169
- full_repo_download_complete=download_complete,
170
- pipeline_tag=metadata.get('pipeline_tag'),
171
- download_time=metadata.get('download_time'),
172
- avatar_url=metadata.get('avatar_url')
173
- ))
165
+ size_bytes, files = _get_directory_size_and_files(subitem_path)
166
+ if files: # Only include if there are files
167
+ # Check if the download is complete
168
+ download_complete = _check_for_incomplete_downloads(subitem_path)
169
+ # Load metadata if it exists
170
+ metadata = _load_download_metadata(subitem_path)
171
+ models.append(DownloadedModel(
172
+ repo_id=f"{item}/{subitem}",
173
+ files=files,
174
+ folder_type='owner_repo',
175
+ local_path=subitem_path,
176
+ size_bytes=size_bytes,
177
+ file_count=len(files),
178
+ full_repo_download_complete=download_complete,
179
+ pipeline_tag=metadata.get('pipeline_tag'),
180
+ download_time=metadata.get('download_time'),
181
+ avatar_url=metadata.get('avatar_url')
182
+ ))
174
183
  else:
175
184
  direct_files.append(subitem)
176
185
  except (OSError, IOError):
@@ -179,27 +188,24 @@ def _scan_for_repo_folders(base_path: str) -> List[DownloadedModel]:
179
188
 
180
189
  # Direct repo folder (no owner structure)
181
190
  if not has_subdirs and direct_files:
182
- # Only include if nexa.manifest or download_metadata.json exists (backward compatibility)
183
- if _has_valid_metadata(item_path):
184
- size_bytes, files = _get_directory_size_and_files(item_path)
185
- if files: # Only include if there are files
186
- # Check if the download is complete
187
- download_complete = _check_for_incomplete_downloads(item_path)
188
- # Load metadata if it exists
189
- repo_id = item
190
- metadata = load_download_metadata(item_path, repo_id)
191
- models.append(DownloadedModel(
192
- repo_id=repo_id,
193
- files=files,
194
- folder_type='direct_repo',
195
- local_path=item_path,
196
- size_bytes=size_bytes,
197
- file_count=len(files),
198
- full_repo_download_complete=download_complete,
199
- pipeline_tag=metadata.get('pipeline_tag'),
200
- download_time=metadata.get('download_time'),
201
- avatar_url=metadata.get('avatar_url')
202
- ))
191
+ size_bytes, files = _get_directory_size_and_files(item_path)
192
+ if files: # Only include if there are files
193
+ # Check if the download is complete
194
+ download_complete = _check_for_incomplete_downloads(item_path)
195
+ # Load metadata if it exists
196
+ metadata = _load_download_metadata(item_path)
197
+ models.append(DownloadedModel(
198
+ repo_id=item,
199
+ files=files,
200
+ folder_type='direct_repo',
201
+ local_path=item_path,
202
+ size_bytes=size_bytes,
203
+ file_count=len(files),
204
+ full_repo_download_complete=download_complete,
205
+ pipeline_tag=metadata.get('pipeline_tag'),
206
+ download_time=metadata.get('download_time'),
207
+ avatar_url=metadata.get('avatar_url')
208
+ ))
203
209
 
204
210
  except (OSError, IOError):
205
211
  # Skip if base path can't be accessed
@@ -729,57 +735,27 @@ class HuggingFaceDownloader:
729
735
 
730
736
  def _fetch_and_save_metadata(self, repo_id: str, local_dir: str) -> None:
731
737
  """Fetch model info and save metadata after successful download."""
732
- # Initialize metadata with defaults to ensure manifest is always created
733
- old_metadata = {
734
- 'pipeline_tag': "text-generation", # Default to text-generation pipeline-tag
735
- 'download_time': datetime.now().isoformat(),
736
- 'avatar_url': None
737
- }
738
-
739
- # Try to fetch additional metadata, but don't let failures prevent manifest creation
740
738
  try:
741
739
  # Fetch model info to get pipeline_tag
742
740
  info = self.api.model_info(repo_id, token=self.token)
743
- if hasattr(info, 'pipeline_tag'):
744
- old_metadata['pipeline_tag'] = info.pipeline_tag
745
- except Exception as e:
746
- # Log the error but continue with manifest creation
747
- print(f"Warning: Could not fetch model info for {repo_id}: {e}")
748
-
749
- try:
741
+ pipeline_tag = info.pipeline_tag if hasattr(info, 'pipeline_tag') else None
742
+
750
743
  # Get avatar URL
751
744
  avatar_url = get_avatar_url_for_repo(repo_id, custom_endpoint=self.endpoint)
752
- if avatar_url:
753
- old_metadata['avatar_url'] = avatar_url
754
- except Exception as e:
755
- # Log the error but continue with manifest creation
756
- print(f"Warning: Could not fetch avatar URL for {repo_id}: {e}")
757
-
758
- # CRITICAL: Always create the manifest file, regardless of metadata fetch failures
759
- try:
760
- save_manifest_with_files_metadata(repo_id, local_dir, old_metadata)
761
- print(f"[OK] Successfully created nexa.manifest for {repo_id}")
762
- except Exception as e:
763
- # This is critical - if manifest creation fails, we should know about it
764
- print(f"ERROR: Failed to create nexa.manifest for {repo_id}: {e}")
765
- # Try a fallback approach - create a minimal manifest
766
- try:
767
- minimal_manifest = {
768
- "Name": repo_id,
769
- "ModelType": "other",
770
- "PluginId": "unknown",
771
- "ModelFile": {},
772
- "MMProjFile": {"Name": "", "Downloaded": False, "Size": 0},
773
- "TokenizerFile": {"Name": "", "Downloaded": False, "Size": 0},
774
- "ExtraFiles": None,
775
- "pipeline_tag": old_metadata.get('pipeline_tag'),
776
- "download_time": old_metadata.get('download_time'),
777
- "avatar_url": old_metadata.get('avatar_url')
778
- }
779
- save_download_metadata(local_dir, minimal_manifest)
780
- print(f"[OK] Created minimal nexa.manifest for {repo_id} as fallback")
781
- except Exception as fallback_error:
782
- print(f"CRITICAL ERROR: Could not create even minimal manifest for {repo_id}: {fallback_error}")
745
+
746
+ # Prepare metadata
747
+ metadata = {
748
+ 'pipeline_tag': pipeline_tag,
749
+ 'download_time': datetime.now().isoformat(),
750
+ 'avatar_url': avatar_url
751
+ }
752
+
753
+ # Save metadata to the repository directory
754
+ _save_download_metadata(local_dir, metadata)
755
+
756
+ except Exception:
757
+ # Don't fail the download if metadata fetch fails
758
+ pass
783
759
 
784
760
  def _download_single_file(
785
761
  self,
@@ -796,7 +772,7 @@ class HuggingFaceDownloader:
796
772
  # Check if file already exists
797
773
  local_file_path = os.path.join(file_local_dir, file_name)
798
774
  if not force_download and self._check_file_exists_and_valid(local_file_path):
799
- print(f"[SKIP] File already exists: {file_name}")
775
+ print(f" File already exists, skipping: {file_name}")
800
776
  # Stop progress tracking
801
777
  if progress_tracker:
802
778
  progress_tracker.stop_tracking()
@@ -844,6 +820,14 @@ class HuggingFaceDownloader:
844
820
  # Create a subdirectory for this specific repo
845
821
  repo_local_dir = self._create_repo_directory(local_dir, repo_id)
846
822
 
823
+ # Check if repository already exists (basic check for directory existence)
824
+ if not force_download and os.path.exists(repo_local_dir) and os.listdir(repo_local_dir):
825
+ print(f"✓ Repository already exists, skipping: {repo_id}")
826
+ # Stop progress tracking
827
+ if progress_tracker:
828
+ progress_tracker.stop_tracking()
829
+ return repo_local_dir
830
+
847
831
  try:
848
832
  download_kwargs = {
849
833
  'repo_id': repo_id,
@@ -903,7 +887,7 @@ class HuggingFaceDownloader:
903
887
  # Check if file already exists
904
888
  local_file_path = os.path.join(repo_local_dir, file_name)
905
889
  if not force_download and self._check_file_exists_and_valid(local_file_path):
906
- print(f"[SKIP] File already exists: {file_name}")
890
+ print(f" File already exists, skipping: {file_name}")
907
891
  overall_progress.update(1)
908
892
  continue
909
893
 
@@ -107,7 +107,7 @@ class DownloadProgressTracker:
107
107
  time_diff = current_time - self.last_time
108
108
 
109
109
  # Only calculate if we have a meaningful time difference (avoid division by very small numbers)
110
- if time_diff > 0.1: # At least 100ms between measurements
110
+ if time_diff > 0.5: # At least 500ms between measurements
111
111
  bytes_diff = current_downloaded - self.last_downloaded
112
112
 
113
113
  # Only calculate speed if bytes actually changed
@@ -118,14 +118,6 @@ class DownloadProgressTracker:
118
118
  self.speed_history.append(speed)
119
119
  if len(self.speed_history) > self.max_speed_history:
120
120
  self.speed_history.pop(0)
121
-
122
- # Update tracking variables when we actually calculate speed
123
- self.last_downloaded = current_downloaded
124
- self.last_time = current_time
125
- else:
126
- # First measurement - initialize tracking variables
127
- self.last_downloaded = current_downloaded
128
- self.last_time = current_time
129
121
 
130
122
  # Return the average of historical speeds if we have any
131
123
  # This ensures we show the last known speed even when skipping updates
@@ -165,9 +157,13 @@ class DownloadProgressTracker:
165
157
  total_file_sizes += data['total']
166
158
  active_file_count += 1
167
159
 
168
- # Calculate speed (tracking variables are updated internally)
160
+ # Calculate speed
169
161
  speed = self.calculate_speed(total_downloaded)
170
162
 
163
+ # Update tracking variables
164
+ self.last_downloaded = total_downloaded
165
+ self.last_time = time.time()
166
+
171
167
  # Determine total size - prioritize pre-fetched repo size, then aggregate file sizes
172
168
  if self.total_repo_size > 0:
173
169
  # Use pre-fetched repository info if available
@@ -249,11 +245,11 @@ class DownloadProgressTracker:
249
245
  if known_total and total_size_raw > 0:
250
246
  # Known total size - show actual progress
251
247
  filled_width = int(bar_width * min(percentage, 100) / 100)
252
- bar = '#' * filled_width + '-' * (bar_width - filled_width)
248
+ bar = '' * filled_width + '' * (bar_width - filled_width)
253
249
  else:
254
250
  # Unknown total size - show animated progress
255
251
  animation_pos = int(time.time() * 2) % bar_width
256
- bar = '-' * animation_pos + '#' + '-' * (bar_width - animation_pos - 1)
252
+ bar = '' * animation_pos + '' + '' * (bar_width - animation_pos - 1)
257
253
 
258
254
  # Format the progress line
259
255
  status = progress_data.get('status', 'unknown')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nexaai
3
- Version: 1.0.9
3
+ Version: 1.0.11rc1
4
4
  Summary: Python bindings for NexaSDK C-lib backend
5
5
  Author-email: "Nexa AI, Inc." <dev@nexa.ai>
6
6
  Project-URL: Homepage, https://github.com/NexaAI/nexasdk-bridge
@@ -21,7 +21,6 @@ Provides-Extra: mlx
21
21
  Requires-Dist: mlx; extra == "mlx"
22
22
  Requires-Dist: mlx-lm; extra == "mlx"
23
23
  Requires-Dist: mlx-vlm; extra == "mlx"
24
- Requires-Dist: mlx-embeddings; extra == "mlx"
25
24
  Requires-Dist: tokenizers; extra == "mlx"
26
25
  Requires-Dist: safetensors; extra == "mlx"
27
26
  Requires-Dist: Pillow; extra == "mlx"
@@ -1,6 +1,6 @@
1
1
  nexaai/__init__.py,sha256=Lt8NU57eTMtWrDYzpFeYR9XtGAPXqizynP83TPU0UW0,2105
2
- nexaai/_stub.cp310-win_amd64.pyd,sha256=prPCDVLT2TNclJO6TTUEqn7cIhKvCt-T0J6cobBaSlY,10752
3
- nexaai/_version.py,sha256=YNusSkVDDoIG_U-jPH9h6HT-4PWMEdZ7mfTwH3R1-xg,142
2
+ nexaai/_stub.cp310-win_amd64.pyd,sha256=7aqosa3n18u2ozQ1vEFLREjBe1LsDwbga_XtCvEtXDI,10752
3
+ nexaai/_version.py,sha256=IAZFz3Tmh0wm59QVo7wZ0vs1f6pEESeZiaGrhy9wl8Y,147
4
4
  nexaai/asr.py,sha256=_fsGaxpiU137bUtO5ujtFSYCI1RLsyeEm3Gf4GhHVRk,2118
5
5
  nexaai/base.py,sha256=qQBCiQVNzgpkQjZX9aiFDEdbAAe56TROKC3WnWra2Zg,1021
6
6
  nexaai/common.py,sha256=6keIpdX5XS5us4z79EMoa6RSkVze9SbbXax13IJ9yvs,3525
@@ -16,25 +16,25 @@ nexaai/asr_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  nexaai/asr_impl/mlx_asr_impl.py,sha256=XwMX3LYMeulp8cDS0TCCYcjvttFHAyDWQ_oMvABwQmI,3349
17
17
  nexaai/asr_impl/pybind_asr_impl.py,sha256=20o5SOPzhF9x41ra8L_qIM7YxCkYeLb5csSrNde-dds,1560
18
18
  nexaai/binds/__init__.py,sha256=tYvy0pFhoY29GstDT5r-oRiPRarPLECvJAkcamJItOg,83
19
- nexaai/binds/common_bind.cp310-win_amd64.pyd,sha256=0R-nkrDqQyEHgdp_U-CqjnExG75RQPqVSlaioQCu6yQ,201216
20
- nexaai/binds/embedder_bind.cp310-win_amd64.pyd,sha256=7MixJxfH945Z-PD6DR591YoSJjpH27RZiyrJsHdCEeM,182784
19
+ nexaai/binds/common_bind.cp310-win_amd64.pyd,sha256=V0gHlgHOmcKqFllX-4-0_WZkeGIfB1ZJhv2ygQHt2DE,201216
20
+ nexaai/binds/embedder_bind.cp310-win_amd64.pyd,sha256=c-FvvhkIQNffP4qPABCZd7P0I3M7dAu7q5iXJXliFUE,182784
21
21
  nexaai/binds/libcrypto-3-x64.dll,sha256=-Lau6pL5DpDXzpg9MED63gCeL8oRrSLI_e2LeaxIHqk,7314432
22
22
  nexaai/binds/libssl-3-x64.dll,sha256=Tzzyu5jRpUugFxr_65hbFlAtFpjxIDpOYMU1E0ijkJw,1313792
23
- nexaai/binds/llm_bind.cp310-win_amd64.pyd,sha256=M7IhRGIJA_BqYGLE2e_0aiF0Bo-YbfX-G8xANIfmhpk,162816
24
- nexaai/binds/nexa_bridge.dll,sha256=afAM7Sfz3Wx-Xy29D4FP0dgVuFbH1kpC2iPzqxVm8wc,178688
25
- nexaai/binds/nexa_llama_cpp/ggml-base.dll,sha256=KI4qpa6BKAO-v-q3F6kUE44oQp-X0Po41Hg-QWHGIxk,513536
26
- nexaai/binds/nexa_llama_cpp/ggml-cpu.dll,sha256=ZmPo74eaazq0Y-D_8FNIvtKJqI-xxq1xpVwUG65rlWo,656384
27
- nexaai/binds/nexa_llama_cpp/ggml-cuda.dll,sha256=kuAps8hxorUWCvpxrgVkZBGf-NQFsHFozX06wwjyF9A,302319616
28
- nexaai/binds/nexa_llama_cpp/ggml-vulkan.dll,sha256=5SclfC18rbvdhwhgClv-r31H_GIMbrZoIu8ZTgQSxFw,24686080
29
- nexaai/binds/nexa_llama_cpp/ggml.dll,sha256=c9eLBKMyy8yVaytb3_aRz8Q8LL16U8XhhKmJ7_FfeFw,66560
30
- nexaai/binds/nexa_llama_cpp/llama.dll,sha256=AGKbkn8-9EGnEGTZRudHiVPXZBZKbyCDO-18CbMjJBg,1587712
31
- nexaai/binds/nexa_llama_cpp/mtmd.dll,sha256=zH17sJtxpCWLCZMaQ3TiEydhvI0FJLZzU0E5-dxkenQ,557056
32
- nexaai/binds/nexa_llama_cpp/nexa_plugin.dll,sha256=eNqgG5-3XeNRpRRSudtr1gpbIIlD58_G-W_tqgs4dYY,1366528
23
+ nexaai/binds/llm_bind.cp310-win_amd64.pyd,sha256=-mpRfsSUoM-aYVlDW2pEq6v7tDPAzK0MtMjHySr_rr0,162816
24
+ nexaai/binds/nexa_bridge.dll,sha256=qfsnj_Jts1ReToEMqvofLrVK4vImTSD1GgOHPWg2kD4,166912
25
+ nexaai/binds/nexa_llama_cpp/ggml-base.dll,sha256=a3OsLcS9AAuQ-nKTDqi5BUYrfAU_6rY5pP7dnOgWzUQ,514560
26
+ nexaai/binds/nexa_llama_cpp/ggml-cpu.dll,sha256=M72uWKily3IsUgc1XDYlTA3q0t6RYrJMJmeRyk9-oa8,663552
27
+ nexaai/binds/nexa_llama_cpp/ggml-cuda.dll,sha256=JXo5QpjoldFTUU0QphmvTV52aJB01cWoak-e17IgKJs,315100160
28
+ nexaai/binds/nexa_llama_cpp/ggml-vulkan.dll,sha256=y3cUfOqckORGsyFTxT6g11RQyuEyYkV5JX_mLgWPyxY,26204160
29
+ nexaai/binds/nexa_llama_cpp/ggml.dll,sha256=MWRWpVOvWV4XL6gU-Dz8v9Hm2RprPsrbMdzIrKnbUTE,66560
30
+ nexaai/binds/nexa_llama_cpp/llama.dll,sha256=KBn-iK1UDBIMC-csWQNNz0wFG-5gevto5u7kMiRHK6M,1587712
31
+ nexaai/binds/nexa_llama_cpp/mtmd.dll,sha256=hc2C9kxlTtp-2bSi82BooYwa7riBo5bo9poIFWrY_tE,560128
32
+ nexaai/binds/nexa_llama_cpp/nexa_plugin.dll,sha256=cUdm1G651PTwriYabad1vDgXZ-vxWDxC6CVTpr5h0CE,1384448
33
33
  nexaai/cv_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  nexaai/cv_impl/mlx_cv_impl.py,sha256=QLd_8w90gtxH8kmssaDYatCTRvQNIJuUGKZNnYrmx6E,3317
35
35
  nexaai/cv_impl/pybind_cv_impl.py,sha256=aSOCAxmHrwJbEkSN6VX3Cykqlj_9RIpVrZXILul04GA,1096
36
36
  nexaai/embedder_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- nexaai/embedder_impl/mlx_embedder_impl.py,sha256=Kzd-veLNl95FbI2oEJMtr6qKbjtPDDajzsGUVjJfTRA,4598
37
+ nexaai/embedder_impl/mlx_embedder_impl.py,sha256=MN5vAGohgyEreLn3H7dg2JeWp2v8emLhrfIDGndk-us,4498
38
38
  nexaai/embedder_impl/pybind_embedder_impl.py,sha256=FoLsUrzF5cNtEsSFchPlapkdqLGFOUGNPx0Kc8hdCvA,3589
39
39
  nexaai/image_gen_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  nexaai/image_gen_impl/mlx_image_gen_impl.py,sha256=peUE9ue9ApaPlZVOICBWiHtd13sY40OWQbE8EjfIUMU,11511
@@ -50,15 +50,12 @@ nexaai/tts_impl/mlx_tts_impl.py,sha256=LcH9bVdIl3Q6lOzSUB_X2s-_nWFmlCl1yL7XSUK0f
50
50
  nexaai/tts_impl/pybind_tts_impl.py,sha256=n3z4zmPQayQJgAwcvETw0IBUCp8IYROuYFSg0tAy_8Y,1487
51
51
  nexaai/utils/avatar_fetcher.py,sha256=D01f8je-37Nd68zGw8MYK2m7y3fvGlC6h0KR-aN9kdU,3925
52
52
  nexaai/utils/decode.py,sha256=0Z9jDH4ICzw4YXj8nD4L-sMouDaev-TISGRQ4KzidWE,421
53
- nexaai/utils/manifest_utils.py,sha256=zMgQpf5dAgF2RjGhk73zBggxRDGMRKDGxh2a8m8kmYg,10045
54
- nexaai/utils/model_manager.py,sha256=EZ_wMTVIcLkuQTgtkHztKn2gc75S2oeaoKDdozswuC0,51458
55
- nexaai/utils/model_types.py,sha256=arIyb9q-1uG0nyUGdWZaxxDJAxv0cfnJEpjCzyELL5Q,1416
56
- nexaai/utils/progress_tracker.py,sha256=BztrFqtjwNUmeREwZ5m7H6ZcrVzQEbpZfsxndWh4z0A,15778
57
- nexaai/utils/quantization_utils.py,sha256=jjQaz7K4qH6TdP8Tnv5Ktb2viz8BaVBSOrb_jm3ns28,7889
53
+ nexaai/utils/model_manager.py,sha256=Ksl-tKq-a3miTUxEn6-SSOC_KVdn6RPjcUdkWmDDwCk,49767
54
+ nexaai/utils/progress_tracker.py,sha256=FmJBoOlzfQdc-TmccEav0cBR_iSNrrcskG3Fm1OrEJA,15482
58
55
  nexaai/vlm_impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
56
  nexaai/vlm_impl/mlx_vlm_impl.py,sha256=oY_qb9z_iF0zArBuY5CCYIvZcA3R0i_NKXrr_r-QSgg,10989
60
57
  nexaai/vlm_impl/pybind_vlm_impl.py,sha256=Hu8g8OXyPn8OzLQOpRSE5lfGmhjChiKj7fMRB8mC_cI,9147
61
- nexaai-1.0.9.dist-info/METADATA,sha256=nO4vHcnhCNKVyTCjQs6trVAXuygpCbqzvEBYdNadBIM,1229
62
- nexaai-1.0.9.dist-info/WHEEL,sha256=KUuBC6lxAbHCKilKua8R9W_TM71_-9Sg5uEP3uDWcoU,101
63
- nexaai-1.0.9.dist-info/top_level.txt,sha256=LRE2YERlrZk2vfuygnSzsEeqSknnZbz3Z1MHyNmBU4w,7
64
- nexaai-1.0.9.dist-info/RECORD,,
58
+ nexaai-1.0.11rc1.dist-info/METADATA,sha256=dpKfhFQkkUYBQnAsSGEuLLHEfe__vDuJ3R-WOHHCrmY,1186
59
+ nexaai-1.0.11rc1.dist-info/WHEEL,sha256=KUuBC6lxAbHCKilKua8R9W_TM71_-9Sg5uEP3uDWcoU,101
60
+ nexaai-1.0.11rc1.dist-info/top_level.txt,sha256=LRE2YERlrZk2vfuygnSzsEeqSknnZbz3Z1MHyNmBU4w,7
61
+ nexaai-1.0.11rc1.dist-info/RECORD,,
@@ -1,280 +0,0 @@
1
- """
2
- Manifest and metadata utilities for handling nexa.manifest files and model metadata.
3
-
4
- This module provides utilities to:
5
- - Load and save nexa.manifest files
6
- - Create GGUF and MLX manifests
7
- - Process manifest metadata (handle null fields, fetch avatars, etc.)
8
- - Manage backward compatibility with old download_metadata.json files
9
- """
10
-
11
- import os
12
- import json
13
- from datetime import datetime
14
- from typing import Dict, Any, List, Optional
15
-
16
- from .quantization_utils import (
17
- extract_quantization_from_filename,
18
- detect_quantization_for_mlx
19
- )
20
- from .model_types import (
21
- PIPELINE_TO_MODEL_TYPE,
22
- MODEL_TYPE_TO_PIPELINE
23
- )
24
-
25
-
26
- def process_manifest_metadata(manifest: Dict[str, Any], repo_id: str) -> Dict[str, Any]:
27
- """Process manifest metadata to handle null/missing fields."""
28
- # Handle pipeline_tag
29
- pipeline_tag = manifest.get('pipeline_tag')
30
- if not pipeline_tag:
31
- # Reverse map from ModelType if available
32
- model_type = manifest.get('ModelType')
33
- pipeline_tag = MODEL_TYPE_TO_PIPELINE.get(model_type) if model_type else None
34
-
35
- # Handle download_time - keep as null if missing
36
- download_time = manifest.get('download_time')
37
-
38
- # Handle avatar_url - fetch on-the-fly if missing/null
39
- avatar_url = manifest.get('avatar_url')
40
- if not avatar_url:
41
- try:
42
- from .avatar_fetcher import get_avatar_url_for_repo
43
- avatar_url = get_avatar_url_for_repo(repo_id)
44
- except Exception:
45
- # If fetching fails, leave as None
46
- avatar_url = None
47
-
48
- # Return processed metadata
49
- processed_manifest = manifest.copy()
50
- processed_manifest.update({
51
- 'pipeline_tag': pipeline_tag,
52
- 'download_time': download_time,
53
- 'avatar_url': avatar_url
54
- })
55
-
56
- return processed_manifest
57
-
58
-
59
- def load_nexa_manifest(directory_path: str) -> Dict[str, Any]:
60
- """Load manifest from nexa.manifest if it exists."""
61
- manifest_path = os.path.join(directory_path, 'nexa.manifest')
62
- if os.path.exists(manifest_path):
63
- try:
64
- with open(manifest_path, 'r', encoding='utf-8') as f:
65
- return json.load(f)
66
- except (json.JSONDecodeError, IOError):
67
- pass
68
- return {}
69
-
70
-
71
- def load_download_metadata(directory_path: str, repo_id: Optional[str] = None) -> Dict[str, Any]:
72
- """Load download metadata from nexa.manifest if it exists, fallback to old format."""
73
- # First try to load from new manifest format
74
- manifest = load_nexa_manifest(directory_path)
75
- if manifest and repo_id:
76
- # Process the manifest to handle null/missing fields
77
- return process_manifest_metadata(manifest, repo_id)
78
- elif manifest:
79
- # Return manifest as-is if no repo_id provided (for backward compatibility)
80
- return manifest
81
-
82
- # Fallback to old format for backward compatibility
83
- old_metadata_path = os.path.join(directory_path, 'download_metadata.json')
84
- if os.path.exists(old_metadata_path):
85
- try:
86
- with open(old_metadata_path, 'r', encoding='utf-8') as f:
87
- return json.load(f)
88
- except (json.JSONDecodeError, IOError):
89
- pass
90
- return {}
91
-
92
-
93
- def save_download_metadata(directory_path: str, metadata: Dict[str, Any]) -> None:
94
- """Save download metadata to nexa.manifest in the new format."""
95
- manifest_path = os.path.join(directory_path, 'nexa.manifest')
96
- try:
97
- with open(manifest_path, 'w', encoding='utf-8') as f:
98
- json.dump(metadata, f, indent=2)
99
- except IOError:
100
- # If we can't save metadata, don't fail the download
101
- pass
102
-
103
-
104
- def create_gguf_manifest(repo_id: str, files: List[str], directory_path: str, old_metadata: Dict[str, Any]) -> Dict[str, Any]:
105
- """Create GGUF format manifest."""
106
-
107
- # Load existing manifest to merge GGUF files if it exists
108
- existing_manifest = load_nexa_manifest(directory_path)
109
-
110
- model_files = {}
111
- if existing_manifest and "ModelFile" in existing_manifest:
112
- model_files = existing_manifest["ModelFile"].copy()
113
-
114
- # Process GGUF files
115
- for file_name in files:
116
- if file_name.endswith('.gguf'):
117
- # Use the new enum-based quantization extraction
118
- quantization_type = extract_quantization_from_filename(file_name)
119
- quant_level = quantization_type.value if quantization_type else "UNKNOWN"
120
-
121
- file_path = os.path.join(directory_path, file_name)
122
- file_size = 0
123
- if os.path.exists(file_path):
124
- try:
125
- file_size = os.path.getsize(file_path)
126
- except (OSError, IOError):
127
- pass
128
-
129
- model_files[quant_level] = {
130
- "Name": file_name,
131
- "Downloaded": True,
132
- "Size": file_size
133
- }
134
-
135
- manifest = {
136
- "Name": repo_id,
137
- "ModelType": PIPELINE_TO_MODEL_TYPE.get(old_metadata.get('pipeline_tag'), "other"),
138
- "PluginId": "llama_cpp",
139
- "ModelFile": model_files,
140
- "MMProjFile": {
141
- "Name": "",
142
- "Downloaded": False,
143
- "Size": 0
144
- },
145
- "TokenizerFile": {
146
- "Name": "",
147
- "Downloaded": False,
148
- "Size": 0
149
- },
150
- "ExtraFiles": None,
151
- # Preserve old metadata fields
152
- "pipeline_tag": old_metadata.get('pipeline_tag'),
153
- "download_time": old_metadata.get('download_time'),
154
- "avatar_url": old_metadata.get('avatar_url')
155
- }
156
-
157
- return manifest
158
-
159
-
160
- def create_mlx_manifest(repo_id: str, files: List[str], directory_path: str, old_metadata: Dict[str, Any]) -> Dict[str, Any]:
161
- """Create MLX format manifest."""
162
-
163
- model_files = {}
164
- extra_files = []
165
-
166
- # Try different methods to extract quantization for MLX models
167
- quantization_type = detect_quantization_for_mlx(repo_id, directory_path)
168
-
169
- # Use the detected quantization or default to "DEFAULT"
170
- quant_level = quantization_type.value if quantization_type else "DEFAULT"
171
-
172
- for file_name in files:
173
- file_path = os.path.join(directory_path, file_name)
174
- file_size = 0
175
- if os.path.exists(file_path):
176
- try:
177
- file_size = os.path.getsize(file_path)
178
- except (OSError, IOError):
179
- pass
180
-
181
- # Check if this is a main model file (safetensors but not index files)
182
- if (file_name.endswith('.safetensors') and not file_name.endswith('.index.json')):
183
- model_files[quant_level] = {
184
- "Name": file_name,
185
- "Downloaded": True,
186
- "Size": file_size
187
- }
188
- else:
189
- # Add to extra files
190
- extra_files.append({
191
- "Name": file_name,
192
- "Downloaded": True,
193
- "Size": file_size
194
- })
195
-
196
- manifest = {
197
- "Name": repo_id,
198
- "ModelType": PIPELINE_TO_MODEL_TYPE.get(old_metadata.get('pipeline_tag'), "other"),
199
- "PluginId": "mlx",
200
- "ModelFile": model_files,
201
- "MMProjFile": {
202
- "Name": "",
203
- "Downloaded": False,
204
- "Size": 0
205
- },
206
- "TokenizerFile": {
207
- "Name": "",
208
- "Downloaded": False,
209
- "Size": 0
210
- },
211
- "ExtraFiles": extra_files if extra_files else None,
212
- # Preserve old metadata fields
213
- "pipeline_tag": old_metadata.get('pipeline_tag'),
214
- "download_time": old_metadata.get('download_time'),
215
- "avatar_url": old_metadata.get('avatar_url')
216
- }
217
-
218
- return manifest
219
-
220
-
221
- def detect_model_type(files: List[str]) -> str:
222
- """Detect if this is a GGUF or MLX model based on file extensions."""
223
- has_gguf = any(f.endswith('.gguf') for f in files)
224
- has_safetensors = any(f.endswith('.safetensors') or 'safetensors' in f for f in files)
225
-
226
- if has_gguf:
227
- return "gguf"
228
- elif has_safetensors:
229
- return "mlx"
230
- else:
231
- # Default to mlx for other types
232
- return "mlx"
233
-
234
-
235
- def create_manifest_from_files(repo_id: str, files: List[str], directory_path: str, old_metadata: Dict[str, Any]) -> Dict[str, Any]:
236
- """
237
- Create appropriate manifest format based on detected model type.
238
-
239
- Args:
240
- repo_id: Repository ID
241
- files: List of files in the model directory
242
- directory_path: Path to the model directory
243
- old_metadata: Existing metadata (pipeline_tag, download_time, avatar_url)
244
-
245
- Returns:
246
- Dict containing the appropriate manifest format
247
- """
248
- model_type = detect_model_type(files)
249
-
250
- if model_type == "gguf":
251
- return create_gguf_manifest(repo_id, files, directory_path, old_metadata)
252
- else: # mlx or other
253
- return create_mlx_manifest(repo_id, files, directory_path, old_metadata)
254
-
255
-
256
- def save_manifest_with_files_metadata(repo_id: str, local_dir: str, old_metadata: Dict[str, Any]) -> None:
257
- """
258
- Create and save manifest based on files found in the directory.
259
-
260
- Args:
261
- repo_id: Repository ID
262
- local_dir: Local directory containing the model files
263
- old_metadata: Existing metadata to preserve
264
- """
265
- # Get list of files in the directory
266
- files = []
267
- try:
268
- for root, dirs, filenames in os.walk(local_dir):
269
- for filename in filenames:
270
- # Store relative path from the directory
271
- rel_path = os.path.relpath(os.path.join(root, filename), local_dir)
272
- files.append(rel_path)
273
- except (OSError, IOError):
274
- pass
275
-
276
- # Create appropriate manifest
277
- manifest = create_manifest_from_files(repo_id, files, local_dir, old_metadata)
278
-
279
- # Save manifest
280
- save_download_metadata(local_dir, manifest)
@@ -1,47 +0,0 @@
1
- """
2
- Model type mappings for HuggingFace pipeline tags to our internal model types.
3
-
4
- This module provides centralized model type mapping functionality to avoid
5
- circular imports between other utility modules.
6
- """
7
-
8
- from enum import Enum
9
- from typing import Dict
10
-
11
-
12
- class ModelTypeMapping(Enum):
13
- """Enum for mapping HuggingFace pipeline_tag to our ModelType."""
14
- TEXT_GENERATION = ("text-generation", "llm")
15
- IMAGE_TEXT_TO_TEXT = ("image-text-to-text", "vlm")
16
-
17
- def __init__(self, pipeline_tag: str, model_type: str):
18
- self.pipeline_tag = pipeline_tag
19
- self.model_type = model_type
20
-
21
-
22
- # Create mapping dictionaries from the enum
23
- PIPELINE_TO_MODEL_TYPE: Dict[str, str] = {
24
- mapping.pipeline_tag: mapping.model_type
25
- for mapping in ModelTypeMapping
26
- }
27
-
28
- MODEL_TYPE_TO_PIPELINE: Dict[str, str] = {
29
- mapping.model_type: mapping.pipeline_tag
30
- for mapping in ModelTypeMapping
31
- }
32
-
33
-
34
- def map_pipeline_tag_to_model_type(pipeline_tag: str) -> str:
35
- """Map HuggingFace pipeline_tag to our ModelType."""
36
- if not pipeline_tag:
37
- return "other"
38
-
39
- return PIPELINE_TO_MODEL_TYPE.get(pipeline_tag, "other")
40
-
41
-
42
- def map_model_type_to_pipeline_tag(model_type: str) -> str:
43
- """Reverse map ModelType back to HuggingFace pipeline_tag."""
44
- if not model_type:
45
- return None
46
-
47
- return MODEL_TYPE_TO_PIPELINE.get(model_type)
@@ -1,239 +0,0 @@
1
- """
2
- Quantization utilities for extracting quantization types from model files and configurations.
3
-
4
- This module provides utilities to extract quantization information from:
5
- - GGUF model filenames
6
- - MLX model repository IDs
7
- - MLX model config.json files
8
- """
9
-
10
- import os
11
- import json
12
- import re
13
- import logging
14
- from enum import Enum
15
- from typing import Optional
16
-
17
- # Set up logger
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class QuantizationType(str, Enum):
22
- """Enum for GGUF and MLX model quantization types."""
23
- # GGUF quantization types
24
- BF16 = "BF16"
25
- F16 = "F16"
26
- Q2_K = "Q2_K"
27
- Q2_K_L = "Q2_K_L"
28
- Q3_K_M = "Q3_K_M"
29
- Q3_K_S = "Q3_K_S"
30
- Q4_0 = "Q4_0"
31
- Q4_1 = "Q4_1"
32
- Q4_K_M = "Q4_K_M"
33
- Q4_K_S = "Q4_K_S"
34
- Q5_K_M = "Q5_K_M"
35
- Q5_K_S = "Q5_K_S"
36
- Q6_K = "Q6_K"
37
- Q8_0 = "Q8_0"
38
- MXFP4 = "MXFP4"
39
- MXFP8 = "MXFP8"
40
-
41
- # MLX bit-based quantization types
42
- BIT_1 = "1BIT"
43
- BIT_2 = "2BIT"
44
- BIT_3 = "3BIT"
45
- BIT_4 = "4BIT"
46
- BIT_5 = "5BIT"
47
- BIT_6 = "6BIT"
48
- BIT_7 = "7BIT"
49
- BIT_8 = "8BIT"
50
- BIT_16 = "16BIT"
51
-
52
-
53
- def extract_quantization_from_filename(filename: str) -> Optional[QuantizationType]:
54
- """
55
- Extract quantization type from filename.
56
-
57
- Args:
58
- filename: The filename to extract quantization from
59
-
60
- Returns:
61
- QuantizationType enum value or None if not found
62
- """
63
- # Define mapping from lowercase patterns to enum values
64
- # Include "." to ensure precise matching (e.g., "q4_0." not "q4_0_xl")
65
- pattern_to_enum = {
66
- 'bf16.': QuantizationType.BF16,
67
- 'f16.': QuantizationType.F16, # Add F16 support
68
- 'q2_k_l.': QuantizationType.Q2_K_L, # Check Q2_K_L before Q2_K to avoid partial match
69
- 'q2_k.': QuantizationType.Q2_K,
70
- 'q3_k_m.': QuantizationType.Q3_K_M,
71
- 'q3_ks.': QuantizationType.Q3_K_S,
72
- 'q4_k_m.': QuantizationType.Q4_K_M,
73
- 'q4_k_s.': QuantizationType.Q4_K_S,
74
- 'q4_0.': QuantizationType.Q4_0,
75
- 'q4_1.': QuantizationType.Q4_1,
76
- 'q5_k_m.': QuantizationType.Q5_K_M,
77
- 'q5_k_s.': QuantizationType.Q5_K_S,
78
- 'q6_k.': QuantizationType.Q6_K,
79
- 'q8_0.': QuantizationType.Q8_0,
80
- 'mxfp4.': QuantizationType.MXFP4,
81
- 'mxfp8.': QuantizationType.MXFP8,
82
- }
83
-
84
- filename_lower = filename.lower()
85
-
86
- # Check longer patterns first to avoid partial matches
87
- # Sort by length descending to check q2_k_l before q2_k, q4_k_m before q4_0, etc.
88
- for pattern in sorted(pattern_to_enum.keys(), key=len, reverse=True):
89
- if pattern in filename_lower:
90
- return pattern_to_enum[pattern]
91
-
92
- return None
93
-
94
-
95
- def extract_quantization_from_repo_id(repo_id: str) -> Optional[QuantizationType]:
96
- """
97
- Extract quantization type from repo_id for MLX models by looking for bit patterns.
98
-
99
- Args:
100
- repo_id: The repository ID to extract quantization from
101
-
102
- Returns:
103
- QuantizationType enum value or None if not found
104
- """
105
- # Define mapping from bit numbers to enum values
106
- bit_to_enum = {
107
- 1: QuantizationType.BIT_1,
108
- 2: QuantizationType.BIT_2,
109
- 3: QuantizationType.BIT_3,
110
- 4: QuantizationType.BIT_4,
111
- 5: QuantizationType.BIT_5,
112
- 6: QuantizationType.BIT_6,
113
- 7: QuantizationType.BIT_7,
114
- 8: QuantizationType.BIT_8,
115
- 16: QuantizationType.BIT_16,
116
- }
117
-
118
- # First check for patterns like "4bit", "8bit" etc. (case insensitive)
119
- pattern = r'(\d+)bit'
120
- matches = re.findall(pattern, repo_id.lower())
121
-
122
- for match in matches:
123
- try:
124
- bit_number = int(match)
125
- if bit_number in bit_to_enum:
126
- logger.debug(f"Found {bit_number}bit quantization in repo_id: {repo_id}")
127
- return bit_to_enum[bit_number]
128
- except ValueError:
129
- continue
130
-
131
- # Also check for patterns like "-q8", "_Q4" etc.
132
- q_pattern = r'[-_]q(\d+)'
133
- q_matches = re.findall(q_pattern, repo_id.lower())
134
-
135
- for match in q_matches:
136
- try:
137
- bit_number = int(match)
138
- if bit_number in bit_to_enum:
139
- logger.debug(f"Found Q{bit_number} quantization in repo_id: {repo_id}")
140
- return bit_to_enum[bit_number]
141
- except ValueError:
142
- continue
143
-
144
- return None
145
-
146
-
147
- def extract_quantization_from_mlx_config(mlx_folder_path: str) -> Optional[QuantizationType]:
148
- """
149
- Extract quantization type from MLX model's config.json file.
150
-
151
- Args:
152
- mlx_folder_path: Path to the MLX model folder
153
-
154
- Returns:
155
- QuantizationType enum value or None if not found
156
- """
157
- config_path = os.path.join(mlx_folder_path, "config.json")
158
-
159
- if not os.path.exists(config_path):
160
- logger.debug(f"Config file not found: {config_path}")
161
- return None
162
-
163
- try:
164
- with open(config_path, 'r', encoding='utf-8') as f:
165
- config = json.load(f)
166
-
167
- # Look for quantization.bits field
168
- quantization_config = config.get("quantization", {})
169
- if isinstance(quantization_config, dict):
170
- bits = quantization_config.get("bits")
171
- if isinstance(bits, int):
172
- # Define mapping from bit numbers to enum values
173
- bit_to_enum = {
174
- 1: QuantizationType.BIT_1,
175
- 2: QuantizationType.BIT_2,
176
- 3: QuantizationType.BIT_3,
177
- 4: QuantizationType.BIT_4,
178
- 5: QuantizationType.BIT_5,
179
- 6: QuantizationType.BIT_6,
180
- 7: QuantizationType.BIT_7,
181
- 8: QuantizationType.BIT_8,
182
- 16: QuantizationType.BIT_16,
183
- }
184
-
185
- if bits in bit_to_enum:
186
- logger.debug(f"Found {bits}bit quantization in config.json: {config_path}")
187
- return bit_to_enum[bits]
188
- else:
189
- logger.debug(f"Unsupported quantization bits value: {bits}")
190
-
191
- except (json.JSONDecodeError, IOError) as e:
192
- logger.warning(f"Error reading config.json from {config_path}: {e}")
193
- except Exception as e:
194
- logger.warning(f"Unexpected error reading config.json from {config_path}: {e}")
195
-
196
- return None
197
-
198
-
199
- def extract_gguf_quantization(filename: str) -> str:
200
- """
201
- Extract quantization level from GGUF filename using the enum-based approach.
202
-
203
- This function provides backward compatibility by returning a string representation
204
- of the quantization type.
205
-
206
- Args:
207
- filename: The GGUF filename
208
-
209
- Returns:
210
- String representation of the quantization type or "UNKNOWN" if not found
211
- """
212
- quantization_type = extract_quantization_from_filename(filename)
213
- if quantization_type:
214
- return quantization_type.value
215
- return "UNKNOWN"
216
-
217
-
218
- def detect_quantization_for_mlx(repo_id: str, directory_path: str) -> Optional[QuantizationType]:
219
- """
220
- Detect quantization for MLX models using multiple methods in priority order.
221
-
222
- Args:
223
- repo_id: The repository ID
224
- directory_path: Path to the model directory
225
-
226
- Returns:
227
- QuantizationType enum value or None if not found
228
- """
229
- # Method 1: Extract from repo_id
230
- quantization_type = extract_quantization_from_repo_id(repo_id)
231
- if quantization_type:
232
- return quantization_type
233
-
234
- # Method 2: Extract from config.json if available
235
- quantization_type = extract_quantization_from_mlx_config(directory_path)
236
- if quantization_type:
237
- return quantization_type
238
-
239
- return None