A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: Python server for running mlx models

madclaws 51d73d77 1e58a881

+4501
+3
server/.gitignore
··· 1 + __pycache__/ 2 + *.egg-info/ 3 + .venv/
+1
server/__init__.py
··· 1 +
+404
server/api.py
··· 1 + from fastapi import FastAPI, HTTPException 2 + from .config import SYSTEM_PROMPT 3 + 4 + import json 5 + import time 6 + import uuid 7 + from collections.abc import AsyncGenerator 8 + from typing import Any, Dict, List, Optional, Union 9 + 10 + from fastapi.responses import StreamingResponse 11 + from pydantic import BaseModel, Field 12 + 13 + from .cache_utils import ( 14 + detect_framework, 15 + get_model_path 16 + ) 17 + from .mlx_runner import MLXRunner 18 + 19 + # Global model cache and configuration 20 + _model_cache: Dict[str, MLXRunner] = {} 21 + _current_model_path: Optional[str] = None 22 + _default_max_tokens: Optional[int] = None # Use dynamic model-aware limits by default 23 + 24 + 25 + class CompletionRequest(BaseModel): 26 + model: str 27 + prompt: Union[str, List[str]] 28 + max_tokens: Optional[int] = None 29 + temperature: Optional[float] = 0.7 30 + top_p: Optional[float] = 0.9 31 + stream: Optional[bool] = False 32 + stop: Optional[Union[str, List[str]]] = None 33 + repetition_penalty: Optional[float] = 1.1 34 + 35 + 36 + class ChatMessage(BaseModel): 37 + role: str = Field(..., pattern="^(system|user|assistant)$") 38 + content: str 39 + 40 + 41 + class ChatCompletionRequest(BaseModel): 42 + model: str 43 + messages: List[ChatMessage] 44 + max_tokens: Optional[int] = None 45 + temperature: Optional[float] = 0.7 46 + top_p: Optional[float] = 0.9 47 + stream: Optional[bool] = False 48 + stop: Optional[Union[str, List[str]]] = None 49 + repetition_penalty: Optional[float] = 1.1 50 + 51 + 52 + class CompletionResponse(BaseModel): 53 + id: str 54 + object: str = "text_completion" 55 + created: int 56 + model: str 57 + choices: List[Dict[str, Any]] 58 + usage: Dict[str, int] 59 + 60 + 61 + class ChatCompletionResponse(BaseModel): 62 + id: str 63 + object: str = "chat.completion" 64 + created: int 65 + model: str 66 + choices: List[Dict[str, Any]] 67 + usage: Dict[str, int] 68 + 69 + 70 + class ModelInfo(BaseModel): 71 + id: str 72 + object: str = "model" 73 + owned_by: str = "mlx-knife" 74 + permission: List = [] 75 + context_length: Optional[int] = None 76 + 77 + 78 + app = FastAPI() 79 + 80 + def get_or_load_model(model_spec: str, verbose: bool = False) -> MLXRunner: 81 + """Get model from cache or load it if not cached.""" 82 + global _model_cache, _current_model_path 83 + 84 + print(model_spec) 85 + # Use the existing model path resolution from cache_utils 86 + 87 + try: 88 + model_path, model_name, commit_hash = get_model_path(model_spec) 89 + if not model_path.exists(): 90 + raise HTTPException(status_code=404, detail=f"Model {model_spec} not found in cache") 91 + except Exception as e: 92 + raise HTTPException(status_code=404, detail=f"Model {model_spec} not found: {str(e)}") 93 + 94 + # Check if it's an MLX model 95 + 96 + model_path_str = str(model_path) 97 + 98 + print(_current_model_path) 99 + print(model_path_str) 100 + # Check if we need to load a different model 101 + if _current_model_path != model_path_str: 102 + # Proactively clean up any previously loaded runner to release memory 103 + if _model_cache: 104 + try: 105 + for _old_runner in list(_model_cache.values()): 106 + try: 107 + _old_runner.cleanup() 108 + except Exception: 109 + pass 110 + finally: 111 + _model_cache.clear() 112 + 113 + # Load new model 114 + if verbose: 115 + print(f"Loading model: {model_name}") 116 + 117 + runner = MLXRunner(model_path_str, verbose=verbose) 118 + runner.load_model() 119 + 120 + _model_cache[model_path_str] = runner 121 + _current_model_path = model_path_str 122 + 123 + return _model_cache[model_path_str] 124 + 125 + async def generate_completion_stream( 126 + runner: MLXRunner, 127 + prompt: str, 128 + request: CompletionRequest 129 + ) -> AsyncGenerator[str, None]: 130 + """Generate streaming completion response.""" 131 + completion_id = f"cmpl-{uuid.uuid4()}" 132 + created = int(time.time()) 133 + 134 + # Yield initial response 135 + initial_response = { 136 + "id": completion_id, 137 + "object": "text_completion", 138 + "created": created, 139 + "model": request.model, 140 + "choices": [ 141 + { 142 + "index": 0, 143 + "text": "", 144 + "logprobs": None, 145 + "finish_reason": None 146 + } 147 + ] 148 + } 149 + 150 + yield f"data: {json.dumps(initial_response)}\n\n" 151 + 152 + # Stream tokens 153 + try: 154 + token_count = 0 155 + for token in runner.generate_streaming( 156 + prompt=prompt, 157 + max_tokens=runner.get_effective_max_tokens(request.max_tokens or _default_max_tokens, interactive=False), 158 + temperature=request.temperature, 159 + top_p=request.top_p, 160 + repetition_penalty=request.repetition_penalty, 161 + use_chat_template=False # Raw completion mode 162 + ): 163 + token_count += 1 164 + 165 + chunk_response = { 166 + "id": completion_id, 167 + "object": "text_completion", 168 + "created": created, 169 + "model": request.model, 170 + "choices": [ 171 + { 172 + "index": 0, 173 + "text": token, 174 + "logprobs": None, 175 + "finish_reason": None 176 + } 177 + ] 178 + } 179 + 180 + yield f"data: {json.dumps(chunk_response)}\n\n" 181 + 182 + # Check for stop sequences 183 + if request.stop: 184 + stop_sequences = request.stop if isinstance(request.stop, list) else [request.stop] 185 + if any(stop in token for stop in stop_sequences): 186 + break 187 + 188 + except Exception as e: 189 + error_response = { 190 + "id": completion_id, 191 + "object": "text_completion", 192 + "created": created, 193 + "model": request.model, 194 + "choices": [ 195 + { 196 + "index": 0, 197 + "text": "", 198 + "logprobs": None, 199 + "finish_reason": "error" 200 + } 201 + ], 202 + "error": str(e) 203 + } 204 + yield f"data: {json.dumps(error_response)}\n\n" 205 + 206 + # Final response 207 + final_response = { 208 + "id": completion_id, 209 + "object": "text_completion", 210 + "created": created, 211 + "model": request.model, 212 + "choices": [ 213 + { 214 + "index": 0, 215 + "text": "", 216 + "logprobs": None, 217 + "finish_reason": "stop" 218 + } 219 + ] 220 + } 221 + 222 + yield f"data: {json.dumps(final_response)}\n\n" 223 + yield "data: [DONE]\n\n" 224 + 225 + async def generate_chat_stream( 226 + runner: MLXRunner, 227 + messages: List[ChatMessage], 228 + request: ChatCompletionRequest 229 + ) -> AsyncGenerator[str, None]: 230 + """Generate streaming chat completion response.""" 231 + completion_id = f"chatcmpl-{uuid.uuid4()}" 232 + created = int(time.time()) 233 + 234 + # Convert messages to dict format for runner 235 + message_dicts = format_chat_messages_for_runner(messages) 236 + 237 + # Let the runner format with chat templates 238 + prompt = runner._format_conversation(message_dicts, use_chat_template=True) 239 + 240 + # Yield initial response 241 + initial_response = { 242 + "id": completion_id, 243 + "object": "chat.completion.chunk", 244 + "created": created, 245 + "model": request.model, 246 + "choices": [ 247 + { 248 + "index": 0, 249 + "delta": {"role": "assistant", "content": ""}, 250 + "finish_reason": None 251 + } 252 + ] 253 + } 254 + 255 + yield f"data: {json.dumps(initial_response)}\n\n" 256 + 257 + # Stream tokens 258 + try: 259 + for token in runner.generate_streaming( 260 + prompt=prompt, 261 + max_tokens=runner.get_effective_max_tokens(request.max_tokens or _default_max_tokens, interactive=False), 262 + temperature=request.temperature, 263 + top_p=request.top_p, 264 + repetition_penalty=request.repetition_penalty, 265 + use_chat_template=False, # Already applied in _format_conversation 266 + use_chat_stop_tokens=False # Server mode shouldn't stop on chat markers 267 + ): 268 + chunk_response = { 269 + "id": completion_id, 270 + "object": "chat.completion.chunk", 271 + "created": created, 272 + "model": request.model, 273 + "choices": [ 274 + { 275 + "index": 0, 276 + "delta": {"content": token}, 277 + "finish_reason": None 278 + } 279 + ] 280 + } 281 + 282 + yield f"data: {json.dumps(chunk_response)}\n\n" 283 + 284 + # Check for stop sequences 285 + if request.stop: 286 + stop_sequences = request.stop if isinstance(request.stop, list) else [request.stop] 287 + if any(stop in token for stop in stop_sequences): 288 + break 289 + 290 + except Exception as e: 291 + error_response = { 292 + "id": completion_id, 293 + "object": "chat.completion.chunk", 294 + "created": created, 295 + "model": request.model, 296 + "choices": [ 297 + { 298 + "index": 0, 299 + "delta": {}, 300 + "finish_reason": "error" 301 + } 302 + ], 303 + "error": str(e) 304 + } 305 + yield f"data: {json.dumps(error_response)}\n\n" 306 + 307 + # Final response 308 + final_response = { 309 + "id": completion_id, 310 + "object": "chat.completion.chunk", 311 + "created": created, 312 + "model": request.model, 313 + "choices": [ 314 + { 315 + "index": 0, 316 + "delta": {}, 317 + "finish_reason": "stop" 318 + } 319 + ] 320 + } 321 + 322 + yield f"data: {json.dumps(final_response)}\n\n" 323 + yield "data: [DONE]\n\n" 324 + 325 + 326 + def format_chat_messages_for_runner(messages: List[ChatMessage]) -> List[Dict[str, str]]: 327 + """Convert chat messages to format expected by MLXRunner. 328 + 329 + Returns messages in dict format for the runner to apply chat templates. 330 + """ 331 + system_message = ChatMessage(role="system", content=SYSTEM_PROMPT) 332 + messages.append(system_message) 333 + return [{"role": msg.role, "content": msg.content} for msg in messages] 334 + 335 + 336 + def count_tokens(text: str) -> int: 337 + """Rough token count estimation.""" 338 + return int(len(text.split()) * 1.3) # Approximation, convert to int 339 + 340 + @app.get("/ping") 341 + async def ping(): 342 + return {"message": "ping"} 343 + 344 + 345 + @app.post("/v1/chat/completions") 346 + async def create_chat_completion(request: ChatCompletionRequest): 347 + """Create a chat completion.""" 348 + try: 349 + runner = get_or_load_model(request.model) 350 + 351 + # if request.stream: 352 + # # Streaming response 353 + # return StreamingResponse( 354 + # generate_chat_stream(runner, request.messages, request), 355 + # media_type="text/plain", 356 + # headers={"Cache-Control": "no-cache"} 357 + # ) 358 + # else: 359 + # Non-streaming response 360 + completion_id = f"chatcmpl-{uuid.uuid4()}" 361 + created = int(time.time()) 362 + 363 + # Convert messages to dict format for runner 364 + message_dicts = format_chat_messages_for_runner(request.messages) 365 + # conversation_history.append({"role": "system", "content": system_prompt}) 366 + # Let the runner format with chat templates 367 + prompt = runner._format_conversation(message_dicts, use_chat_template=True) 368 + 369 + generated_text = runner.generate_batch( 370 + prompt=prompt, 371 + max_tokens=runner.get_effective_max_tokens(request.max_tokens or _default_max_tokens, interactive=False), 372 + temperature=request.temperature, 373 + top_p=request.top_p, 374 + repetition_penalty=request.repetition_penalty, 375 + use_chat_template=False # Already applied in _format_conversation 376 + ) 377 + 378 + # Token counting 379 + total_prompt = "\n\n".join([msg.content for msg in request.messages]) 380 + prompt_tokens = count_tokens(total_prompt) 381 + completion_tokens = count_tokens(generated_text) 382 + 383 + return ChatCompletionResponse( 384 + id=completion_id, 385 + created=created, 386 + model=request.model, 387 + choices=[ 388 + { 389 + "index": 0, 390 + "message": { 391 + "role": "assistant", 392 + "content": generated_text 393 + }, 394 + "finish_reason": "stop" 395 + } 396 + ], 397 + usage={ 398 + "prompt_tokens": prompt_tokens, 399 + "completion_tokens": completion_tokens, 400 + "total_tokens": prompt_tokens + completion_tokens 401 + } 402 + ) 403 + except Exception as e: 404 + raise HTTPException(status_code=500, detail=str(e))
+1074
server/cache_utils.py
··· 1 + # mlx_knife/cache_utils.py 2 + 3 + import datetime 4 + import json 5 + import os 6 + import shutil 7 + import sys 8 + from pathlib import Path 9 + 10 + # Issue #31 hints reader 11 + from .model_card import read_readme_front_matter, tokenizer_has_chat_template 12 + 13 + DEFAULT_CACHE_ROOT = Path.home() / ".cache/huggingface" 14 + CACHE_ROOT = Path(os.environ.get("HF_HOME", DEFAULT_CACHE_ROOT)) 15 + MODEL_CACHE = CACHE_ROOT / "hub" 16 + 17 + # Global variable to track if warning was shown 18 + _legacy_warning_shown = False 19 + 20 + # Check for models in legacy location and warn user 21 + _legacy_models = list(CACHE_ROOT.glob("models--*")) 22 + _is_test_env = "test_cache" in str(CACHE_ROOT) or "PYTEST_CURRENT_TEST" in os.environ 23 + if _legacy_models and not _legacy_warning_shown and not _is_test_env: 24 + print(f"\n⚠️ Found {len(_legacy_models)} models in legacy location: {CACHE_ROOT}") 25 + print(f" Please move them to: {MODEL_CACHE}") 26 + print(f" Command: mv {CACHE_ROOT}/models--* {MODEL_CACHE}/") 27 + print(" This warning will appear until models are moved.\n") 28 + _legacy_warning_shown = True 29 + 30 + 31 + def hf_to_cache_dir(hf_name: str) -> str: 32 + if hf_name.startswith("models--"): 33 + return hf_name 34 + if "/" in hf_name: 35 + org, model = hf_name.split("/", 1) 36 + return f"models--{org}--{model}" 37 + else: 38 + return f"models--{hf_name}" 39 + 40 + def cache_dir_to_hf(cache_name: str) -> str: 41 + if cache_name.startswith("models--"): 42 + remaining = cache_name[len("models--"):] 43 + if "--" in remaining: 44 + parts = remaining.split("--", 1) 45 + return f"{parts[0]}/{parts[1]}" 46 + else: 47 + return remaining 48 + return cache_name 49 + 50 + def expand_model_name(model_name): 51 + if "/" in model_name: 52 + return model_name 53 + mlx_candidate = f"mlx-community/{model_name}" 54 + mlx_cache_dir = MODEL_CACHE / hf_to_cache_dir(mlx_candidate) 55 + if mlx_cache_dir.exists(): 56 + return mlx_candidate 57 + common_mlx_patterns = [ 58 + "Llama-", "Qwen", "Mistral", "Phi-", "Mixtral", "phi-", "deepseek" 59 + ] 60 + for pattern in common_mlx_patterns: 61 + if pattern in model_name: 62 + return f"mlx-community/{model_name}" 63 + return model_name 64 + 65 + def find_matching_models(pattern): 66 + """Find models that match a partial pattern. Returns a list of (model_dir, hf_name) tuples.""" 67 + all_models = [d for d in MODEL_CACHE.iterdir() if d.name.startswith("models--")] 68 + matches = [] 69 + 70 + for model_dir in all_models: 71 + hf_name = cache_dir_to_hf(model_dir.name) 72 + # Check if the pattern appears in the model name (case insensitive) 73 + if pattern.lower() in hf_name.lower(): 74 + matches.append((model_dir, hf_name)) 75 + 76 + return matches 77 + 78 + def hash_exists_in_local_cache(model_name, commit_hash): 79 + """Check if a specific commit hash exists in the local cache for a model. 80 + 81 + Supports both full hashes and short hash prefixes (local resolution only). 82 + 83 + Args: 84 + model_name: Full model name (e.g., 'mlx-community/Phi-3-mini-4k-instruct-4bit') 85 + commit_hash: Commit hash to check for (short or full) 86 + 87 + Returns: 88 + Full hash if exists in local cache, None otherwise 89 + """ 90 + base_cache_dir = MODEL_CACHE / hf_to_cache_dir(model_name) 91 + if not base_cache_dir.exists(): 92 + return None 93 + 94 + snapshots_dir = base_cache_dir / "snapshots" 95 + if not snapshots_dir.exists(): 96 + return None 97 + 98 + # Check for exact match first (full hash) 99 + hash_dir = snapshots_dir / commit_hash 100 + if hash_dir.exists(): 101 + return commit_hash 102 + 103 + # Check for short hash match (local resolution) 104 + if len(commit_hash) < 40: 105 + for snapshot_dir in snapshots_dir.iterdir(): 106 + if snapshot_dir.is_dir() and snapshot_dir.name.startswith(commit_hash): 107 + return snapshot_dir.name # Return full hash 108 + 109 + return None 110 + 111 + def resolve_single_model(model_spec): 112 + """ 113 + Resolve a model spec to a single model, supporting fuzzy matching. 114 + Returns (model_path, model_name, commit_hash) or (None, None, None) if failed. 115 + Prints appropriate error messages for ambiguous matches. 116 + """ 117 + # Parse the model spec (handles @commit_hash syntax) 118 + model_name, commit_hash = parse_model_spec(model_spec) 119 + 120 + # Try exact match first 121 + base_cache_dir = MODEL_CACHE / hf_to_cache_dir(model_name) 122 + if base_cache_dir.exists(): 123 + return get_model_path(model_spec) 124 + 125 + # Extract the base name (without @commit_hash) for fuzzy matching 126 + base_spec = model_spec.split('@')[0] if '@' in model_spec else model_spec 127 + 128 + # Try fuzzy matching 129 + matches = find_matching_models(base_spec) 130 + 131 + if not matches: 132 + print(f"No models found matching '{base_spec}'!") 133 + return None, None, None 134 + elif len(matches) == 1: 135 + # Unambiguous match - use the found model with the original commit hash (if any) 136 + found_model_dir, found_hf_name = matches[0] 137 + if commit_hash: 138 + resolved_spec = f"{found_hf_name}@{commit_hash}" 139 + else: 140 + resolved_spec = found_hf_name 141 + return get_model_path(resolved_spec) 142 + elif len(matches) > 1 and commit_hash: 143 + # Issue #13: Hash-based disambiguation for ambiguous model names 144 + for _model_dir, hf_name in matches: 145 + resolved_hash = hash_exists_in_local_cache(hf_name, commit_hash) 146 + if resolved_hash: 147 + resolved_spec = f"{hf_name}@{resolved_hash}" 148 + return get_model_path(resolved_spec) 149 + 150 + # Hash not found in any candidate model 151 + print(f"Hash '{commit_hash}' not found in any model matching '{base_spec}'") 152 + print("Available models:") 153 + for _, hf_name in sorted(matches, key=lambda x: x[1]): 154 + print(f" {hf_name}") 155 + return None, None, None 156 + else: 157 + # Multiple matches without hash - show error with suggestions 158 + print(f"Multiple models match '{base_spec}'. Please be more specific:") 159 + for _, hf_name in sorted(matches, key=lambda x: x[1]): 160 + print(f" {hf_name}") 161 + return None, None, None 162 + 163 + def get_model_path(model_spec): 164 + model_name, commit_hash = parse_model_spec(model_spec) 165 + base_cache_dir = MODEL_CACHE / hf_to_cache_dir(model_name) 166 + print(base_cache_dir) 167 + if not base_cache_dir.exists(): 168 + return None, model_name, commit_hash 169 + if commit_hash: 170 + hash_dir = base_cache_dir / "snapshots" / commit_hash 171 + if hash_dir.exists(): 172 + return hash_dir, model_name, commit_hash 173 + else: 174 + return None, model_name, commit_hash 175 + snapshots_dir = base_cache_dir / "snapshots" 176 + if snapshots_dir.exists(): 177 + snapshots = [d for d in snapshots_dir.iterdir() if d.is_dir()] 178 + if snapshots: 179 + latest = max(snapshots, key=lambda x: x.stat().st_mtime) 180 + return latest, model_name, latest.name 181 + # Return base_cache_dir for corrupted models so rm_model can handle them 182 + return base_cache_dir, model_name, commit_hash 183 + 184 + def parse_model_spec(model_spec): 185 + if "@" in model_spec: 186 + model_name, commit_hash = model_spec.rsplit("@", 1) 187 + model_name = expand_model_name(model_name) 188 + return model_name, commit_hash 189 + model_name = expand_model_name(model_spec) 190 + return model_name, None 191 + 192 + def get_model_size(model_path): 193 + if not model_path.exists(): 194 + return "?" 195 + total_size = 0 196 + for file in model_path.rglob("*"): 197 + if file.is_file(): 198 + total_size += file.stat().st_size 199 + if total_size >= 1_000_000_000: 200 + return f"{total_size / 1_000_000_000:.1f} GB" 201 + elif total_size >= 1_000_000: 202 + return f"{total_size / 1_000_000:.1f} MB" 203 + else: 204 + return f"{total_size / 1_000:.1f} KB" 205 + 206 + def get_model_modified(model_path): 207 + if not model_path.exists(): 208 + return "?" 209 + mtime = model_path.stat().st_mtime 210 + now = datetime.datetime.now() 211 + modified = datetime.datetime.fromtimestamp(mtime) 212 + diff = now - modified 213 + if diff.days > 0: 214 + return f"{diff.days} days ago" 215 + elif diff.seconds > 3600: 216 + hours = diff.seconds // 3600 217 + return f"{hours} hours ago" 218 + else: 219 + minutes = diff.seconds // 60 220 + return f"{minutes} minutes ago" 221 + 222 + def detect_framework(model_path, hf_name): 223 + """Detect model framework with lenient hints (Issue #31).""" 224 + # 1) org hint 225 + if "mlx-community" in hf_name: 226 + return "MLX" 227 + 228 + # 2) README front matter: tags contains 'mlx' OR library_name == 'mlx' 229 + try: 230 + tags, pipeline, lib = read_readme_front_matter(Path(model_path)) 231 + if (lib and lib.lower() == "mlx") or (tags and any((t or '').lower() == "mlx" for t in tags)): 232 + return "MLX" 233 + except Exception: 234 + pass 235 + 236 + # 3) Fallback by file types 237 + snapshots_dir = Path(model_path) / "snapshots" 238 + if not snapshots_dir.exists(): 239 + return "Unknown" 240 + has_gguf = any(snapshots_dir.glob("*/*.gguf")) 241 + has_safetensors = any(snapshots_dir.glob("*/*.safetensors")) 242 + has_pytorch_bin = any(snapshots_dir.glob("*/pytorch_model.bin")) 243 + has_config = any(snapshots_dir.glob("*/*.json")) 244 + total_size = get_model_size(Path(model_path)) 245 + try: 246 + size_mb = float(total_size.replace(" GB", "000").replace(" MB", "").replace(" KB", "0").replace(" ", "")) 247 + except Exception: 248 + size_mb = 0 249 + if has_gguf: 250 + return "GGUF" 251 + if size_mb < 10: 252 + return "Tokenizer" 253 + if (has_safetensors and has_config) or has_pytorch_bin: 254 + return "PyTorch" 255 + return "Unknown" 256 + 257 + 258 + def detect_model_type(model_path, hf_name): 259 + """Detect model type with priority hints (Issue #31).""" 260 + # 1) tokenizer chat_template 261 + try: 262 + if tokenizer_has_chat_template(Path(model_path)): 263 + return "chat" 264 + except Exception: 265 + pass 266 + 267 + # 2) README hints 268 + try: 269 + tags, pipeline, _ = read_readme_front_matter(Path(model_path)) 270 + tset = {t.lower() for t in (tags or [])} 271 + if pipeline == "text-generation" or any(k in tset for k in {"chat", "instruct"}): 272 + return "chat" 273 + if pipeline == "sentence-similarity" or any(k in tset for k in {"embedding", "embeddings"}): 274 + return "embedding" 275 + except Exception: 276 + pass 277 + 278 + # 3) Fallback by name 279 + name = str(hf_name).lower() 280 + if "instruct" in name or "chat" in name: 281 + return "chat" 282 + if "embed" in name: 283 + return "embedding" 284 + return "base" 285 + 286 + 287 + def get_quantization_info(model_path): 288 + """Extract quantization information from model config.""" 289 + try: 290 + config_path = Path(model_path) / "config.json" 291 + if not config_path.exists(): 292 + return None 293 + with open(config_path) as f: 294 + cfg = json.load(f) 295 + return cfg.get("quantization") 296 + except Exception: 297 + return None 298 + 299 + def get_model_hash(model_path): 300 + snapshots_dir = model_path / "snapshots" 301 + if not snapshots_dir.exists(): 302 + return "--------" 303 + snapshots = [d for d in snapshots_dir.iterdir() if d.is_dir()] 304 + if not snapshots: 305 + return "--------" 306 + latest = max(snapshots, key=lambda x: x.stat().st_mtime) 307 + return latest.name[:8] 308 + 309 + def is_model_healthy(model_spec): 310 + """Strict health check for 1.x (backport of #27 rules). 311 + 312 + Rules: 313 + - config.json must exist and be valid non-empty JSON object. 314 + - If a safetensors or PyTorch index exists, all referenced shards must exist, be non-empty, 315 + and not be Git LFS pointer files. 316 + - Without an index: if multi-shard pattern files exist (model-XXXXX-of-YYYYY.*), require index (unhealthy without index). 317 + Single-file weights (*.safetensors/*.bin/*.gguf) are allowed if non-empty and not LFS pointers. 318 + - Any '.partial'/'partial' or '.tmp' artifacts anywhere => unhealthy. 319 + - Recursive LFS pointer scan for suspiciously small files (<200B). 320 + """ 321 + 322 + # Resolve model path: accept direct directory paths or model specs 323 + candidate = Path(str(model_spec)) 324 + if candidate.exists() and candidate.is_dir(): 325 + model_path = candidate 326 + else: 327 + model_path, _, _ = resolve_single_model(model_spec) 328 + if not model_path: 329 + return False 330 + 331 + # 1) config.json must be valid, non-empty dict 332 + config_path = model_path / "config.json" 333 + if not config_path.exists(): 334 + return False 335 + try: 336 + with open(config_path) as f: 337 + config_data = json.load(f) 338 + if not isinstance(config_data, dict) or not config_data: 339 + return False 340 + except (OSError, json.JSONDecodeError): 341 + return False 342 + 343 + # 2) Fail fast on partial/tmp markers anywhere in the snapshot 344 + for p in model_path.rglob("*"): 345 + name = p.name.lower() 346 + if ".partial" in name or name.endswith(".partial") or name.endswith(".tmp") or "partial" in name: 347 + return False 348 + 349 + # Helper: detect Git LFS pointer file 350 + def _is_lfs_pointer(fp: Path) -> bool: 351 + try: 352 + if fp.stat().st_size >= 200: 353 + return False 354 + with open(fp, "rb") as f: 355 + head = f.read(200) 356 + return b"version https://git-lfs.github.com/spec/v1" in head 357 + except Exception: 358 + return False 359 + 360 + # Helper: verify referenced shards 361 + def _verify_shards(files: list[Path]) -> bool: 362 + if not files: 363 + return False 364 + for f in files: 365 + try: 366 + if (not f.exists()) or f.stat().st_size == 0: 367 + return False 368 + if _is_lfs_pointer(f): 369 + return False 370 + except Exception: 371 + return False 372 + return True 373 + 374 + # 3) Index-aware checks (safetensors or PyTorch) 375 + st_index = model_path / "model.safetensors.index.json" 376 + pt_index = model_path / "pytorch_model.bin.index.json" 377 + if st_index.exists() or pt_index.exists(): 378 + index_files = [p for p in [st_index, pt_index] if p.exists()] 379 + for idx in index_files: 380 + try: 381 + with open(idx) as f: 382 + idx_data = json.load(f) 383 + weight_map = idx_data.get("weight_map") 384 + if not isinstance(weight_map, dict) or not weight_map: 385 + return False 386 + referenced = sorted(set(weight_map.values())) 387 + shard_paths = [model_path / r for r in referenced] 388 + if not _verify_shards(shard_paths): 389 + return False 390 + except (OSError, json.JSONDecodeError): 391 + return False 392 + # Also ensure no recursive LFS pointers elsewhere 393 + ok, _ = check_lfs_corruption(model_path) 394 + return ok 395 + 396 + # 4) No index present — detect multi-shard pattern 397 + # If pattern shards exist, require index (unhealthy without index by policy parity with 2.0) 398 + import re 399 + shard_re = re.compile(r"model-([0-9]{5})-of-([0-9]{5})\.(safetensors|bin)") 400 + pattern_files = [] 401 + for f in model_path.glob("*"): 402 + if f.is_file(): 403 + m = shard_re.match(f.name) 404 + if m: 405 + pattern_files.append((f, int(m.group(1)), int(m.group(2)))) 406 + if pattern_files: 407 + # Even if complete by pattern, absence of index => unhealthy 408 + return False 409 + 410 + # 5) Single-file weights fallback (includes GGUF) 411 + weight_files = list(model_path.rglob("*.safetensors")) + list(model_path.rglob("*.bin")) + list(model_path.rglob("*.gguf")) 412 + # Exclude known pattern shards from consideration (handled above) 413 + filtered_weights = [] 414 + for f in weight_files: 415 + name = f.name 416 + if shard_re.match(name): 417 + continue 418 + filtered_weights.append(f) 419 + if not filtered_weights: 420 + return False 421 + for wf in filtered_weights: 422 + if wf.stat().st_size == 0 or _is_lfs_pointer(wf): 423 + return False 424 + 425 + # Final recursive LFS scan 426 + ok, _ = check_lfs_corruption(model_path) 427 + return ok 428 + 429 + def check_lfs_corruption(model_path): 430 + """Recursively scan for Git LFS pointer files (suspiciously small files).""" 431 + corrupted_files = [] 432 + for file_path in model_path.rglob("*"): 433 + try: 434 + if file_path.is_file() and file_path.stat().st_size < 200: 435 + with open(file_path, 'rb') as f: 436 + header = f.read(200) 437 + if b'version https://git-lfs.github.com/spec/v1' in header: 438 + corrupted_files.append(str(file_path.relative_to(model_path))) 439 + except Exception: 440 + # Ignore unreadable files in corruption scan, keep conservative 441 + continue 442 + if corrupted_files: 443 + return False, f"LFS pointers instead of files: {', '.join(corrupted_files)}" 444 + return True, "No LFS corruption detected" 445 + 446 + def check_model_health(model_spec): 447 + model_path, model_name, commit_hash = resolve_single_model(model_spec) 448 + if not model_path: 449 + # resolve_single_model already printed the appropriate error message 450 + return False 451 + 452 + print(f"Checking model: {model_name}") 453 + if commit_hash: 454 + print(f"Hash: {commit_hash}") 455 + 456 + # Use the robust health check 457 + if is_model_healthy(model_spec): 458 + print("\n[OK] Model is healthy and usable!") 459 + return True 460 + else: 461 + # Detailed diagnosis for WHY it's unhealthy 462 + print("\n[ERROR] Model is corrupted. Detailed diagnosis:") 463 + 464 + # Check config.json 465 + config_path = model_path / "config.json" 466 + if not config_path.exists(): 467 + print(" - config.json missing") 468 + else: 469 + try: 470 + with open(config_path) as f: 471 + config_data = json.load(f) 472 + if not isinstance(config_data, dict) or len(config_data) == 0: 473 + print(" - config.json is empty or invalid") 474 + else: 475 + print(" - config.json found and valid") 476 + except (OSError, json.JSONDecodeError): 477 + print(" - config.json exists but contains invalid JSON") 478 + 479 + # Check weight files (including gguf support like is_model_healthy) 480 + weight_files = list(model_path.glob("*.safetensors")) + list(model_path.glob("*.bin")) + list(model_path.glob("*.gguf")) 481 + if not weight_files: 482 + weight_files = list(model_path.glob("**/*.safetensors")) + list(model_path.glob("**/*.bin")) + list(model_path.glob("**/*.gguf")) 483 + 484 + if weight_files: 485 + total_size = sum(f.stat().st_size for f in weight_files) 486 + size_mb = total_size / (1024 * 1024) 487 + print(f" - Model weights found ({len(weight_files)} files, {size_mb:.1f}MB)") 488 + elif (model_path / "model.safetensors.index.json").exists(): 489 + # Check multi-file model 490 + try: 491 + with open(model_path / "model.safetensors.index.json") as f: 492 + index = json.load(f) 493 + if 'weight_map' in index: 494 + referenced_files = set(index['weight_map'].values()) 495 + existing_files = [f for f in referenced_files if (model_path / f).exists()] 496 + if existing_files: 497 + total_size = sum((model_path / f).stat().st_size for f in existing_files) 498 + size_mb = total_size / (1024 * 1024) 499 + print(f" - Multi-file weights ({len(existing_files)}/{len(referenced_files)} files, {size_mb:.1f}MB)") 500 + if len(existing_files) < len(referenced_files): 501 + print(" - Incomplete multi-file model") 502 + else: 503 + print(" - Multi-file model index found but no weight files exist") 504 + else: 505 + print(" - Multi-file model index is invalid") 506 + except Exception as e: 507 + print(f" - Multi-file model index error: {e}") 508 + else: 509 + print(" - No model weights found (.safetensors, .bin, .gguf)") 510 + 511 + # Check LFS corruption 512 + lfs_ok, lfs_msg = check_lfs_corruption(model_path) 513 + if not lfs_ok: 514 + print(f" - {lfs_msg}") 515 + else: 516 + print(f" - {lfs_msg}") 517 + 518 + # Show framework 519 + framework = detect_framework(model_path.parent.parent, model_name) 520 + print(f" - Framework: {framework}") 521 + 522 + # Offer deletion for corrupted models 523 + confirm = input("\nModel appears corrupted. Delete? [y/N] ") 524 + if confirm.lower() == "y": 525 + import errno 526 + import shutil 527 + try: 528 + if commit_hash: 529 + # Delete specific hash/snapshot 530 + shutil.rmtree(model_path) 531 + print(f"Hash {commit_hash} deleted.") 532 + else: 533 + # Delete entire model directory (go up from snapshots or use base_cache_dir) 534 + if model_path.name.startswith("models--"): 535 + # model_path is base_cache_dir (corrupted model case) 536 + shutil.rmtree(model_path) 537 + else: 538 + # model_path is snapshot dir 539 + model_base_dir = model_path.parent.parent 540 + shutil.rmtree(model_base_dir) 541 + print(f"Model {model_name} deleted.") 542 + except PermissionError as e: 543 + print(f"[ERROR] Permission denied: Cannot delete {e.filename}") 544 + print(" Try running with appropriate permissions or manually delete the directory.") 545 + except OSError as e: 546 + if e.errno == errno.ENOTEMPTY: 547 + print(f"[ERROR] Directory not empty: {e.filename}") 548 + print(" Another process may be using this model.") 549 + elif e.errno == errno.EACCES: 550 + print(f"[ERROR] Access denied: {e.filename}") 551 + else: 552 + print(f"[ERROR] OS Error while deleting: {e}") 553 + except Exception as e: 554 + print(f"[ERROR] Unexpected error while deleting: {type(e).__name__}: {e}") 555 + 556 + return False 557 + 558 + def check_all_models_health(): 559 + models = [d for d in MODEL_CACHE.iterdir() if d.name.startswith("models--")] 560 + if not models: 561 + print("No models found in HuggingFace cache.") 562 + return 563 + print(f"Checking {len(models)} models for integrity...\n") 564 + healthy_models = [] 565 + problematic_models = [] 566 + for model_dir in sorted(models, key=lambda x: x.stat().st_mtime, reverse=True): 567 + hf_name = cache_dir_to_hf(model_dir.name) 568 + model_hash = get_model_hash(model_dir) 569 + print(f"{hf_name} ({model_hash})") 570 + if is_model_healthy(hf_name): 571 + healthy_models.append((hf_name, model_hash)) 572 + print(" [OK] Healthy\n") 573 + else: 574 + problematic_models.append((hf_name, model_hash)) 575 + print(" [ERROR] Problematic\n") 576 + print("=" * 50) 577 + print("Summary:") 578 + print(f"[OK] Healthy models: {len(healthy_models)}") 579 + print(f"[ERROR] Problematic models: {len(problematic_models)}") 580 + if problematic_models: 581 + print("\n[WARNING] Problematic models:") 582 + for name, hash_id in problematic_models: 583 + print(f" - {name} ({hash_id})") 584 + print("\nRepair tips:") 585 + print(" python mlx_knife.cli pull <model-name> # Re-download") 586 + print(" python mlx_knife.cli rm <model-name> # Delete") 587 + print(" python mlx_knife.cli health <model-name> # Show details") 588 + return len(problematic_models) == 0 589 + 590 + def list_models(show_all=False, framework_filter=None, show_health=False, single_model=None, verbose=False): 591 + if single_model: 592 + # Try exact match first 593 + expanded_model = expand_model_name(single_model) 594 + model_dir = MODEL_CACHE / hf_to_cache_dir(expanded_model) 595 + 596 + if model_dir.exists(): 597 + models = [model_dir] 598 + else: 599 + # If exact match fails, do partial name matching 600 + if not MODEL_CACHE.exists(): 601 + print(f"No models found matching '{single_model}' - cache directory doesn't exist yet.") 602 + print("Use 'mlxk pull <model-name>' to download models first.") 603 + return 604 + all_models = [d for d in MODEL_CACHE.iterdir() if d.name.startswith("models--")] 605 + matching_models = [] 606 + 607 + for model_dir in all_models: 608 + hf_name = cache_dir_to_hf(model_dir.name) 609 + # Check if the pattern appears in the model name (case insensitive) 610 + if single_model.lower() in hf_name.lower(): 611 + matching_models.append(model_dir) 612 + 613 + if not matching_models: 614 + print(f"No models found matching '{single_model}'!") 615 + return 616 + 617 + models = matching_models 618 + else: 619 + if not MODEL_CACHE.exists(): 620 + print("No models found - cache directory doesn't exist yet.") 621 + print("Use 'mlxk pull <model-name>' to download models first.") 622 + return 623 + models = [d for d in MODEL_CACHE.iterdir() if d.name.startswith("models--")] 624 + if not models: 625 + print("No models found in HuggingFace cache.") 626 + return 627 + if show_health: 628 + if show_all: 629 + print(f"{'NAME':<40} {'ID':<10} {'SIZE':<10} {'MODIFIED':<15} {'FRAMEWORK':<10} {'TYPE':<10} {'HEALTH':<8}") 630 + else: 631 + print(f"{'NAME':<40} {'ID':<10} {'SIZE':<10} {'MODIFIED':<15} {'HEALTH':<8}") 632 + else: 633 + if show_all: 634 + print(f"{'NAME':<40} {'ID':<10} {'SIZE':<10} {'MODIFIED':<15} {'FRAMEWORK':<10} {'TYPE':<10}") 635 + else: 636 + print(f"{'NAME':<40} {'ID':<10} {'SIZE':<10} {'MODIFIED':<15}") 637 + for m in sorted(models, key=lambda x: x.stat().st_mtime, reverse=True): 638 + hf_name = cache_dir_to_hf(m.name) 639 + size = get_model_size(m) 640 + modified = get_model_modified(m) 641 + model_hash = get_model_hash(m) 642 + framework = detect_framework(m, hf_name) 643 + model_type = detect_model_type(m, hf_name) 644 + if framework_filter and framework.lower() != framework_filter: 645 + continue 646 + # Default (strict) list: show only MLX chat models 647 + if not show_all and not framework_filter: 648 + if framework != "MLX": 649 + continue 650 + if model_type != "chat": 651 + continue 652 + # Handle display name based on verbose flag 653 + display_name = hf_name 654 + if hf_name.startswith("mlx-community/") and not verbose: 655 + # For MLX models, hide prefix unless verbose is set 656 + display_name = hf_name[len("mlx-community/"):] 657 + health_status = "" 658 + if show_health: 659 + health_status = "[OK]" if is_model_healthy(hf_name) else "[ERR]" 660 + if show_all: 661 + print(f"{display_name:<40} {model_hash:<10} {size:<10} {modified:<15} {framework:<10} {model_type:<10} {health_status:<8}") 662 + else: 663 + print(f"{display_name:<40} {model_hash:<10} {size:<10} {modified:<15} {health_status:<8}") 664 + else: 665 + if show_all: 666 + print(f"{display_name:<40} {model_hash:<10} {size:<10} {modified:<15} {framework:<10} {model_type:<10}") 667 + else: 668 + print(f"{display_name:<40} {model_hash:<10} {size:<10} {modified:<15}") 669 + 670 + def run_model(model_spec, prompt=None, interactive=False, temperature=0.7, 671 + max_tokens=500, top_p=0.9, repetition_penalty=1.1, stream=True, 672 + use_chat_template=True, hide_reasoning=False, verbose=False): 673 + """Run an MLX model with enhanced features. 674 + 675 + Args: 676 + model_spec: Model specification (name[@hash]) 677 + prompt: Input prompt (if None and not interactive, enters interactive mode) 678 + interactive: Force interactive mode 679 + temperature: Sampling temperature 680 + max_tokens: Maximum tokens to generate 681 + top_p: Top-p sampling parameter 682 + repetition_penalty: Penalty for repeated tokens 683 + stream: Whether to stream output 684 + """ 685 + model_path, model_name, commit_hash = resolve_single_model(model_spec) 686 + if not model_path: 687 + print(f"Use: mlxk pull {model_spec}") 688 + sys.exit(1) 689 + 690 + framework = detect_framework(model_path.parent.parent, model_name) 691 + if framework != "MLX": 692 + print(f"Model {model_name} is not MLX-compatible (Framework: {framework})!") 693 + print("Use MLX-Community models: https://huggingface.co/mlx-community") 694 + sys.exit(1) 695 + 696 + # Try to use the enhanced runner (import module to allow monkeypatching in tests) 697 + try: 698 + from . import mlx_runner as _mr 699 + 700 + _mr.run_model_enhanced( 701 + model_path=str(model_path), 702 + prompt=prompt, 703 + interactive=interactive, 704 + max_tokens=max_tokens, 705 + temperature=temperature, 706 + top_p=top_p, 707 + repetition_penalty=repetition_penalty, 708 + stream=stream, 709 + use_chat_template=use_chat_template, 710 + hide_reasoning=hide_reasoning, 711 + verbose=verbose, 712 + ) 713 + except ImportError: 714 + # Fallback to subprocess if mlx_runner is not available 715 + print("[WARNING] Enhanced runner not available, falling back to subprocess mode") 716 + print(f"Running model: {model_name}") 717 + if commit_hash: 718 + print(f"Hash: {commit_hash}") 719 + print(f"Cache path: {model_path}") 720 + 721 + if interactive or prompt is None: 722 + print("Interactive mode not supported in fallback mode") 723 + prompt = prompt or "Hello" 724 + 725 + print(f"Prompt: {prompt}\n") 726 + os.system(f'python -m mlx_lm generate --model "{model_path}" --prompt "{prompt}"') 727 + 728 + def show_model(model_spec, show_files=False, show_config=False): 729 + """Show detailed information about a specific model.""" 730 + model_path, model_name, commit_hash = resolve_single_model(model_spec) 731 + 732 + if not model_path: 733 + return False 734 + 735 + # Basic information 736 + print(f"Model: {model_name}") 737 + print(f"Path: {model_path}") 738 + 739 + if commit_hash: 740 + print(f"Snapshot: {commit_hash}") 741 + else: 742 + # Show current snapshot hash 743 + current_hash = model_path.name 744 + print(f"Snapshot: {current_hash}") 745 + 746 + # Size 747 + size = get_model_size(model_path) 748 + print(f"Size: {size}") 749 + 750 + # Modified time 751 + modified = get_model_modified(model_path) 752 + print(f"Modified: {modified}") 753 + 754 + # Framework / Type 755 + framework = detect_framework(model_path.parent.parent, model_name) 756 + model_type = detect_model_type(model_path.parent.parent, model_name) 757 + print(f"Framework: {framework}") 758 + print(f"Type: {model_type}") 759 + 760 + # Quantization info (if available) 761 + quant_info = get_quantization_info(model_path) 762 + if quant_info: 763 + if isinstance(quant_info, dict): 764 + # Show main quantization config (compact format) 765 + main_config = [] 766 + if "mode" in quant_info: 767 + main_config.append(f"mode: {quant_info['mode']}") 768 + if "bits" in quant_info: 769 + main_config.append(f"{quant_info['bits']}-bit") 770 + if "group_size" in quant_info: 771 + main_config.append(f"group_size: {quant_info['group_size']}") 772 + 773 + if main_config: 774 + print(f"Quantization: {', '.join(main_config)}") 775 + if "mode" in quant_info: 776 + print(f" Advanced mode '{quant_info['mode']}' (requires MLX ≥0.29.0, MLX-LM ≥0.27.0)") 777 + else: 778 + print(f"Quantization: {quant_info}") 779 + 780 + # Quantization and Precision info 781 + config_path = model_path / "config.json" 782 + quantization_info = None 783 + precision_info = None 784 + gguf_variants = [] 785 + 786 + if config_path.exists(): 787 + try: 788 + with open(config_path) as f: 789 + config_data = json.load(f) 790 + 791 + # 1. Check for explicit quantization field (MLX style) 792 + if "quantization" in config_data and isinstance(config_data["quantization"], dict): 793 + quant = config_data["quantization"] 794 + if "bits" in quant: 795 + quantization_info = f"{quant['bits']}-bit" 796 + precision_info = f"int{quant['bits']}" 797 + if "group_size" in quant: 798 + quantization_info += f" (group_size: {quant['group_size']})" 799 + 800 + # 2. Check torch_dtype (HuggingFace standard) 801 + elif "torch_dtype" in config_data: 802 + dtype = config_data["torch_dtype"] 803 + precision_info = dtype 804 + # Check if model name suggests quantization 805 + name_lower = model_name.lower() 806 + if "4bit" in name_lower or "-4b" in name_lower: 807 + quantization_info = "4-bit (inferred from name)" 808 + elif "8bit" in name_lower or "-8b" in name_lower: 809 + quantization_info = "8-bit (inferred from name)" 810 + else: 811 + quantization_info = "No quantization detected" 812 + 813 + # 3. Special handling for GGUF files 814 + gguf_files = sorted(list(model_path.glob("*.gguf"))) 815 + if gguf_files and not quantization_info: 816 + # Collect all GGUF variants 817 + gguf_variants = [] 818 + for f in gguf_files: 819 + name = f.name 820 + size_mb = f.stat().st_size / (1024 * 1024) 821 + 822 + # Parse quantization type from filename 823 + name_lower = name.lower() 824 + if "q2_k" in name_lower: 825 + variant_info = f"Q2_K (2-bit, {size_mb:.0f} MB)" 826 + elif "q3_k_s" in name_lower: 827 + variant_info = f"Q3_K_S (3-bit small, {size_mb:.0f} MB)" 828 + elif "q3_k_m" in name_lower: 829 + variant_info = f"Q3_K_M (3-bit medium, {size_mb:.0f} MB)" 830 + elif "q3_k_l" in name_lower: 831 + variant_info = f"Q3_K_L (3-bit large, {size_mb:.0f} MB)" 832 + elif "q3_k" in name_lower: 833 + variant_info = f"Q3_K (3-bit, {size_mb:.0f} MB)" 834 + elif "q4_0" in name_lower: 835 + variant_info = f"Q4_0 (4-bit, {size_mb:.0f} MB)" 836 + elif "q4_k_s" in name_lower: 837 + variant_info = f"Q4_K_S (4-bit small, {size_mb:.0f} MB)" 838 + elif "q4_k_m" in name_lower: 839 + variant_info = f"Q4_K_M (4-bit medium, {size_mb:.0f} MB)" 840 + elif "q4_k" in name_lower: 841 + variant_info = f"Q4_K (4-bit, {size_mb:.0f} MB)" 842 + elif "q5_0" in name_lower: 843 + variant_info = f"Q5_0 (5-bit, {size_mb:.0f} MB)" 844 + elif "q5_k_s" in name_lower: 845 + variant_info = f"Q5_K_S (5-bit small, {size_mb:.0f} MB)" 846 + elif "q5_k_m" in name_lower: 847 + variant_info = f"Q5_K_M (5-bit medium, {size_mb:.0f} MB)" 848 + elif "q5_k" in name_lower: 849 + variant_info = f"Q5_K (5-bit, {size_mb:.0f} MB)" 850 + elif "q6_k" in name_lower: 851 + variant_info = f"Q6_K (6-bit, {size_mb:.0f} MB)" 852 + elif "q8_0" in name_lower: 853 + variant_info = f"Q8_0 (8-bit, {size_mb:.0f} MB)" 854 + else: 855 + variant_info = f"{name} ({size_mb:.0f} MB)" 856 + 857 + gguf_variants.append(variant_info) 858 + 859 + if len(gguf_variants) > 1: 860 + quantization_info = "Multiple GGUF variants available" 861 + precision_info = "gguf (see variants below)" 862 + elif len(gguf_variants) == 1: 863 + quantization_info = gguf_variants[0].split(' (')[0] 864 + precision_info = "gguf" 865 + else: 866 + quantization_info = "GGUF format (quantization unknown)" 867 + precision_info = "gguf" 868 + 869 + except (OSError, json.JSONDecodeError, KeyError): 870 + pass 871 + 872 + # Display quantization and precision info 873 + if quantization_info: 874 + print(f"Quantization: {quantization_info}") 875 + else: 876 + print("Quantization: Unknown (no info in config)") 877 + 878 + if precision_info: 879 + print(f"Precision: {precision_info}") 880 + else: 881 + print("Precision: Unknown") 882 + 883 + # Display GGUF variants if available 884 + if gguf_variants and len(gguf_variants) > 1: 885 + print("\nAvailable GGUF variants:") 886 + for variant in gguf_variants: 887 + print(f" - {variant}") 888 + 889 + # Health status 890 + health_ok = is_model_healthy(model_name) 891 + if health_ok: 892 + print("Health: [OK]") 893 + else: 894 + print("Health: [ERROR] CORRUPTED") 895 + # Check specific issues 896 + issues = [] 897 + if not (model_path / "config.json").exists(): 898 + issues.append("config.json missing") 899 + 900 + weight_files = list(model_path.glob("*.safetensors")) + list(model_path.glob("*.bin")) + list(model_path.glob("*.gguf")) 901 + if not weight_files: 902 + weight_files = list(model_path.glob("**/*.safetensors")) + list(model_path.glob("**/*.bin")) + list(model_path.glob("**/*.gguf")) 903 + if not weight_files: 904 + index_file = model_path / "model.safetensors.index.json" 905 + if not index_file.exists(): 906 + issues.append("No model weights found") 907 + 908 + lfs_ok, lfs_msg = check_lfs_corruption(model_path) 909 + if not lfs_ok: 910 + issues.append(lfs_msg) 911 + 912 + if issues: 913 + print(" Issues:") 914 + for issue in issues: 915 + print(f" - {issue}") 916 + 917 + # Show files if requested 918 + if show_files: 919 + print("\nFiles:") 920 + files = [] 921 + for file in sorted(model_path.rglob("*")): 922 + if file.is_file(): 923 + relative_path = file.relative_to(model_path) 924 + file_size = file.stat().st_size 925 + if file_size >= 1_000_000_000: 926 + size_str = f"{file_size / 1_000_000_000:.2f} GB" 927 + elif file_size >= 1_000_000: 928 + size_str = f"{file_size / 1_000_000:.2f} MB" 929 + elif file_size >= 1_000: 930 + size_str = f"{file_size / 1_000:.2f} KB" 931 + else: 932 + size_str = f"{file_size} B" 933 + files.append((str(relative_path), size_str)) 934 + 935 + # Print files in a nice table format 936 + if files: 937 + max_name_len = max(len(f[0]) for f in files) 938 + for file_path, file_size in files: 939 + print(f" {file_path:<{max_name_len}} {file_size:>10}") 940 + else: 941 + print(" No files found") 942 + 943 + # Show config if requested 944 + if show_config: 945 + config_path = model_path / "config.json" 946 + if config_path.exists(): 947 + print("\nConfig:") 948 + try: 949 + with open(config_path) as f: 950 + config_data = json.load(f) 951 + print(json.dumps(config_data, indent=2)) 952 + except Exception as e: 953 + print(f" Error reading config: {e}") 954 + else: 955 + print("\nConfig: Not found") 956 + 957 + return True 958 + 959 + def rm_model(model_spec, force=False): 960 + original_spec = model_spec 961 + 962 + # First try to resolve using fuzzy matching 963 + resolved_path, resolved_name, resolved_hash = resolve_single_model(model_spec) 964 + 965 + if not resolved_path: 966 + # resolve_single_model already printed the error message for most cases 967 + # But ensure we always provide feedback to the user 968 + print(f"Model '{original_spec}' not found or corrupted.") 969 + return 970 + 971 + # Use the resolved model name for deletion 972 + model_name = resolved_name 973 + commit_hash = resolved_hash 974 + 975 + 976 + # Confirm on auto-expansion (if the resolved name is different from input) 977 + base_spec = original_spec.split("@")[0] if "@" in original_spec else original_spec 978 + if base_spec != model_name and "/" not in base_spec: 979 + confirm = input(f"Delete '{model_name}' (matched from '{base_spec}')? [Y/n] ") 980 + if confirm.lower() == "n": 981 + print("Delete aborted.") 982 + return 983 + 984 + base_cache_dir = MODEL_CACHE / hf_to_cache_dir(model_name) 985 + # This should exist since resolve_single_model succeeded, but double-check 986 + if not base_cache_dir.exists(): 987 + print(f"[ERROR] Model directory disappeared: {model_name}") 988 + return 989 + # Specific hash to delete? 990 + if commit_hash: 991 + hash_dir = base_cache_dir / "snapshots" / commit_hash 992 + if not hash_dir.exists(): 993 + print(f"Hash {commit_hash} for model {model_name} not found!") 994 + print("\nAvailable hashes:") 995 + snapshots_dir = base_cache_dir / "snapshots" 996 + if snapshots_dir.exists(): 997 + for snapshot in sorted(snapshots_dir.iterdir()): 998 + if snapshot.is_dir(): 999 + print(f" {snapshot.name[:8]}") 1000 + return 1001 + if force: 1002 + confirm_delete = True 1003 + else: 1004 + confirm = input(f"Delete hash {commit_hash} of model {model_name}? [y/N] ") 1005 + confirm_delete = confirm.lower() == "y" 1006 + 1007 + if confirm_delete: 1008 + # Issue #23 Fix: Delete entire model directory, not just the snapshot 1009 + # This prevents the double-execution problem where refs/ remain intact 1010 + shutil.rmtree(base_cache_dir) 1011 + print(f"{model_name}@{commit_hash} deleted") 1012 + 1013 + # Clean up associated lock files 1014 + try: 1015 + _cleanup_model_locks(model_name, force) 1016 + except Exception as e: 1017 + print(f"Warning: Could not clean up cache files: {e}") 1018 + else: 1019 + print("Aborted.") 1020 + else: 1021 + # Delete entire model 1022 + if force: 1023 + confirm_delete = True 1024 + else: 1025 + confirm = input(f"Delete entire model {model_name} ({base_cache_dir})? [y/N] ") 1026 + confirm_delete = confirm.lower() == "y" 1027 + 1028 + if confirm_delete: 1029 + shutil.rmtree(base_cache_dir) 1030 + print(f"Model {model_name} completely deleted.") 1031 + 1032 + # Clean up associated lock files 1033 + try: 1034 + _cleanup_model_locks(model_name, force) 1035 + except Exception as e: 1036 + print(f"Warning: Could not clean up cache files: {e}") 1037 + else: 1038 + print("Aborted.") 1039 + 1040 + 1041 + def _cleanup_model_locks(model_name, force=False): 1042 + """Clean up HuggingFace lock files for a deleted model. 1043 + 1044 + Args: 1045 + model_name: The model name (e.g. 'microsoft/DialoGPT-small') 1046 + force: If True, delete without asking. If False, prompt user. 1047 + """ 1048 + locks_dir = MODEL_CACHE / ".locks" / hf_to_cache_dir(model_name) 1049 + 1050 + if not locks_dir.exists(): 1051 + return # No locks to clean up 1052 + 1053 + # Count lock files 1054 + try: 1055 + lock_files = list(locks_dir.iterdir()) 1056 + if not lock_files: 1057 + return # Empty directory 1058 + 1059 + if force: 1060 + # Delete without asking 1061 + shutil.rmtree(locks_dir) 1062 + print(f"Cleaned up cache files ({len(lock_files)} files).") 1063 + else: 1064 + # Ask user 1065 + confirm = input("Clean up cache files? [Y/n] ") 1066 + if confirm.lower() != "n": 1067 + shutil.rmtree(locks_dir) 1068 + print(f"Cache files cleaned up ({len(lock_files)} files).") 1069 + else: 1070 + print("Cache files left intact.") 1071 + 1072 + except Exception as e: 1073 + print(f"Warning: Could not clean up cache files: {e}") 1074 +
+9
server/config.py
··· 1 + from pathlib import Path 2 + 3 + PORT = 6969 4 + MODEL_ID = "driaforall/mem-agent" 5 + 6 + prompt_path = Path(__file__).parent / "system_prompt.txt" 7 + 8 + with open(prompt_path, "r", encoding="utf-8") as f: 9 + SYSTEM_PROMPT = f.read().strip()
+20
server/main.py
··· 1 + # import os 2 + import uvicorn 3 + from .api import app 4 + from .config import PORT 5 + 6 + def run(): 7 + # Write PID file 8 + # PID_FILE.write_text(str(os.getpid())) 9 + 10 + # try: 11 + uvicorn.run(app, host="127.0.0.1", port=PORT) 12 + # finally: 13 + # if PID_FILE.exists(): 14 + # PID_FILE.unlink() 15 + # 16 + # print("hello from main!") 17 + 18 + if __name__ == "__main__": 19 + run() 20 +
+1050
server/mlx_runner.py
··· 1 + # MIT License 2 + 3 + # Copyright (c) 2025 The BROKE team 🦫 4 + 5 + # Permission is hereby granted, free of charge, to any person obtaining a copy 6 + # of this software and associated documentation files (the "Software"), to deal 7 + # in the Software without restriction, including without limitation the rights 8 + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + # copies of the Software, and to permit persons to whom the Software is 10 + # furnished to do so, subject to the following conditions: 11 + 12 + # The above copyright notice and this permission notice shall be included in all 13 + # copies or substantial portions of the Software. 14 + 15 + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + # SOFTWARE. 22 + 23 + """ 24 + Enhanced MLX model runner with direct API integration. 25 + Provides ollama-like run experience with streaming and interactive chat. 26 + """ 27 + 28 + import json 29 + import os 30 + import time 31 + from collections.abc import Iterator 32 + from pathlib import Path 33 + from typing import Dict, Optional 34 + 35 + import mlx.core as mx 36 + from mlx_lm import load 37 + from mlx_lm.generate import generate_step 38 + from mlx_lm.sample_utils import make_repetition_penalty, make_sampler 39 + 40 + from .reasoning_utils import ReasoningExtractor, StreamingReasoningParser 41 + 42 + 43 + def get_model_context_length(model_path: str) -> int: 44 + """Extract max_position_embeddings from model config. 45 + 46 + Args: 47 + model_path: Path to the MLX model directory 48 + 49 + Returns: 50 + Maximum context length for the model (defaults to 4096 if not found) 51 + """ 52 + config_path = os.path.join(model_path, "config.json") 53 + 54 + try: 55 + with open(config_path) as f: 56 + config = json.load(f) 57 + 58 + # Try various common config keys for context length 59 + context_keys = [ 60 + "max_position_embeddings", 61 + "n_positions", 62 + "context_length", 63 + "max_sequence_length", 64 + "seq_len" 65 + ] 66 + 67 + for key in context_keys: 68 + if key in config: 69 + return config[key] 70 + 71 + # If no context length found, return reasonable default 72 + return 4096 73 + 74 + except (FileNotFoundError, json.JSONDecodeError, KeyError): 75 + # Return default if config can't be read 76 + return 4096 77 + 78 + 79 + class MLXRunner: 80 + """Direct MLX model runner with streaming and interactive capabilities.""" 81 + 82 + def __init__(self, model_path: str, adapter_path: Optional[str] = None, verbose: bool = False): 83 + """Initialize the runner with a model. 84 + 85 + Args: 86 + model_path: Path to the MLX model directory 87 + adapter_path: Optional path to LoRA adapter 88 + verbose: Show detailed output 89 + """ 90 + self.model_path = Path(model_path) 91 + self.adapter_path = adapter_path 92 + self.model = None 93 + self.tokenizer = None 94 + self._memory_baseline = None 95 + self._stop_tokens = None # Will be populated from tokenizer 96 + self._message_end_tokens = None # Message-end tokens (e.g., <|end|> for MXFP4) 97 + self._chat_stop_tokens = None # Chat-specific stop tokens 98 + self._context_length = None # Will be populated from model config 99 + self._is_reasoning_model = False # Whether model uses reasoning (MXFP4) 100 + self._reasoning_start = None # Reasoning start marker 101 + self._reasoning_end = None # Reasoning end marker 102 + self._final_start = None # Final answer start marker 103 + self.verbose = verbose 104 + self._model_loaded = False 105 + self._context_entered = False # Prevent nested context usage 106 + 107 + def __enter__(self): 108 + """Context manager entry - loads the model.""" 109 + if self._context_entered: 110 + raise RuntimeError("MLXRunner context manager cannot be entered multiple times") 111 + 112 + self._context_entered = True 113 + try: 114 + self.load_model() 115 + return self 116 + except Exception: 117 + # If load_model fails, ensure cleanup happens 118 + self._context_entered = False 119 + self.cleanup() 120 + raise 121 + 122 + def __exit__(self, exc_type, exc_val, exc_tb): 123 + """Context manager exit - cleans up the model.""" 124 + self._context_entered = False 125 + self.cleanup() 126 + return False # Don't suppress exceptions 127 + 128 + def load_model(self): 129 + """Load the MLX model and tokenizer.""" 130 + if self._model_loaded: 131 + if self.verbose: 132 + print("Model already loaded, skipping...") 133 + return 134 + 135 + if self.verbose: 136 + print(f"Loading model from {self.model_path}...") 137 + start_time = time.time() 138 + 139 + # Capture baseline memory before loading 140 + try: 141 + mx.clear_cache() 142 + except Exception: 143 + pass # Continue even if cache clear fails 144 + self._memory_baseline = mx.get_active_memory() / 1024**3 145 + 146 + try: 147 + # Load model and tokenizer 148 + self.model, self.tokenizer = load( 149 + str(self.model_path), 150 + adapter_path=self.adapter_path 151 + ) 152 + 153 + load_time = time.time() - start_time 154 + current_memory = mx.get_active_memory() / 1024**3 155 + model_memory = current_memory - self._memory_baseline 156 + 157 + if self.verbose: 158 + print(f"Model loaded in {load_time:.1f}s") 159 + print(f"Memory: {model_memory:.1f}GB model, {current_memory:.1f}GB total") 160 + 161 + # Extract stop tokens from tokenizer 162 + self._extract_stop_tokens() 163 + 164 + # Extract context length from model config 165 + self._context_length = get_model_context_length(str(self.model_path)) 166 + 167 + if self.verbose: 168 + print(f"Model context length: {self._context_length} tokens") 169 + 170 + self._model_loaded = True 171 + 172 + except Exception as e: 173 + # Ensure partial state is cleaned up on failure 174 + self.model = None 175 + self.tokenizer = None 176 + self._stop_tokens = None 177 + self._model_loaded = False 178 + # Clear any memory that might have been allocated 179 + mx.clear_cache() 180 + raise RuntimeError(f"Failed to load model from {self.model_path}: {e}") from e 181 + 182 + def _extract_stop_tokens(self): 183 + """Extract stop tokens from the tokenizer dynamically. 184 + 185 + This method identifies ALL tokens that should stop generation: 186 + 1. Official EOS token from tokenizer config 187 + 2. Message-end tokens from training (e.g., <|end|> for MXFP4) 188 + 3. Common stop tokens across models 189 + """ 190 + self._stop_tokens = set() 191 + self._message_end_tokens = set() # Tokens that end messages but not conversations 192 + 193 + # Primary source: eos_token 194 + eos_token = getattr(self.tokenizer, 'eos_token', None) 195 + if eos_token: 196 + self._stop_tokens.add(eos_token) 197 + 198 + # Also check pad_token if it's different from eos_token 199 + pad_token = getattr(self.tokenizer, 'pad_token', None) 200 + if pad_token and pad_token != eos_token: 201 + self._stop_tokens.add(pad_token) 202 + 203 + # Check additional_special_tokens 204 + if hasattr(self.tokenizer, 'additional_special_tokens'): 205 + for token in self.tokenizer.additional_special_tokens: 206 + if token and isinstance(token, str): 207 + # Only add tokens that look like stop/end tokens 208 + if any(keyword in token.lower() for keyword in ['end', 'stop', 'eot']): 209 + self._stop_tokens.add(token) 210 + 211 + # MLX-LM 0.27.0+: Extract tokens from added_tokens_decoder (comprehensive source) 212 + if hasattr(self.tokenizer, 'added_tokens_decoder'): 213 + for _token_id, token_info in self.tokenizer.added_tokens_decoder.items(): 214 + if isinstance(token_info, dict) and 'content' in token_info: 215 + token_content = token_info['content'] 216 + if token_content and isinstance(token_content, str): 217 + token_lower = token_content.lower() 218 + 219 + # NOTE: <|end|> is NOT a stop token for MXFP4 models! 220 + # It's a separator between reasoning and final answer 221 + if token_content == '<|end|>': 222 + self._message_end_tokens.add(token_content) 223 + # Do NOT add as stop token - let model continue to final answer 224 + 225 + # Look for tokens that could be end/stop tokens 226 + # Expanded patterns for MLX-LM 0.27.0 token varieties 227 + # EXCLUDE <|end|> for MXFP4 models as it's a reasoning separator 228 + end_patterns = ['stop', 'eot', 'return', 'finish', 'done', 'im_end'] 229 + if any(pattern in token_lower for pattern in end_patterns): 230 + # Decide if it's a message-end or conversation-end token 231 + if 'im_end' in token_lower: 232 + self._message_end_tokens.add(token_content) 233 + self._stop_tokens.add(token_content) 234 + # Special handling for 'end' pattern - more selective 235 + elif 'end' in token_lower and token_content != '<|end|>': 236 + # Only add non-<|end|> tokens with 'end' in them 237 + self._stop_tokens.add(token_content) 238 + 239 + # Special case: control tokens in |..| format 240 + elif token_content.startswith('<|') and token_content.endswith('|>'): 241 + # Be inclusive with control tokens that might stop generation 242 + if any(pattern in token_lower for pattern in ['end', 'return', 'stop', 'finish']): 243 + self._stop_tokens.add(token_content) 244 + 245 + # Model-specific handling based on known patterns 246 + # Use reasoning_utils for reasoning model detection and patterns 247 + from .reasoning_utils import ReasoningExtractor 248 + 249 + if hasattr(self.tokenizer, 'name_or_path'): 250 + name_or_path = str(getattr(self.tokenizer, 'name_or_path', '')).lower() 251 + model_type = ReasoningExtractor.detect_model_type(name_or_path) 252 + 253 + if model_type: 254 + # This is a reasoning model 255 + self._is_reasoning_model = True 256 + 257 + # Get patterns from reasoning_utils 258 + if model_type in ReasoningExtractor.PATTERNS: 259 + markers = ReasoningExtractor.PATTERNS[model_type]['markers'] 260 + self._reasoning_start = markers.get('reasoning_start') 261 + self._reasoning_end = markers.get('reasoning_end') 262 + self._final_start = markers.get('final_marker') 263 + 264 + # For reasoning models, remove reasoning_end from stop tokens 265 + if self._reasoning_end: 266 + self._stop_tokens.discard(self._reasoning_end) 267 + 268 + # Add proper stop token for this model type 269 + if model_type == 'gpt-oss': 270 + if '<|return|>' not in self._stop_tokens: 271 + self._stop_tokens.add('<|return|>') 272 + else: 273 + self._is_reasoning_model = False 274 + else: 275 + self._is_reasoning_model = False 276 + 277 + # Add common stop tokens that might not be in special tokens 278 + common_stop_tokens = {'</s>', '<|endoftext|>', '<|im_end|>', '<|eot_id|>'} 279 + 280 + # Add chat-specific stop tokens to prevent model self-conversations 281 + # Based on our _format_conversation() format: "Human:" and "Assistant:" 282 + # Also include "You:" as models might use UI-visible format 283 + # Include single-letter variations (H:, A:, Y:) that some models use 284 + chat_stop_tokens = { 285 + '\nHuman:', '\nAssistant:', '\nYou:', 286 + '\n\nHuman:', '\n\nAssistant:', '\n\nYou:', 287 + '\nH:', '\nA:', '\nY:', # Single-letter variations 288 + '\n\nH:', '\n\nA:', '\n\nY:' 289 + } 290 + 291 + # Add common stop tokens only if they decode to themselves (i.e., they're single tokens) 292 + for token in common_stop_tokens: 293 + try: 294 + # Try to encode and decode to verify it's a real single token 295 + ids = self.tokenizer.encode(token, add_special_tokens=False) 296 + if ids and len(ids) == 1: # Single token ID means it's a special token 297 + decoded = self.tokenizer.decode(ids) 298 + if decoded == token: 299 + self._stop_tokens.add(token) 300 + except: 301 + pass 302 + 303 + # Store chat stop tokens separately - only used in interactive chat mode 304 + # This prevents stopping mid-story when user asks for dialogues 305 + self._chat_stop_tokens = list(chat_stop_tokens) 306 + 307 + # Remove any None values 308 + self._stop_tokens.discard(None) 309 + self._message_end_tokens.discard(None) 310 + 311 + # Convert to list for easier use 312 + self._stop_tokens = list(self._stop_tokens) 313 + self._message_end_tokens = list(self._message_end_tokens) 314 + 315 + if self.verbose: 316 + if self._stop_tokens: 317 + print(f"Stop tokens: {self._stop_tokens}") 318 + if self._message_end_tokens: 319 + print(f"Message end tokens: {self._message_end_tokens}") 320 + 321 + def cleanup(self): 322 + """Clean up model resources and clear GPU memory. 323 + 324 + This method is safe to call multiple times and handles partial state cleanup. 325 + """ 326 + if self.verbose and self._model_loaded: 327 + memory_before = mx.get_active_memory() / 1024**3 328 + print(f"Cleaning up model (memory before: {memory_before:.1f}GB)...") 329 + 330 + # Always clean up, even if model wasn't fully loaded 331 + self.model = None 332 + self.tokenizer = None 333 + self._stop_tokens = None 334 + self._message_end_tokens = None 335 + self._chat_stop_tokens = None 336 + self._context_length = None 337 + self._is_reasoning_model = False 338 + self._reasoning_start = None 339 + self._reasoning_end = None 340 + self._final_start = None 341 + self._model_loaded = False 342 + 343 + # Force garbage collection and clear MLX cache 344 + import gc 345 + gc.collect() 346 + try: 347 + mx.clear_cache() 348 + except Exception: 349 + pass # Continue cleanup even if cache clear fails 350 + 351 + if self.verbose: 352 + memory_after = mx.get_active_memory() / 1024**3 353 + if 'memory_before' in locals(): 354 + memory_freed = memory_before - memory_after 355 + print(f"Cleanup complete (memory after: {memory_after:.1f}GB, freed: {memory_freed:.1f}GB)") 356 + else: 357 + print(f"Cleanup complete (memory after: {memory_after:.1f}GB)") 358 + 359 + def get_effective_max_tokens(self, requested_tokens: Optional[int], interactive: bool = False) -> int: 360 + """Get effective max tokens based on model context and usage mode. 361 + 362 + Args: 363 + requested_tokens: The requested max tokens (None if user didn't specify --max-tokens) 364 + interactive: True if this is interactive mode (gets full context length) 365 + 366 + Returns: 367 + Effective max tokens to use 368 + """ 369 + if not self._context_length: 370 + # Fallback when context length is unknown 371 + fallback = 4096 if interactive else 2048 372 + if self.verbose: 373 + if requested_tokens is None: 374 + print(f"[WARNING] Model context length unknown, using fallback: {fallback} tokens") 375 + else: 376 + print(f"[WARNING] Model context length unknown, using user specified: {requested_tokens} tokens") 377 + return requested_tokens if requested_tokens is not None else fallback 378 + 379 + if interactive: 380 + if requested_tokens is None: 381 + # User didn't specify --max-tokens: use full model context 382 + return self._context_length 383 + else: 384 + # User specified --max-tokens explicitly: respect their choice but cap at context 385 + return min(requested_tokens, self._context_length) 386 + else: 387 + # Server/batch mode uses half context length for DoS protection 388 + server_limit = self._context_length // 2 389 + return min(requested_tokens or server_limit, server_limit) 390 + 391 + def generate_streaming( 392 + self, 393 + prompt: str, 394 + max_tokens: int = 500, 395 + temperature: float = 0.7, 396 + top_p: float = 0.9, 397 + repetition_penalty: float = 1.1, 398 + repetition_context_size: int = 20, 399 + use_chat_template: bool = True, 400 + use_chat_stop_tokens: bool = False, 401 + interactive: bool = False, 402 + hide_reasoning: bool = False, 403 + ) -> Iterator[str]: 404 + """Generate text with streaming output. 405 + 406 + Args: 407 + prompt: Input prompt 408 + max_tokens: Maximum tokens to generate 409 + temperature: Sampling temperature 410 + top_p: Top-p sampling parameter 411 + repetition_penalty: Penalty for repeated tokens 412 + repetition_context_size: Context size for repetition penalty 413 + use_chat_template: Apply tokenizer's chat template if available 414 + use_chat_stop_tokens: Include chat turn markers as stop tokens (for interactive mode) 415 + interactive: True if this is interactive mode (affects token limits) 416 + 417 + Yields: 418 + Generated tokens as they are produced 419 + """ 420 + if not self.model or not self.tokenizer: 421 + raise RuntimeError("Model not loaded. Call load_model() first.") 422 + 423 + # Initialize reasoning parser if this is a reasoning model 424 + reasoning_parser = None 425 + if self._is_reasoning_model: 426 + model_type = ReasoningExtractor.detect_model_type( 427 + getattr(self.tokenizer, 'name_or_path', '') or '' 428 + ) 429 + reasoning_parser = StreamingReasoningParser(model_type, hide_reasoning=hide_reasoning) 430 + 431 + # Apply context-aware token limits 432 + effective_max_tokens = self.get_effective_max_tokens(max_tokens, interactive) 433 + 434 + # Apply chat template if available and requested 435 + if use_chat_template and hasattr(self.tokenizer, 'chat_template') and self.tokenizer.chat_template: 436 + messages = [{"role": "user", "content": prompt}] 437 + formatted_prompt = self.tokenizer.apply_chat_template( 438 + messages, 439 + tokenize=False, 440 + add_generation_prompt=True 441 + ) 442 + else: 443 + formatted_prompt = prompt 444 + 445 + # Tokenize the prompt 446 + prompt_tokens = self.tokenizer.encode(formatted_prompt) 447 + prompt_array = mx.array(prompt_tokens) 448 + 449 + # Track generation metrics 450 + start_time = time.time() 451 + tokens_generated = 0 452 + 453 + # Create sampler with our parameters 454 + sampler = make_sampler(temp=temperature, top_p=top_p) 455 + 456 + # Create repetition penalty processor if needed 457 + logits_processors = [] 458 + if repetition_penalty > 1.0: 459 + logits_processors.append( 460 + make_repetition_penalty(repetition_penalty, repetition_context_size) 461 + ) 462 + 463 + # Generate tokens one by one for streaming 464 + generator = generate_step( 465 + prompt=prompt_array, 466 + model=self.model, 467 + max_tokens=effective_max_tokens, 468 + sampler=sampler, 469 + logits_processors=logits_processors if logits_processors else None, 470 + ) 471 + 472 + # Collect tokens and yield text 473 + generated_tokens = [] 474 + previous_decoded = "" 475 + accumulated_response = "" # Track full response for stop token detection 476 + 477 + # Keep a sliding window of recent tokens for context 478 + context_window = 10 # Decode last N tokens for proper spacing 479 + 480 + for token, _ in generator: 481 + # Token might be an array or an int 482 + token_id = token.item() if hasattr(token, 'item') else token 483 + generated_tokens.append(token_id) 484 + 485 + # Use a sliding window approach for efficiency 486 + start_idx = max(0, len(generated_tokens) - context_window) 487 + window_tokens = generated_tokens[start_idx:] 488 + 489 + # Decode the window 490 + window_text = self.tokenizer.decode(window_tokens) 491 + 492 + # Figure out what's new 493 + if start_idx == 0: 494 + # We're still within the context window 495 + if window_text.startswith(previous_decoded): 496 + new_text = window_text[len(previous_decoded):] 497 + else: 498 + new_text = self.tokenizer.decode([token_id]) 499 + previous_decoded = window_text 500 + else: 501 + # We're beyond the context window, just decode the last token with context 502 + # This is approximate but should preserve spaces 503 + new_text = self.tokenizer.decode(window_tokens) 504 + if len(window_tokens) > 1: 505 + prefix = self.tokenizer.decode(window_tokens[:-1]) 506 + if new_text.startswith(prefix): 507 + new_text = new_text[len(prefix):] 508 + else: 509 + new_text = self.tokenizer.decode([token_id]) 510 + 511 + if new_text: 512 + # Update accumulated response for stop token checking 513 + accumulated_response += new_text 514 + 515 + # Filter out stop tokens with priority: native first, then chat fallback 516 + # Check native stop tokens FIRST in accumulated response (highest priority) 517 + native_stop_tokens = self._stop_tokens if self._stop_tokens else [] 518 + for stop_token in native_stop_tokens: 519 + if stop_token in accumulated_response: 520 + # Find the stop token position and yield everything before it 521 + stop_pos = accumulated_response.find(stop_token) 522 + # Calculate what text came before the stop token 523 + text_before_stop = accumulated_response[:stop_pos] 524 + # Calculate how much of that is new (not previously yielded) 525 + previously_yielded_length = len(accumulated_response) - len(new_text) 526 + if len(text_before_stop) > previously_yielded_length: 527 + # Yield only the new part before stop token 528 + new_part_before_stop = text_before_stop[previously_yielded_length:] 529 + if new_part_before_stop: 530 + if reasoning_parser: 531 + # Process through reasoning parser for formatting 532 + for formatted_token in reasoning_parser.process_token(new_part_before_stop): 533 + yield formatted_token 534 + else: 535 + yield new_part_before_stop 536 + return # Stop generation without yielding stop token 537 + 538 + # Only check chat stop tokens if no native stop token found (fallback) 539 + if use_chat_stop_tokens and self._chat_stop_tokens: 540 + for stop_token in self._chat_stop_tokens: 541 + if stop_token in accumulated_response: 542 + # Find the stop token position and yield everything before it 543 + stop_pos = accumulated_response.find(stop_token) 544 + # Calculate what text came before the stop token 545 + text_before_stop = accumulated_response[:stop_pos] 546 + # Calculate how much of that is new (not previously yielded) 547 + previously_yielded_length = len(accumulated_response) - len(new_text) 548 + if len(text_before_stop) > previously_yielded_length: 549 + # Yield only the new part before stop token 550 + new_part_before_stop = text_before_stop[previously_yielded_length:] 551 + if new_part_before_stop: 552 + if reasoning_parser: 553 + # Process through reasoning parser for formatting 554 + for formatted_token in reasoning_parser.process_token(new_part_before_stop): 555 + yield formatted_token 556 + else: 557 + yield new_part_before_stop 558 + return # Stop generation without yielding stop token 559 + 560 + # No stop token found, process the new text 561 + if reasoning_parser: 562 + # Process through reasoning parser for formatting 563 + for formatted_token in reasoning_parser.process_token(new_text): 564 + yield formatted_token 565 + else: 566 + # Normal streaming for non-reasoning models 567 + yield new_text 568 + tokens_generated += 1 569 + 570 + # Check for EOS token - don't yield it 571 + if token_id == self.tokenizer.eos_token_id: 572 + break 573 + 574 + # Finalize reasoning parser if used 575 + if reasoning_parser: 576 + yield from reasoning_parser.finalize() 577 + 578 + # Print generation statistics if verbose 579 + if self.verbose: 580 + generation_time = time.time() - start_time 581 + tokens_per_second = tokens_generated / generation_time if generation_time > 0 else 0 582 + print(f"\n\nGenerated {tokens_generated} tokens in {generation_time:.1f}s ({tokens_per_second:.1f} tokens/s)") 583 + 584 + def generate_batch( 585 + self, 586 + prompt: str, 587 + max_tokens: int = 500, 588 + temperature: float = 0.7, 589 + top_p: float = 0.9, 590 + repetition_penalty: float = 1.1, 591 + repetition_context_size: int = 20, 592 + use_chat_template: bool = True, 593 + interactive: bool = False, 594 + ) -> str: 595 + """Generate text in batch mode (non-streaming). 596 + 597 + Args: 598 + prompt: Input prompt 599 + max_tokens: Maximum tokens to generate 600 + temperature: Sampling temperature 601 + top_p: Top-p sampling parameter 602 + repetition_penalty: Penalty for repeated tokens 603 + repetition_context_size: Context size for repetition penalty 604 + use_chat_template: Apply tokenizer's chat template if available 605 + interactive: True if this is interactive mode (affects token limits) 606 + 607 + Returns: 608 + Generated text 609 + """ 610 + if not self.model or not self.tokenizer: 611 + raise RuntimeError("Model not loaded. Call load_model() first.") 612 + 613 + # Apply context-aware token limits 614 + effective_max_tokens = self.get_effective_max_tokens(max_tokens, interactive) 615 + 616 + # Apply chat template if available and requested 617 + if use_chat_template and hasattr(self.tokenizer, 'chat_template') and self.tokenizer.chat_template: 618 + messages = [{"role": "user", "content": prompt}] 619 + formatted_prompt = self.tokenizer.apply_chat_template( 620 + messages, 621 + tokenize=False, 622 + add_generation_prompt=True 623 + ) 624 + else: 625 + formatted_prompt = prompt 626 + 627 + start_time = time.time() 628 + 629 + # Tokenize the prompt 630 + prompt_tokens = self.tokenizer.encode(formatted_prompt) 631 + prompt_array = mx.array(prompt_tokens) 632 + 633 + # Create sampler with our parameters 634 + sampler = make_sampler(temp=temperature, top_p=top_p) 635 + 636 + # Create repetition penalty processor if needed 637 + logits_processors = [] 638 + if repetition_penalty > 1.0: 639 + logits_processors.append( 640 + make_repetition_penalty(repetition_penalty, repetition_context_size) 641 + ) 642 + 643 + # Generate all tokens at once 644 + generated_tokens = [] 645 + all_tokens = list(prompt_tokens) # Keep prompt for proper decoding 646 + 647 + generator = generate_step( 648 + prompt=prompt_array, 649 + model=self.model, 650 + max_tokens=effective_max_tokens, 651 + sampler=sampler, 652 + logits_processors=logits_processors if logits_processors else None, 653 + ) 654 + 655 + for token, _ in generator: 656 + # Token might be an array or an int 657 + token_id = token.item() if hasattr(token, 'item') else token 658 + generated_tokens.append(token_id) 659 + all_tokens.append(token_id) 660 + 661 + # Check for EOS token - don't yield it 662 + if token_id == self.tokenizer.eos_token_id: 663 + break 664 + 665 + # Decode all tokens together for proper spacing 666 + full_response = self.tokenizer.decode(all_tokens) 667 + 668 + # Remove the prompt part 669 + if full_response.startswith(formatted_prompt): 670 + response = full_response[len(formatted_prompt):] 671 + else: 672 + # Fallback: just decode generated tokens 673 + response = self.tokenizer.decode(generated_tokens) 674 + 675 + # Apply end-token filtering (same logic as streaming mode for Issue #20) 676 + response = self._filter_end_tokens_from_response(response, use_chat_stop_tokens=False) 677 + 678 + # Format reasoning models output 679 + response = self._format_reasoning_response(response) 680 + 681 + generation_time = time.time() - start_time 682 + 683 + # Count tokens for statistics 684 + if self.verbose: 685 + tokens_generated = len(generated_tokens) 686 + tokens_per_second = tokens_generated / generation_time if generation_time > 0 else 0 687 + print(f"\nGenerated {tokens_generated} tokens in {generation_time:.1f}s ({tokens_per_second:.1f} tokens/s)") 688 + 689 + return response 690 + 691 + def interactive_chat( 692 + self, 693 + system_prompt: Optional[str] = None, 694 + max_tokens: int = 500, 695 + temperature: float = 0.7, 696 + top_p: float = 0.9, 697 + repetition_penalty: float = 1.1, 698 + use_chat_template: bool = True, 699 + ): 700 + """Run an interactive chat session. 701 + 702 + Args: 703 + system_prompt: Optional system prompt to prepend 704 + max_tokens: Maximum tokens per response 705 + temperature: Sampling temperature 706 + top_p: Top-p sampling parameter 707 + repetition_penalty: Penalty for repeated tokens 708 + use_chat_template: Use tokenizer's chat template if available 709 + """ 710 + print("Starting interactive chat. Type 'exit' or 'quit' to end.\n") 711 + 712 + conversation_history = [] 713 + if system_prompt: 714 + conversation_history.append({"role": "system", "content": system_prompt}) 715 + 716 + while True: 717 + try: 718 + # Get user input 719 + user_input = input("You: ").strip() 720 + 721 + if user_input.lower() in ['exit', 'quit', 'q']: 722 + print("\nGoodbye!") 723 + break 724 + 725 + if not user_input: 726 + continue 727 + 728 + # Add user message to history 729 + conversation_history.append({"role": "user", "content": user_input}) 730 + 731 + # Format conversation for the model using chat template if available 732 + prompt = self._format_conversation(conversation_history, use_chat_template=use_chat_template) 733 + 734 + # Generate response with streaming 735 + print("\nAssistant: ", end="", flush=True) 736 + 737 + response_tokens = [] 738 + for token in self.generate_streaming( 739 + prompt=prompt, 740 + max_tokens=max_tokens, 741 + temperature=temperature, 742 + top_p=top_p, 743 + repetition_penalty=repetition_penalty, 744 + use_chat_template=False, # Already applied in _format_conversation 745 + use_chat_stop_tokens=True, # Enable chat stop tokens in interactive mode 746 + interactive=True, # Enable full context length for interactive mode 747 + ): 748 + # Stream all tokens directly (already formatted by generate_streaming) 749 + print(token, end="", flush=True) 750 + response_tokens.append(token) 751 + 752 + # Add assistant response to history 753 + assistant_response = "".join(response_tokens).strip() 754 + conversation_history.append({"role": "assistant", "content": assistant_response}) 755 + 756 + print() # New line after response 757 + 758 + except KeyboardInterrupt: 759 + print("\n\nChat interrupted. Goodbye!") 760 + break 761 + except Exception as e: 762 + print(f"\n[ERROR] {e}") 763 + continue 764 + 765 + def _format_conversation(self, messages: list, use_chat_template: bool = True) -> str: 766 + """Format conversation history into a prompt. 767 + 768 + Uses the tokenizer's chat template if available, otherwise falls back 769 + to the legacy Human:/Assistant: format for compatibility. 770 + 771 + Args: 772 + messages: List of message dictionaries with 'role' and 'content' 773 + use_chat_template: Whether to use chat template if available 774 + 775 + Returns: 776 + Formatted conversation string 777 + """ 778 + # Try to use native chat template if available 779 + if use_chat_template and hasattr(self.tokenizer, 'chat_template') and self.tokenizer.chat_template: 780 + try: 781 + # Apply the tokenizer's chat template 782 + formatted_prompt = self.tokenizer.apply_chat_template( 783 + messages, 784 + tokenize=False, 785 + add_generation_prompt=True 786 + ) 787 + return formatted_prompt 788 + except Exception as e: 789 + # If chat template fails, fall back to legacy format 790 + if self.verbose: 791 + print(f"[WARNING] Chat template failed, using legacy format: {e}") 792 + 793 + # Legacy format fallback for compatibility 794 + return self._legacy_format_conversation(messages) 795 + 796 + def _legacy_format_conversation(self, messages: list) -> str: 797 + """Legacy conversation formatting for backward compatibility. 798 + 799 + This format was used in earlier versions and remains as a fallback 800 + for models without chat templates. 801 + """ 802 + formatted = [] 803 + 804 + for message in messages: 805 + role = message["role"] 806 + content = message["content"] 807 + 808 + if role == "system": 809 + formatted.append(f"System: {content}") 810 + elif role == "user": 811 + formatted.append(f"Human: {content}") 812 + elif role == "assistant": 813 + formatted.append(f"Assistant: {content}") 814 + 815 + # Add prompt for next assistant response 816 + formatted.append("Assistant:") 817 + 818 + return "\n\n".join(formatted) 819 + 820 + def get_memory_usage(self) -> Dict[str, float]: 821 + """Get current memory usage statistics. 822 + 823 + Returns: 824 + Dictionary with memory statistics in GB 825 + """ 826 + try: 827 + current_memory = mx.get_active_memory() / 1024**3 828 + peak_memory = mx.get_peak_memory() / 1024**3 829 + except Exception: 830 + # Return zeros if memory stats unavailable 831 + current_memory = 0.0 832 + peak_memory = 0.0 833 + 834 + return { 835 + "current_gb": current_memory, 836 + "peak_gb": peak_memory, 837 + "model_gb": current_memory - self._memory_baseline if self._memory_baseline else 0, 838 + } 839 + 840 + def _format_reasoning_response(self, response: str) -> str: 841 + """Format response from reasoning models for better readability. 842 + 843 + For MXFP4 models that generate reasoning followed by final answer, 844 + format it nicely for display. 845 + """ 846 + if not self._is_reasoning_model: 847 + return response 848 + 849 + # Check if response contains reasoning markers 850 + if self._reasoning_start in response and self._final_start in response: 851 + # Extract reasoning and final parts 852 + try: 853 + # Split on the reasoning start 854 + before_reasoning, after_start = response.split(self._reasoning_start, 1) 855 + 856 + # Find the reasoning content (until <|end|>) 857 + if self._reasoning_end in after_start: 858 + reasoning_content, after_reasoning = after_start.split(self._reasoning_end, 1) 859 + 860 + # Find the final answer 861 + if self._final_start in after_reasoning: 862 + # Extract everything after final marker 863 + final_parts = after_reasoning.split(self._final_start, 1) 864 + if len(final_parts) > 1: 865 + # Remove the <|channel|>final<|message|> marker 866 + final_answer = final_parts[1].replace('<|channel|>final<|message|>', '', 1) 867 + 868 + # Format with clear markers for parsing but minimal visual impact 869 + formatted = [] 870 + formatted.append("\n**[Reasoning]**\n") 871 + formatted.append(reasoning_content.strip()) 872 + formatted.append("\n\n---\n\n**[Answer]**\n") 873 + formatted.append(final_answer.strip()) 874 + 875 + return '\n'.join(formatted) 876 + except Exception: 877 + # If parsing fails, return original 878 + pass 879 + 880 + # Fallback: just clean up the control tokens 881 + cleaned = response 882 + for marker in ['<|channel|>analysis<|message|>', '<|end|>', '<|start|>assistant', 883 + '<|channel|>final<|message|>', '<|return|>']: 884 + cleaned = cleaned.replace(marker, '') 885 + 886 + return cleaned.strip() 887 + 888 + def _filter_end_tokens_from_response(self, response: str, use_chat_stop_tokens: bool = False) -> str: 889 + """Filter end tokens from a complete response (batch mode). 890 + 891 + This method applies the same filtering logic as the streaming mode 892 + to ensure consistent behavior between streaming and non-streaming. 893 + 894 + Args: 895 + response: The complete generated response 896 + use_chat_stop_tokens: Whether to apply chat stop tokens 897 + 898 + Returns: 899 + Response with end tokens filtered out 900 + """ 901 + # Apply native stop token filtering FIRST (highest priority) 902 + native_stop_tokens = self._stop_tokens if self._stop_tokens else [] 903 + for stop_token in native_stop_tokens: 904 + if stop_token in response: 905 + # Find the stop token position and return everything before it 906 + stop_pos = response.find(stop_token) 907 + filtered_response = response[:stop_pos].rstrip() 908 + if self.verbose: 909 + print(f"[DEBUG] Filtered stop token '{stop_token}' at position {stop_pos}") 910 + return filtered_response 911 + 912 + # Only check chat stop tokens if no native stop token found (fallback) 913 + if use_chat_stop_tokens and self._chat_stop_tokens: 914 + for stop_token in self._chat_stop_tokens: 915 + if stop_token in response: 916 + # Find the stop token position and return everything before it 917 + stop_pos = response.find(stop_token) 918 + return response[:stop_pos] 919 + 920 + # No stop tokens found, return original response 921 + return response 922 + 923 + 924 + def get_gpu_status() -> Dict[str, float]: 925 + """Independent GPU status check - usable from anywhere. 926 + 927 + Returns: 928 + Dictionary with GPU memory statistics in GB 929 + """ 930 + return { 931 + "active_memory_gb": mx.get_active_memory() / 1024**3, 932 + "peak_memory_gb": mx.get_peak_memory() / 1024**3, 933 + } 934 + 935 + 936 + def check_memory_available(required_gb: float) -> bool: 937 + """Pre-flight check before model loading. 938 + 939 + Args: 940 + required_gb: Required memory in GB 941 + 942 + Returns: 943 + True if memory is likely available (conservative estimate) 944 + """ 945 + current_memory = mx.get_active_memory() / 1024**3 946 + 947 + # Conservative estimate: assume system has at least 8GB unified memory 948 + # and we should leave some headroom (2GB) for system processes 949 + estimated_total = 8.0 # This could be improved by detecting actual system memory 950 + available = estimated_total - current_memory - 2.0 # 2GB headroom 951 + 952 + return available >= required_gb 953 + 954 + 955 + def run_model_enhanced( 956 + model_path: str, 957 + prompt: Optional[str] = None, 958 + interactive: bool = False, 959 + max_tokens: int = 500, 960 + temperature: float = 0.7, 961 + top_p: float = 0.9, 962 + repetition_penalty: float = 1.1, 963 + stream: bool = True, 964 + use_chat_template: bool = True, 965 + hide_reasoning: bool = False, 966 + verbose: bool = False, 967 + ) -> Optional[str]: 968 + """Enhanced run function with direct MLX integration. 969 + 970 + Uses context manager pattern for automatic resource cleanup. 971 + 972 + Args: 973 + model_path: Path to the MLX model 974 + prompt: Input prompt (if None, enters interactive mode) 975 + interactive: Force interactive mode 976 + max_tokens: Maximum tokens to generate 977 + temperature: Sampling temperature 978 + top_p: Top-p sampling parameter 979 + repetition_penalty: Penalty for repeated tokens 980 + stream: Whether to stream output 981 + 982 + Returns: 983 + Generated text (in non-interactive mode) 984 + """ 985 + try: 986 + with MLXRunner(model_path, verbose=verbose) as runner: 987 + # Interactive mode 988 + if interactive or prompt is None: 989 + runner.interactive_chat( 990 + max_tokens=max_tokens, 991 + temperature=temperature, 992 + top_p=top_p, 993 + repetition_penalty=repetition_penalty, 994 + use_chat_template=use_chat_template, 995 + ) 996 + return None 997 + 998 + # Single prompt mode 999 + if verbose: 1000 + print(f"\nPrompt: {prompt}\n") 1001 + print("Response: ", end="", flush=True) 1002 + 1003 + if stream: 1004 + # Streaming generation 1005 + response_tokens = [] 1006 + try: 1007 + for token in runner.generate_streaming( 1008 + prompt=prompt, 1009 + max_tokens=max_tokens, 1010 + temperature=temperature, 1011 + top_p=top_p, 1012 + repetition_penalty=repetition_penalty, 1013 + use_chat_template=use_chat_template, 1014 + hide_reasoning=hide_reasoning, 1015 + ): 1016 + # Stream all tokens directly (already formatted by generate_streaming) 1017 + print(token, end="", flush=True) 1018 + response_tokens.append(token) 1019 + except KeyboardInterrupt: 1020 + print("\n[INFO] Generation interrupted by user.") 1021 + response = "".join(response_tokens) 1022 + else: 1023 + # Batch generation 1024 + try: 1025 + response = runner.generate_batch( 1026 + prompt=prompt, 1027 + max_tokens=max_tokens, 1028 + temperature=temperature, 1029 + top_p=top_p, 1030 + repetition_penalty=repetition_penalty, 1031 + use_chat_template=use_chat_template, 1032 + ) 1033 + except KeyboardInterrupt: 1034 + print("\n[INFO] Generation interrupted by user.") 1035 + response = "" 1036 + print(response) 1037 + 1038 + # Show memory usage if verbose 1039 + if verbose: 1040 + memory_stats = runner.get_memory_usage() 1041 + print(f"\n\nMemory: {memory_stats['model_gb']:.1f}GB model, {memory_stats['current_gb']:.1f}GB total") 1042 + 1043 + return response 1044 + 1045 + # Note: cleanup happens automatically due to context manager 1046 + 1047 + except Exception as e: 1048 + print(f"\n[ERROR] {e}") 1049 + return None 1050 +
+165
server/model_card.py
··· 1 + 2 + from __future__ import annotations 3 + 4 + # ruff: noqa: UP045 5 + 6 + """ 7 + Lightweight helpers to read model metadata hints from cached Hugging Face models. 8 + 9 + No external dependencies; YAML front matter is hand-parsed leniently. 10 + 11 + Priority rules (Issue #31): 12 + - Tokenizer config: if tokenizer_config.json has chat_template -> Type = chat 13 + - README.md front matter (YAML): 14 + - tags contains "mlx" OR library_name == "mlx" -> Framework = MLX 15 + - pipeline_tag == text-generation OR tags contain chat/instruct -> Type = chat 16 + - pipeline_tag == sentence-similarity OR tags contain embedding -> Type = embedding 17 + - Fallback for framework/type remains in cache_utils 18 + """ 19 + 20 + import json 21 + from pathlib import Path 22 + from typing import Any, Dict, List, Optional, Tuple 23 + 24 + 25 + def _latest_snapshot_dir(model_base_dir: Path) -> Optional[Path]: 26 + """Return latest snapshot directory for a cached HF model base dir.""" 27 + try: 28 + snaps = (model_base_dir / "snapshots") 29 + if not snaps.exists(): 30 + return None 31 + candidates = [d for d in snaps.iterdir() if d.is_dir()] 32 + if not candidates: 33 + return None 34 + return max(candidates, key=lambda p: p.stat().st_mtime) 35 + except Exception: 36 + return None 37 + 38 + 39 + def _lenient_yaml_front_matter(text: str) -> Dict[str, Any]: 40 + """Very small YAML front matter parser for the fields we need. 41 + 42 + Supports forms: 43 + --- 44 + tags: [mlx, chat] 45 + pipeline_tag: text-generation 46 + library_name: mlx 47 + --- 48 + 49 + And list style: 50 + tags: 51 + - mlx 52 + - chat 53 + """ 54 + start = text.find("\n---\n") 55 + # Accept files starting directly with '---' too 56 + if text.startswith('---'): 57 + start = 0 58 + elif start >= 0: 59 + start = start + 1 # move to line start 60 + else: 61 + # Try at very beginning without newline 62 + start = 0 if text[:3] == '---' else -1 63 + if start != 0: 64 + return {} 65 + 66 + # Find closing '---' after start 67 + end = text.find('\n---', 3) 68 + if end == -1: 69 + return {} 70 + header = text[3:end] if text.startswith('---') else text[start + 3:end] 71 + 72 + # Normalize lines 73 + lines = [ln.strip() for ln in header.splitlines() if ln.strip()] 74 + 75 + data: Dict[str, Any] = {} 76 + current_key: Optional[str] = None 77 + list_acc: List[str] = [] 78 + 79 + def flush_list(): 80 + nonlocal list_acc, current_key 81 + if current_key is not None and list_acc: 82 + data[current_key] = list_acc[:] 83 + list_acc = [] 84 + 85 + for ln in lines: 86 + if ln.startswith('- '): 87 + # list item under current_key 88 + val = ln[2:].strip().strip('"\'') 89 + if current_key is not None: 90 + list_acc.append(val) 91 + continue 92 + # key: value or key: [a, b] 93 + if ':' in ln: 94 + # Close any previous list 95 + flush_list() 96 + key, val = ln.split(':', 1) 97 + key = key.strip() 98 + val = val.strip() 99 + current_key = key 100 + if not val: 101 + # expect multi-line list next 102 + data.setdefault(key, []) 103 + continue 104 + # Inline list [a, b] 105 + if val.startswith('[') and val.endswith(']'): 106 + inner = val[1:-1].strip() 107 + items = [] if not inner else [it.strip().strip('"\'') for it in inner.split(',')] 108 + data[key] = [x for x in items if x] 109 + continue 110 + # Scalar 111 + data[key] = val.strip('"\'') 112 + continue 113 + # Non key-value, ignore 114 + # Flush last list 115 + flush_list() 116 + return data 117 + 118 + 119 + def read_readme_front_matter(model_base_dir: Path) -> Tuple[Optional[List[str]], Optional[str], Optional[str]]: 120 + """Read README.md front matter and extract tags, pipeline_tag, library_name. 121 + 122 + Returns (tags, pipeline_tag, library_name) with lowercase normalization where applicable. 123 + Any read/parse error results in (None, None, None). 124 + """ 125 + try: 126 + snap = _latest_snapshot_dir(model_base_dir) 127 + if not snap: 128 + return None, None, None 129 + readme = snap / 'README.md' 130 + if not readme.exists(): 131 + return None, None, None 132 + text = readme.read_text(encoding='utf-8', errors='ignore') 133 + fm = _lenient_yaml_front_matter(text) 134 + if not fm: 135 + return None, None, None 136 + tags = fm.get('tags') 137 + if isinstance(tags, list): 138 + tags = [str(t).strip().lower() for t in tags if str(t).strip()] 139 + else: 140 + tags = None 141 + pipeline = fm.get('pipeline_tag') 142 + pipeline = str(pipeline).strip().lower() if pipeline else None 143 + lib = fm.get('library_name') 144 + lib = str(lib).strip().lower() if lib else None 145 + return tags, pipeline, lib 146 + except Exception: 147 + return None, None, None 148 + 149 + 150 + def tokenizer_has_chat_template(model_base_dir: Path) -> bool: 151 + """Check tokenizer_config.json for a non-empty 'chat_template' field in latest snapshot.""" 152 + try: 153 + snap = _latest_snapshot_dir(model_base_dir) 154 + if not snap: 155 + return False 156 + tk = snap / 'tokenizer_config.json' 157 + if not tk.exists(): 158 + return False 159 + with open(tk, encoding='utf-8') as f: 160 + data = json.load(f) 161 + tmpl = data.get('chat_template') 162 + return bool(tmpl and isinstance(tmpl, str) and tmpl.strip()) 163 + except Exception: 164 + return False 165 +
+15
server/pyproject.toml
··· 1 + [project] 2 + name = "server" 3 + version = "0.1.0" 4 + description = "Local MLX inference server for the Tiles CLI" 5 + requires-python = ">=3.10" 6 + dependencies = [ 7 + "fastapi", 8 + "uvicorn", 9 + "mlx-lm" 10 + ] 11 + 12 + [build-system] 13 + requires = ["setuptools", "wheel"] 14 + build-backend = "setuptools.build_meta" 15 +
+410
server/reasoning_utils.py
··· 1 + """ 2 + Utilities for handling reasoning models and their output. 3 + 4 + Different models use different formats for reasoning: 5 + - MXFP4/GPT-OSS: <|channel|>analysis<|message|>REASONING<|end|>...<|channel|>final<|message|>ANSWER 6 + - DeepSeek R1: <think>REASONING</think>ANSWER 7 + - Claude: <thinking>REASONING</thinking>ANSWER 8 + - QwQ: Similar to MXFP4 9 + """ 10 + 11 + import re 12 + from typing import Dict, Optional, Tuple 13 + 14 + 15 + class ReasoningExtractor: 16 + """Extract reasoning and final answer from model outputs.""" 17 + 18 + # Model-specific patterns 19 + PATTERNS = { 20 + 'gpt-oss': { 21 + 'reasoning': r'<\|channel\|>analysis<\|message\|>(.*?)<\|end\|>', 22 + 'final': r'<\|channel\|>final<\|message\|>(.*?)(?:<\|return\|>|$)', 23 + 'markers': { 24 + 'reasoning_start': '<|channel|>analysis<|message|>', 25 + 'reasoning_end': '<|end|>', 26 + 'final_marker': '<|channel|>final<|message|>', 27 + # Skip tokens that appear between reasoning and final 28 + 'skip_tokens': ['<|start|>assistant<|channel|>final<|message|>', '<|start|>assistant', '<|start|>', '<|channel|>final<|message|>'], 29 + # Conditional skip tokens - only skip if at start of final section 30 + 'conditional_skip': ['assistant'] 31 + } 32 + }, 33 + 'deepseek': { 34 + 'reasoning': r'<think>(.*?)</think>', 35 + 'final': r'</think>(.*?)$', 36 + 'markers': { 37 + 'reasoning_start': '<think>', 38 + 'reasoning_end': '</think>', 39 + } 40 + }, 41 + 'claude': { 42 + 'reasoning': r'<thinking>(.*?)</thinking>', 43 + 'final': r'</thinking>(.*?)$', 44 + 'markers': { 45 + 'reasoning_start': '<thinking>', 46 + 'reasoning_end': '</thinking>', 47 + } 48 + } 49 + } 50 + 51 + @classmethod 52 + def detect_model_type(cls, model_name: str) -> Optional[str]: 53 + """Detect reasoning model type from model name.""" 54 + model_lower = model_name.lower() 55 + 56 + if 'gpt-oss' in model_lower: 57 + return 'gpt-oss' 58 + elif 'deepseek' in model_lower and 'r1' in model_lower: 59 + return 'deepseek' 60 + elif 'claude' in model_lower: 61 + return 'claude' 62 + elif 'qwq' in model_lower: 63 + return 'gpt-oss' # QwQ uses similar format to GPT-OSS 64 + 65 + return None 66 + 67 + @classmethod 68 + def extract(cls, text: str, model_type: Optional[str] = None, 69 + model_name: Optional[str] = None) -> Dict[str, Optional[str]]: 70 + """ 71 + Extract reasoning and final answer from model output. 72 + 73 + Args: 74 + text: The full model output 75 + model_type: Explicit model type ('mxfp4', 'deepseek', etc.) 76 + model_name: Model name to auto-detect type 77 + 78 + Returns: 79 + Dictionary with 'reasoning', 'final_answer', and 'full_response' 80 + """ 81 + # Auto-detect model type if not provided 82 + if not model_type and model_name: 83 + model_type = cls.detect_model_type(model_name) 84 + 85 + # If no model type detected, return text as-is 86 + if not model_type or model_type not in cls.PATTERNS: 87 + return { 88 + 'reasoning': None, 89 + 'final_answer': text, 90 + 'full_response': text, 91 + 'has_reasoning': False 92 + } 93 + 94 + patterns = cls.PATTERNS[model_type] 95 + 96 + # Extract reasoning 97 + reasoning_match = re.search(patterns['reasoning'], text, re.DOTALL) 98 + reasoning = reasoning_match.group(1).strip() if reasoning_match else None 99 + 100 + # Extract final answer 101 + final_match = re.search(patterns['final'], text, re.DOTALL) 102 + final_answer = final_match.group(1).strip() if final_match else None 103 + 104 + # If no final answer found but we have reasoning, 105 + # the text after reasoning might be the answer 106 + if reasoning and not final_answer: 107 + # Try to find text after reasoning markers 108 + markers = patterns.get('markers', {}) 109 + if 'reasoning_end' in markers: 110 + split_text = text.split(markers['reasoning_end'], 1) 111 + if len(split_text) > 1: 112 + # Clean up any remaining markers 113 + remaining = split_text[1] 114 + for marker in markers.values(): 115 + remaining = remaining.replace(marker, '') 116 + final_answer = remaining.strip() 117 + 118 + # If still no final answer, use full text minus reasoning markers 119 + if not final_answer: 120 + final_answer = text 121 + # Remove all known markers 122 + if model_type in cls.PATTERNS: 123 + markers = cls.PATTERNS[model_type].get('markers', {}) 124 + for marker in markers.values(): 125 + final_answer = final_answer.replace(marker, '') 126 + final_answer = final_answer.strip() 127 + 128 + return { 129 + 'reasoning': reasoning, 130 + 'final_answer': final_answer, 131 + 'full_response': text, 132 + 'has_reasoning': bool(reasoning), 133 + 'model_type': model_type 134 + } 135 + 136 + @classmethod 137 + def format_for_display(cls, extracted: Dict[str, Optional[str]], 138 + show_reasoning: bool = False) -> str: 139 + """ 140 + Format extracted content for display. 141 + 142 + Args: 143 + extracted: Output from extract() 144 + show_reasoning: Whether to include reasoning in output 145 + 146 + Returns: 147 + Formatted string for display 148 + """ 149 + if not extracted.get('has_reasoning'): 150 + return extracted.get('final_answer', '') 151 + 152 + if show_reasoning: 153 + output = [] 154 + if extracted.get('reasoning'): 155 + output.append("═══ Reasoning ═══") 156 + output.append(extracted['reasoning']) 157 + output.append("\n═══ Answer ═══") 158 + output.append(extracted.get('final_answer', '')) 159 + return '\n'.join(output) 160 + else: 161 + return extracted.get('final_answer', '') 162 + 163 + 164 + class StreamingReasoningHandler: 165 + """Handle reasoning during streaming generation.""" 166 + 167 + def __init__(self, model_type: Optional[str] = None): 168 + self.model_type = model_type 169 + self.buffer = "" 170 + self.reasoning_buffer = "" 171 + self.final_buffer = "" 172 + self.in_reasoning = False 173 + self.in_final = False 174 + self.markers = {} 175 + 176 + if model_type and model_type in ReasoningExtractor.PATTERNS: 177 + self.markers = ReasoningExtractor.PATTERNS[model_type].get('markers', {}) 178 + 179 + def process_token(self, token: str) -> Tuple[str, bool]: 180 + """ 181 + Process a streaming token. 182 + 183 + Args: 184 + token: The new token 185 + 186 + Returns: 187 + (output_token, should_display) - token to output and whether to display it 188 + """ 189 + self.buffer += token 190 + 191 + # Check for reasoning start 192 + if not self.in_reasoning and self.markers.get('reasoning_start'): 193 + if self.markers['reasoning_start'] in self.buffer: 194 + self.in_reasoning = True 195 + self.reasoning_buffer = self.buffer.split(self.markers['reasoning_start'])[1] 196 + return ("", False) # Don't display reasoning start marker 197 + 198 + # If in reasoning, buffer it 199 + if self.in_reasoning: 200 + self.reasoning_buffer += token 201 + 202 + # Check for reasoning end 203 + if self.markers.get('reasoning_end') and self.markers['reasoning_end'] in self.reasoning_buffer: 204 + self.in_reasoning = False 205 + self.in_final = True 206 + # Clean up reasoning buffer 207 + self.reasoning_buffer = self.reasoning_buffer.replace(self.markers['reasoning_end'], '') 208 + return ("", False) # Don't display reasoning end marker 209 + 210 + return ("", False) # Don't display reasoning content by default 211 + 212 + # If in final answer section 213 + if self.in_final: 214 + # Skip final answer markers 215 + if self.markers.get('final_marker') and self.markers['final_marker'] in token: 216 + return ("", False) 217 + 218 + self.final_buffer += token 219 + return (token, True) # Display final answer 220 + 221 + # Default: display token if not in special section 222 + return (token, True) 223 + 224 + 225 + class StreamingReasoningParser: 226 + """Parser for real-time streaming with reasoning model formatting.""" 227 + 228 + def __init__(self, model_type: Optional[str] = None, hide_reasoning: bool = False): 229 + self.model_type = model_type 230 + self.hide_reasoning = hide_reasoning 231 + self.state = "WAITING" # WAITING, IN_REASONING, IN_FINAL 232 + self.buffer = "" 233 + self.reasoning_content = "" 234 + self.patterns = {} 235 + 236 + if model_type and model_type in ReasoningExtractor.PATTERNS: 237 + self.patterns = ReasoningExtractor.PATTERNS[model_type].get('markers', {}) 238 + 239 + def process_token(self, token: str): 240 + """ 241 + Process a streaming token and yield formatted output. 242 + 243 + Args: 244 + token: New token from model 245 + 246 + Yields: 247 + Formatted output tokens for display 248 + """ 249 + self.buffer += token 250 + 251 + # State: WAITING - looking for reasoning start 252 + if self.state == "WAITING": 253 + reasoning_start = self.patterns.get('reasoning_start') 254 + if reasoning_start and reasoning_start in self.buffer: 255 + # Found reasoning start 256 + before_reasoning = self.buffer.split(reasoning_start, 1)[0] 257 + 258 + # Yield any content before reasoning (but not control tokens) 259 + if before_reasoning.strip() and not before_reasoning.strip().startswith('<|'): 260 + yield before_reasoning 261 + 262 + # Start reasoning section (only if not hiding reasoning) 263 + if not self.hide_reasoning: 264 + yield "**[Reasoning]**\n\n" 265 + 266 + # Switch to reasoning state 267 + self.buffer = self.buffer.split(reasoning_start, 1)[1] 268 + self.state = "IN_REASONING" 269 + 270 + # Process remaining buffer recursively 271 + if self.buffer.strip(): 272 + yield from self.process_token("") 273 + return 274 + 275 + # Check if buffer might contain start of reasoning pattern 276 + if reasoning_start: 277 + # Check if buffer ends with partial pattern 278 + has_partial_match = False 279 + for i in range(1, min(len(reasoning_start) + 1, len(self.buffer) + 1)): 280 + if self.buffer.endswith(reasoning_start[:i]): 281 + has_partial_match = True 282 + break 283 + 284 + if has_partial_match: 285 + # Don't yield yet - might be building up to pattern 286 + return 287 + 288 + # No partial match, safe to yield older content 289 + # Keep enough buffer to detect pattern 290 + pattern_len = len(reasoning_start) 291 + if len(self.buffer) > pattern_len: 292 + to_yield = self.buffer[:-pattern_len] 293 + self.buffer = self.buffer[-pattern_len:] 294 + if to_yield: 295 + yield to_yield 296 + return 297 + 298 + # No reasoning pattern expected or very short buffer 299 + if not reasoning_start: 300 + yield token 301 + 302 + # State: IN_REASONING - collecting reasoning content 303 + elif self.state == "IN_REASONING": 304 + reasoning_end = self.patterns.get('reasoning_end') 305 + if reasoning_end and reasoning_end in self.buffer: 306 + # Found reasoning end 307 + reasoning_part = self.buffer.split(reasoning_end, 1)[0] 308 + 309 + # Yield reasoning content (only if not hiding reasoning) 310 + if reasoning_part and not self.hide_reasoning: 311 + yield reasoning_part 312 + 313 + # Add separator (only if not hiding reasoning) 314 + if not self.hide_reasoning: 315 + yield "\n\n---\n\n**[Answer]**\n\n" 316 + 317 + # Switch to final state 318 + self.buffer = self.buffer.split(reasoning_end, 1)[1] 319 + self.state = "IN_FINAL" 320 + self._final_content_started = False # Track if we've started outputting final content 321 + 322 + # Skip intermediate control tokens 323 + skip_tokens = self.patterns.get('skip_tokens', []) 324 + for skip_token in skip_tokens: 325 + self.buffer = self.buffer.replace(skip_token, '') 326 + 327 + # Skip final marker when we find it 328 + final_marker = self.patterns.get('final_marker') 329 + if final_marker and final_marker in self.buffer: 330 + self.buffer = self.buffer.split(final_marker, 1)[1] 331 + 332 + # Process remaining buffer 333 + if self.buffer.strip(): 334 + yield from self.process_token("") 335 + return 336 + 337 + # Still in reasoning, yield the content (only if not hiding reasoning) 338 + if not self.hide_reasoning: 339 + yield token 340 + 341 + # State: IN_FINAL - normal streaming of final answer 342 + elif self.state == "IN_FINAL": 343 + # Check for control tokens from patterns that should be filtered 344 + skip_tokens = self.patterns.get('skip_tokens', []) 345 + conditional_skip = self.patterns.get('conditional_skip', []) 346 + 347 + # Check if buffer contains any skip tokens and filter them out 348 + for skip_token in skip_tokens: 349 + if skip_token in self.buffer: 350 + # Remove the skip token and continue 351 + self.buffer = self.buffer.replace(skip_token, '') 352 + # Process remaining buffer if any 353 + if self.buffer.strip(): 354 + yield from self.process_token("") 355 + return 356 + 357 + # Check for final marker and filter it too 358 + final_marker = self.patterns.get('final_marker') 359 + if final_marker and final_marker in self.buffer: 360 + # Split at final marker and yield only content after it 361 + parts = self.buffer.split(final_marker, 1) 362 + if len(parts) > 1: 363 + self.buffer = parts[1] 364 + if self.buffer.strip(): 365 + yield from self.process_token("") 366 + return 367 + else: 368 + # Just the marker itself, skip it 369 + return 370 + 371 + # Check conditional skip tokens - only at start of final section 372 + if not getattr(self, '_final_content_started', False): 373 + for cond_token in conditional_skip: 374 + if token.strip() == cond_token: 375 + # Skip this token at the beginning of final section 376 + return 377 + # Mark that final content has started after first non-conditional token 378 + if token.strip() and not any(token.strip() == ct for ct in conditional_skip): 379 + self._final_content_started = True 380 + 381 + # Check if we might be building up to a skip token - be conservative 382 + potential_skip = False 383 + for skip_token in skip_tokens: 384 + if skip_token.startswith(token) or any(skip_token.startswith(self.buffer[-i:]) for i in range(1, min(len(skip_token), len(self.buffer)) + 1)): 385 + potential_skip = True 386 + break 387 + 388 + if potential_skip: 389 + # Don't yield yet, might be building up to a skip token 390 + return 391 + 392 + # Normal token in final answer - safe to yield 393 + yield token 394 + 395 + def finalize(self): 396 + """ 397 + Finalize parsing and yield any remaining buffer content. 398 + Call this when streaming is complete. 399 + """ 400 + if self.buffer.strip(): 401 + if self.state == "WAITING": 402 + # No reasoning was found, output as normal text 403 + yield self.buffer 404 + elif self.state == "IN_REASONING": 405 + # Reasoning never ended, output what we have 406 + yield self.buffer 407 + elif self.state == "IN_FINAL": 408 + # Final answer content 409 + yield self.buffer 410 +
+305
server/system_prompt.txt
··· 1 + # Memory Agent System Prompt 2 + 3 + You are an LLM agent with a self-managed, Obsidian-like memory system. You interact with memory using Python code blocks. 4 + 5 + ## CRITICAL: Response Format Rules 6 + 7 + **EVERY response MUST follow this EXACT structure:** 8 + 9 + 1. **Always start with `<think>`** - Your reasoning about the query and what memory operations are needed 10 + 2. **Always follow with `<python>`** - Either: 11 + - Python code to interact with memory, OR 12 + - Empty tags `<python></python>` if no memory interaction needed 13 + 3. **Only provide `<reply>` if `<python>` is empty** - Your response to the user 14 + 4. **The `<python></python>` and `<reply></reply>` MUST be separate, they should not be inside one another, they should be separate blocks** 15 + 16 + ### Valid Response Patterns: 17 + 18 + **Pattern 1: When interacting with memory** 19 + ``` 20 + <think> 21 + [Your reasoning here] 22 + </think> 23 + 24 + <python> 25 + [Your Python code here] 26 + </python> 27 + ``` 28 + 29 + **Pattern 2: When NOT interacting with memory** 30 + ``` 31 + <think> 32 + [Your reasoning here] 33 + </think> 34 + 35 + <python></python> 36 + 37 + <reply> 38 + [Your response to the user] 39 + </reply> 40 + ``` 41 + 42 + **CRITICAL: Always close ALL tags! Missing </think>, </python>, or </reply> will cause errors!** 43 + 44 + **NEVER:** 45 + - Skip the `<think>` block 46 + - Provide text outside of these tags 47 + - Use `<reply>` when you have Python code in `<python>` 48 + - Respond with plain text after receiving `<result>` blocks 49 + 50 + ## After Receiving `<result>` Blocks 51 + 52 + When you receive `<result>` blocks, you MUST: 53 + 1. Start a new response with `<think>` 54 + 2. Analyze the results and decide if more memory operations are needed 55 + 3. Either provide more Python code OR empty `<python></python>` with a `<reply>` 56 + 57 + **Understanding Results:** 58 + - `{'variable_name': value}` - Your assigned variables and their values 59 + - `{}` - Empty dict means NO variables were assigned (you forgot to capture return values!) 60 + - If you get `{}`, your function calls weren't assigned to variables 61 + 62 + ## Memory API 63 + 64 + **⚠️ CRITICAL: ALWAYS assign function results to variables or they will be LOST!** 65 + ```python 66 + # CORRECT - Results are captured 67 + exists = check_if_file_exists("user.md") 68 + content = read_file("user.md") 69 + 70 + # WRONG - Results are lost, you get empty {} 71 + check_if_file_exists("user.md") 72 + read_file("user.md") 73 + ``` 74 + 75 + ```python 76 + # File Operations 77 + create_file(file_path: str, content: str = "") -> bool # Auto-creates parent directories 78 + update_file(file_path: str, old_content: str, new_content: str) -> Union[bool, str] # Returns True or error message 79 + read_file(file_path: str) -> str 80 + delete_file(file_path: str) -> bool 81 + check_if_file_exists(file_path: str) -> bool 82 + 83 + # Directory Operations 84 + create_dir(dir_path: str) -> bool 85 + list_files() -> str # Shows tree structure of current working directory 86 + check_if_dir_exists(dir_path: str) -> bool 87 + 88 + # Utilities 89 + get_size(file_or_dir_path: str) -> int # Bytes; empty = total memory size 90 + go_to_link(link_string: str) -> bool 91 + ``` 92 + 93 + ## File Update Examples 94 + 95 + ### Adding to a table: 96 + ```python 97 + # Find the last row and add new row after it 98 + old_content = "| 2024-03-15 | Joined Premium | Active |" 99 + new_content = """| 2024-03-15 | Joined Premium | Active | 100 + | 2024-03-20 | Added Family | Active |""" 101 + result = update_file("user.md", old_content, new_content) 102 + 103 + # ALWAYS check the result! 104 + if result != True: 105 + # Handle the error - result contains the error message 106 + print(f"Update failed: {result}") 107 + 108 + Appending a new section: 109 + 110 + # Find the last line of a section and append after it 111 + old_content = "- favorite_color: blue" 112 + new_content = """- favorite_color: blue 113 + - favorite_food: pizza 114 + - favorite_movie: Inception""" 115 + result = update_file("user.md", old_content, new_content) 116 + 117 + Appending to a list: 118 + 119 + # Add a new item to an existing list 120 + old_content = """## Hobbies 121 + - reading 122 + - hiking""" 123 + new_content = """## Hobbies 124 + - reading 125 + - hiking 126 + - photography""" 127 + result = update_file("user.md", old_content, new_content) 128 + 129 + Updating a fact: 130 + 131 + old_content = "- user_age: 25" 132 + new_content = "- user_age: 26" 133 + result = update_file("user.md", old_content, new_content) 134 + 135 + ## Memory Structure 136 + 137 + ### Root Directory 138 + - `user.md`: Personal information & attributes about the user, plus relationships to other entities 139 + - `entities/`: Information about people, places, organizations, etc. 140 + - `[entity_name].md`: One file per entity 141 + 142 + ### File Conventions 143 + - Dates: YYYY-MM-DD format 144 + - File names: snake_case, no spaces 145 + - All files use .md extension 146 + - New sections in files start with ## headers 147 + - Facts stored as: `- fact_name: fact_value` 148 + - Cross-references: Use `[[entity_name]]` to link between entities 149 + 150 + ### user.md Structure 151 + ```markdown 152 + # User Information 153 + - user_name: [name] 154 + - user_age: [age] 155 + - [other attributes] 156 + 157 + ## User Relationships 158 + - wife: [[entities/jane_doe.md]] 159 + - friend: [[entities/john_smith.md]] 160 + - employer: [[entities/google.md]] 161 + 162 + ## Any other relation 163 + - name of entity: Explanation of what markdown files stores. [[entities/entity.md]] 164 + 165 + ## Tables 166 + - user.md can contain tables for structured data 167 + ``` 168 + 169 + ## Memory Operation Guidelines 170 + 171 + ### When to Save Information 172 + - **Personal facts**: Name, age, preferences, important dates 173 + - **Relationships**: Family, friends, colleagues, organizations 174 + - **Recurring topics**: Interests, projects, goals that come up repeatedly 175 + - **Context-dependent info**: Location, job, current situation 176 + 177 + ### When NOT to Save 178 + - Temporary information (e.g., "what's 2+2?") 179 + - General knowledge questions 180 + - One-off calculations or lookups 181 + 182 + ### Entity Creation Rules 183 + - Create new entity when: First mention of a person/place/organization with substantial information 184 + - Update existing entity when: New information about known entity 185 + - Attributes (age, location, etc.) belong in the entity file, NOT as separate entities 186 + !! Make sure the information is non existent before creating a new entity file !! 187 + 188 + ### Linking New Entities 189 + When creating a new entity file, ALWAYS add a link from the most relevant existing file (user.md OR another entity): 190 + 191 + **Example 1: Link from user.md** 192 + ```python 193 + # First: Create the new entity (entities/ dir created automatically) 194 + <python> 195 + content = """# Acme Corporation 196 + - industry: Technology 197 + - location: San Francisco, CA 198 + """ 199 + result = create_file("entities/acme_corp.md", content) 200 + </python> 201 + 202 + # After result, add link to user.md 203 + <python> 204 + old_content = "## User Relationships" 205 + new_content = """## User Relationships 206 + - **Employer**: Technology company where user works as senior engineer. [[entities/acme_corp.md]]""" 207 + result = update_file("user.md", old_content, new_content) 208 + </python> 209 + ``` 210 + 211 + **Example 2: Link between entities** 212 + ```python 213 + # First: Create new entity 214 + <python> 215 + content = """# John Smith 216 + - relationship: Colleague 217 + - department: Engineering 218 + """ 219 + result = create_file("entities/john_smith.md", content) 220 + </python> 221 + 222 + # After result, link from company entity 223 + <python> 224 + old_content = "## Employees" 225 + new_content = """## Employees 226 + - **Senior Engineer**: Works on backend systems team. [[entities/john_smith.md]]""" 227 + result = update_file("entities/acme_corp.md", old_content, new_content) 228 + </python> 229 + ``` 230 + 231 + Example link descriptions: 232 + - **Primary Residence**: Three-bedroom house with home office and garden. [[entities/452_willow_creek_dr.md]] 233 + - **Project Lead**: Manages the mobile app development team. [[entities/sarah_chen.md]] 234 + - **Subsidiary**: Wholly-owned AI research division. [[entities/acme_ai_labs.md]] 235 + 236 + ## Important Operating Rules 237 + 238 + 1. **Initial Check**: On first interaction, ALWAYS check if `user.md` exists and read its contents before any other operations 239 + 2. **Be Proactive**: Save relevant information without explicit requests 240 + 3. **Be Selective**: Only save crucial, reusable information 241 + 4. **No Print Statements**: They won't execute in the Python environment 242 + 5. **Valid Python Only**: Ensure syntactically correct code 243 + 6. **Execution Timeout**: Keep code blocks concise (5-second timeout) 244 + 7. **No Duplicates**: Check existing content before adding information 245 + 8. **CRITICAL - Use Variables**: ALWAYS capture return values for inspection 246 + ```python 247 + # Good - Result will be visible 248 + exists = check_if_file_exists("user.md") 249 + content = read_file("user.md") 250 + result = update_file("user.md", old, new) 251 + 252 + # Bad - Result will be LOST, you'll get empty {} 253 + check_if_file_exists("user.md") 254 + read_file("user.md") 255 + update_file("user.md", old, new) 256 + ``` 257 + **WARNING**: Function calls without assignment return empty {} results! 258 + 9. **Wait for Results**: After submitting Python code, wait for `<result>` blocks before proceeding 259 + 10. **Error Handling**: ALWAYS check return values from file operations 260 + ```python 261 + # Good - checks the result 262 + result = update_file("user.md", old, new) 263 + if result != True: 264 + # result contains the `e`rror message 265 + 266 + # Bad - ignores potential failure 267 + update_file("user.md", old, new) 268 + ``` 269 + 11. **Your `<python>` block MUST compile under `ast.parse` and yield no `SyntaxError`** 270 + 12. **One Operation Per Block**: Execute ONE main operation per `<python>` block to avoid errors 271 + ```python 272 + # Good - separate operations 273 + <python> 274 + exists = check_if_file_exists("user.md") 275 + </python> 276 + # Wait for result, then: 277 + <python> 278 + if exists: 279 + content = read_file("user.md") 280 + </python> 281 + 282 + # Bad - multiple operations can cause issues 283 + <python> 284 + exists = check_if_file_exists("user.md") 285 + content = read_file("user.md") 286 + result = update_file("user.md", old, new) 287 + </python> 288 + ``` 289 + 290 + ## Memory Maintenance 291 + 292 + - Keep user.md as the source of truth for user information 293 + - Ensure cross-references between entities are bidirectional when relevant 294 + - Periodically review entity relationships for consistency 295 + 296 + ## Correct Search Patterns 297 + 298 + - Use `list_files()` to see the complete directory structure 299 + - Start by reading user.md to understand existing relationships. It's your starting point. 300 + - Hop between markdowns using cross-references to gather context using read_file(). 301 + - Use `go_to_link()` to navigate to specific websites if needed, but only if it adds significant value to the memory. 302 + 303 + ## Filtering 304 + 305 + In the user query, you might receive a fact-retrieval question that incudes <filter> tags. In between these tags, the user might provide verbal filter(s) that may be inclusive or exclusive, you HAVE TO ABSOLUTELY FOLLOW THESE FILTERS. These filters provide privacy over user information. If there are no filters, just return the answer as is.
+1045
server/uv.lock
··· 1 + version = 1 2 + revision = 3 3 + requires-python = ">=3.10" 4 + resolution-markers = [ 5 + "python_full_version >= '3.11'", 6 + "python_full_version < '3.11'", 7 + ] 8 + 9 + [[package]] 10 + name = "annotated-types" 11 + version = "0.7.0" 12 + source = { registry = "https://pypi.org/simple" } 13 + sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 14 + wheels = [ 15 + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 16 + ] 17 + 18 + [[package]] 19 + name = "anyio" 20 + version = "4.11.0" 21 + source = { registry = "https://pypi.org/simple" } 22 + dependencies = [ 23 + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 24 + { name = "idna" }, 25 + { name = "sniffio" }, 26 + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 27 + ] 28 + sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } 29 + wheels = [ 30 + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, 31 + ] 32 + 33 + [[package]] 34 + name = "certifi" 35 + version = "2025.10.5" 36 + source = { registry = "https://pypi.org/simple" } 37 + sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } 38 + wheels = [ 39 + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, 40 + ] 41 + 42 + [[package]] 43 + name = "charset-normalizer" 44 + version = "3.4.4" 45 + source = { registry = "https://pypi.org/simple" } 46 + sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } 47 + wheels = [ 48 + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, 49 + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, 50 + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, 51 + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, 52 + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, 53 + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, 54 + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, 55 + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, 56 + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, 57 + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, 58 + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, 59 + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, 60 + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, 61 + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, 62 + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, 63 + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, 64 + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, 65 + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, 66 + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, 67 + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, 68 + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, 69 + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, 70 + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, 71 + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, 72 + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, 73 + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, 74 + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, 75 + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, 76 + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, 77 + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, 78 + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, 79 + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, 80 + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, 81 + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, 82 + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, 83 + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, 84 + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, 85 + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, 86 + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, 87 + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, 88 + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, 89 + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, 90 + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, 91 + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, 92 + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, 93 + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, 94 + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, 95 + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, 96 + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, 97 + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, 98 + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, 99 + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, 100 + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, 101 + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, 102 + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, 103 + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, 104 + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, 105 + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, 106 + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, 107 + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, 108 + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, 109 + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, 110 + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, 111 + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, 112 + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, 113 + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, 114 + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, 115 + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, 116 + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, 117 + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, 118 + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, 119 + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, 120 + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, 121 + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, 122 + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, 123 + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, 124 + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, 125 + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, 126 + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, 127 + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, 128 + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, 129 + ] 130 + 131 + [[package]] 132 + name = "click" 133 + version = "8.3.0" 134 + source = { registry = "https://pypi.org/simple" } 135 + dependencies = [ 136 + { name = "colorama", marker = "sys_platform == 'win32'" }, 137 + ] 138 + sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } 139 + wheels = [ 140 + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, 141 + ] 142 + 143 + [[package]] 144 + name = "colorama" 145 + version = "0.4.6" 146 + source = { registry = "https://pypi.org/simple" } 147 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 148 + wheels = [ 149 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 150 + ] 151 + 152 + [[package]] 153 + name = "exceptiongroup" 154 + version = "1.3.0" 155 + source = { registry = "https://pypi.org/simple" } 156 + dependencies = [ 157 + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 158 + ] 159 + sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } 160 + wheels = [ 161 + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, 162 + ] 163 + 164 + [[package]] 165 + name = "fastapi" 166 + version = "0.119.0" 167 + source = { registry = "https://pypi.org/simple" } 168 + dependencies = [ 169 + { name = "pydantic" }, 170 + { name = "starlette" }, 171 + { name = "typing-extensions" }, 172 + ] 173 + sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7", size = 336756, upload-time = "2025-10-11T17:13:40.53Z" } 174 + wheels = [ 175 + { url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2", size = 107095, upload-time = "2025-10-11T17:13:39.048Z" }, 176 + ] 177 + 178 + [[package]] 179 + name = "filelock" 180 + version = "3.20.0" 181 + source = { registry = "https://pypi.org/simple" } 182 + sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } 183 + wheels = [ 184 + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, 185 + ] 186 + 187 + [[package]] 188 + name = "fsspec" 189 + version = "2025.9.0" 190 + source = { registry = "https://pypi.org/simple" } 191 + sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } 192 + wheels = [ 193 + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, 194 + ] 195 + 196 + [[package]] 197 + name = "h11" 198 + version = "0.16.0" 199 + source = { registry = "https://pypi.org/simple" } 200 + sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 201 + wheels = [ 202 + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 203 + ] 204 + 205 + [[package]] 206 + name = "hf-xet" 207 + version = "1.1.10" 208 + source = { registry = "https://pypi.org/simple" } 209 + sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } 210 + wheels = [ 211 + { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, 212 + { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, 213 + { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, 214 + { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, 215 + { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, 216 + { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, 217 + { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, 218 + ] 219 + 220 + [[package]] 221 + name = "huggingface-hub" 222 + version = "0.35.3" 223 + source = { registry = "https://pypi.org/simple" } 224 + dependencies = [ 225 + { name = "filelock" }, 226 + { name = "fsspec" }, 227 + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, 228 + { name = "packaging" }, 229 + { name = "pyyaml" }, 230 + { name = "requests" }, 231 + { name = "tqdm" }, 232 + { name = "typing-extensions" }, 233 + ] 234 + sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hash = "sha256:350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a", size = 461798, upload-time = "2025-09-29T14:29:58.625Z" } 235 + wheels = [ 236 + { url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", hash = "sha256:0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba", size = 564262, upload-time = "2025-09-29T14:29:55.813Z" }, 237 + ] 238 + 239 + [[package]] 240 + name = "idna" 241 + version = "3.11" 242 + source = { registry = "https://pypi.org/simple" } 243 + sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } 244 + wheels = [ 245 + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, 246 + ] 247 + 248 + [[package]] 249 + name = "jinja2" 250 + version = "3.1.6" 251 + source = { registry = "https://pypi.org/simple" } 252 + dependencies = [ 253 + { name = "markupsafe" }, 254 + ] 255 + sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } 256 + wheels = [ 257 + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, 258 + ] 259 + 260 + [[package]] 261 + name = "markupsafe" 262 + version = "3.0.3" 263 + source = { registry = "https://pypi.org/simple" } 264 + sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } 265 + wheels = [ 266 + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, 267 + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, 268 + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, 269 + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, 270 + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, 271 + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, 272 + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, 273 + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, 274 + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, 275 + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, 276 + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, 277 + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, 278 + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, 279 + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, 280 + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, 281 + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, 282 + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, 283 + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, 284 + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, 285 + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, 286 + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, 287 + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, 288 + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, 289 + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, 290 + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, 291 + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, 292 + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, 293 + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, 294 + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, 295 + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, 296 + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, 297 + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, 298 + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, 299 + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, 300 + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, 301 + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, 302 + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, 303 + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, 304 + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, 305 + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, 306 + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, 307 + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, 308 + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, 309 + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, 310 + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, 311 + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, 312 + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, 313 + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, 314 + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, 315 + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, 316 + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, 317 + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, 318 + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, 319 + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, 320 + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, 321 + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, 322 + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, 323 + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, 324 + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, 325 + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, 326 + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, 327 + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, 328 + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, 329 + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, 330 + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, 331 + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, 332 + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, 333 + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, 334 + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, 335 + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, 336 + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, 337 + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, 338 + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, 339 + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, 340 + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, 341 + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, 342 + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, 343 + ] 344 + 345 + [[package]] 346 + name = "mlx" 347 + version = "0.29.3" 348 + source = { registry = "https://pypi.org/simple" } 349 + dependencies = [ 350 + { name = "mlx-metal", marker = "sys_platform == 'darwin'" }, 351 + ] 352 + wheels = [ 353 + { url = "https://files.pythonhosted.org/packages/a4/8a/743ff24a07f8cfd6fb14b3fe05f122f1d8e04e8a912b2f6d0e14369c8caf/mlx-0.29.3-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:340d46443fe0b1e5d84c1e36aa633310de70365ce79aefcaa6f618e62bd4b045", size = 548930, upload-time = "2025-10-17T19:16:49.872Z" }, 354 + { url = "https://files.pythonhosted.org/packages/b3/2a/af1b8391b6f543e59ca595f63aaddc33e320d3cc57a4c86ded6932d9dc3c/mlx-0.29.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8449c0e7c221e38368a734a471e0c4b1a7fea072947c75e893a1ee214f208d34", size = 548928, upload-time = "2025-10-17T19:16:58.275Z" }, 355 + { url = "https://files.pythonhosted.org/packages/49/85/0c58bdc5733ba92f78f067fc25e131e34db46562719d7909cebfad9313c5/mlx-0.29.3-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:1bba5203ed3f785167f5b8891c2e91ede23401586b0a723bfaf815a3ed450e3d", size = 548931, upload-time = "2025-10-17T19:16:52.198Z" }, 356 + { url = "https://files.pythonhosted.org/packages/94/13/3e91a37fa55dc0e9114620729ab61b27f45ed59053fc77846cad2df54f21/mlx-0.29.3-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0ffdf1f171c903adeaa688210ba39063059b102f3dcc52a64c2200d95d237f15", size = 549089, upload-time = "2025-10-17T19:17:00.446Z" }, 357 + { url = "https://files.pythonhosted.org/packages/13/01/ce008d14fbd2e22b694f568ab4014e14c979a2262c5f8c10e06d4806709f/mlx-0.29.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e7d1d815be0d4a41e598bdb2992822dafd9ab0d59d4b88af760ee0b6584506b7", size = 549091, upload-time = "2025-10-17T19:16:54.428Z" }, 358 + { url = "https://files.pythonhosted.org/packages/72/1c/45642746d36e91e26f3401e9b7931f92d8cc1eb6015cc40218628f320747/mlx-0.29.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:d33bff69887fadfd85ce67b8e11318c2319984f3ad4157f871aa9d3beb9de972", size = 549092, upload-time = "2025-10-17T19:17:10.963Z" }, 359 + { url = "https://files.pythonhosted.org/packages/07/f5/14e12e219a2715296150d35f930dc3a6ff319cd60126408e563f03100113/mlx-0.29.3-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:86c62791ce930028d75c41b88b4e3ceb58f5f2e263ff9bfacda998b0c03d9544", size = 549516, upload-time = "2025-10-17T19:18:13.831Z" }, 360 + { url = "https://files.pythonhosted.org/packages/c6/e2/5177c80e8c33a8be89fa45fa0a839d5b6a5578687d0ec973bf03638a4e73/mlx-0.29.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cddf6bcdc561094af6b3f0706f8768ecc5216a97eb6973e838c3ac2e2fca2cc8", size = 549509, upload-time = "2025-10-17T19:17:21.517Z" }, 361 + { url = "https://files.pythonhosted.org/packages/11/89/aa424217a7a0291b84f8969d504ac63f5af0ef60f248fe5562c3d6e44048/mlx-0.29.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:b2e1a249437d017a7425358420d28e641b7bc9c2650f3e013c1b1f4f239d8533", size = 549511, upload-time = "2025-10-17T19:16:54.227Z" }, 362 + { url = "https://files.pythonhosted.org/packages/fe/a2/078152b45aa8a23949a1b09601d0044f8bb4ab85e909e4475a440c21aaea/mlx-0.29.3-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:d59eccf6a1e1e131becc5a3910504507862da3a4e9b7bd9e73a625515d767844", size = 549585, upload-time = "2025-10-17T19:17:01.872Z" }, 363 + { url = "https://files.pythonhosted.org/packages/ae/bb/869eaac4efaae033c13db5fddd6a8907b5d667d135a35a2e482b1af402ee/mlx-0.29.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6642aa0a6dc2242c024fb8274d00631a7e7ffbdcef26148afd299b877c1e6a4a", size = 549586, upload-time = "2025-10-17T19:16:57.844Z" }, 364 + { url = "https://files.pythonhosted.org/packages/ad/76/196c248c2b2a471f795356564ad1d7dc40284160c8b66370ffadfd991fa1/mlx-0.29.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ec0aef311fab10cb5f2c274afa6edf6c482636096a5f7886aba43676454aa462", size = 549586, upload-time = "2025-10-17T19:16:39.912Z" }, 365 + ] 366 + 367 + [[package]] 368 + name = "mlx-lm" 369 + version = "0.28.3" 370 + source = { registry = "https://pypi.org/simple" } 371 + dependencies = [ 372 + { name = "jinja2" }, 373 + { name = "mlx", marker = "sys_platform == 'darwin'" }, 374 + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, 375 + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, 376 + { name = "protobuf" }, 377 + { name = "pyyaml" }, 378 + { name = "transformers" }, 379 + ] 380 + sdist = { url = "https://files.pythonhosted.org/packages/51/f6/15e002d52c28d8c544ec3aaf9053677468333e6ef0e76ea68579fd77b76d/mlx_lm-0.28.3.tar.gz", hash = "sha256:75df2b925d343ebaf50b63008dede4fe98cd3b02b1b24b7da71ebeb198d674f0", size = 214455, upload-time = "2025-10-17T21:44:33.921Z" } 381 + wheels = [ 382 + { url = "https://files.pythonhosted.org/packages/c2/a6/db3b44a5ac1a1174605628b0a477fbe4632d4fad1f94cf08647e27cc79ad/mlx_lm-0.28.3-py3-none-any.whl", hash = "sha256:ec103e2c9a06bd2cbafd41aafc975e40262176f7360d4f53ec342cebb9e0e6ea", size = 294506, upload-time = "2025-10-17T21:44:32.447Z" }, 383 + ] 384 + 385 + [[package]] 386 + name = "mlx-metal" 387 + version = "0.29.3" 388 + source = { registry = "https://pypi.org/simple" } 389 + wheels = [ 390 + { url = "https://files.pythonhosted.org/packages/41/95/a00054a006df82bb1b5b8f666ae44a676b259146fadbff90fe654309fefc/mlx_metal-0.29.3-py3-none-macosx_13_0_arm64.whl", hash = "sha256:27b5a4d905202a71e84d9fd559ea0236813f6f960ef494e5cafe9c45df4c9d7c", size = 36817352, upload-time = "2025-10-17T19:19:25.801Z" }, 391 + { url = "https://files.pythonhosted.org/packages/c0/d8/5ee91eac16dfcf0334103120b47d4abd8c890ccc0d73d3eee4770ce8810f/mlx_metal-0.29.3-py3-none-macosx_14_0_arm64.whl", hash = "sha256:f426d4b67f96b4d6f0ed50d5992933595aadb370dc3e9ed2410bafbc16229882", size = 36555573, upload-time = "2025-10-17T19:18:42.098Z" }, 392 + { url = "https://files.pythonhosted.org/packages/cd/9a/39b7ecdf21cf2a39ced8d7933eed65c6cb38295cadfd0907dd1abd4d1ded/mlx_metal-0.29.3-py3-none-macosx_15_0_arm64.whl", hash = "sha256:106616f7f825851043c53d3dc186965c003985da9cbb6e5c034f35108fc1fc27", size = 36549163, upload-time = "2025-10-17T19:18:37.701Z" }, 393 + ] 394 + 395 + [[package]] 396 + name = "numpy" 397 + version = "2.2.6" 398 + source = { registry = "https://pypi.org/simple" } 399 + resolution-markers = [ 400 + "python_full_version < '3.11'", 401 + ] 402 + sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } 403 + wheels = [ 404 + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, 405 + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, 406 + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, 407 + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, 408 + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, 409 + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, 410 + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, 411 + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, 412 + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, 413 + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, 414 + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, 415 + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, 416 + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, 417 + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, 418 + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, 419 + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, 420 + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, 421 + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, 422 + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, 423 + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, 424 + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, 425 + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, 426 + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, 427 + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, 428 + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, 429 + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, 430 + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, 431 + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, 432 + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, 433 + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, 434 + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, 435 + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, 436 + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, 437 + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, 438 + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, 439 + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, 440 + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, 441 + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, 442 + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, 443 + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, 444 + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, 445 + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, 446 + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, 447 + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, 448 + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, 449 + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, 450 + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, 451 + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, 452 + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, 453 + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, 454 + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, 455 + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, 456 + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, 457 + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, 458 + ] 459 + 460 + [[package]] 461 + name = "numpy" 462 + version = "2.3.4" 463 + source = { registry = "https://pypi.org/simple" } 464 + resolution-markers = [ 465 + "python_full_version >= '3.11'", 466 + ] 467 + sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } 468 + wheels = [ 469 + { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", size = 21259519, upload-time = "2025-10-15T16:15:19.012Z" }, 470 + { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", size = 14452796, upload-time = "2025-10-15T16:15:23.094Z" }, 471 + { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", size = 5381639, upload-time = "2025-10-15T16:15:25.572Z" }, 472 + { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", size = 6914296, upload-time = "2025-10-15T16:15:27.079Z" }, 473 + { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", size = 14591904, upload-time = "2025-10-15T16:15:29.044Z" }, 474 + { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", size = 16939602, upload-time = "2025-10-15T16:15:31.106Z" }, 475 + { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", size = 16372661, upload-time = "2025-10-15T16:15:33.546Z" }, 476 + { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", size = 18884682, upload-time = "2025-10-15T16:15:36.105Z" }, 477 + { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" }, 478 + { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" }, 479 + { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" }, 480 + { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, 481 + { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, 482 + { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, 483 + { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, 484 + { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, 485 + { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, 486 + { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, 487 + { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, 488 + { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, 489 + { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, 490 + { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, 491 + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, 492 + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, 493 + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, 494 + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, 495 + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, 496 + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, 497 + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, 498 + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, 499 + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, 500 + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, 501 + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, 502 + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, 503 + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, 504 + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, 505 + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, 506 + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, 507 + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, 508 + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, 509 + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, 510 + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, 511 + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, 512 + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, 513 + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, 514 + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, 515 + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, 516 + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, 517 + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, 518 + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, 519 + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, 520 + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, 521 + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, 522 + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, 523 + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, 524 + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, 525 + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, 526 + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, 527 + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, 528 + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, 529 + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, 530 + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, 531 + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, 532 + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, 533 + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, 534 + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, 535 + { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" }, 536 + { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" }, 537 + { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" }, 538 + { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" }, 539 + { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" }, 540 + { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" }, 541 + { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" }, 542 + ] 543 + 544 + [[package]] 545 + name = "packaging" 546 + version = "25.0" 547 + source = { registry = "https://pypi.org/simple" } 548 + sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 549 + wheels = [ 550 + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 551 + ] 552 + 553 + [[package]] 554 + name = "protobuf" 555 + version = "6.33.0" 556 + source = { registry = "https://pypi.org/simple" } 557 + sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } 558 + wheels = [ 559 + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, 560 + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, 561 + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, 562 + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, 563 + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, 564 + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, 565 + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, 566 + ] 567 + 568 + [[package]] 569 + name = "pydantic" 570 + version = "2.12.3" 571 + source = { registry = "https://pypi.org/simple" } 572 + dependencies = [ 573 + { name = "annotated-types" }, 574 + { name = "pydantic-core" }, 575 + { name = "typing-extensions" }, 576 + { name = "typing-inspection" }, 577 + ] 578 + sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } 579 + wheels = [ 580 + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, 581 + ] 582 + 583 + [[package]] 584 + name = "pydantic-core" 585 + version = "2.41.4" 586 + source = { registry = "https://pypi.org/simple" } 587 + dependencies = [ 588 + { name = "typing-extensions" }, 589 + ] 590 + sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } 591 + wheels = [ 592 + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, 593 + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, 594 + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, 595 + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, 596 + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, 597 + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, 598 + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, 599 + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, 600 + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, 601 + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, 602 + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, 603 + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, 604 + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, 605 + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, 606 + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, 607 + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, 608 + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, 609 + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, 610 + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, 611 + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, 612 + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, 613 + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, 614 + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, 615 + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, 616 + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, 617 + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, 618 + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, 619 + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, 620 + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, 621 + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, 622 + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, 623 + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, 624 + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, 625 + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, 626 + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, 627 + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, 628 + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, 629 + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, 630 + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, 631 + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, 632 + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, 633 + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, 634 + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, 635 + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, 636 + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, 637 + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, 638 + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, 639 + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, 640 + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, 641 + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, 642 + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, 643 + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, 644 + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, 645 + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, 646 + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, 647 + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, 648 + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, 649 + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, 650 + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, 651 + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, 652 + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, 653 + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, 654 + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, 655 + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, 656 + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, 657 + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, 658 + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, 659 + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, 660 + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, 661 + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, 662 + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, 663 + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, 664 + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, 665 + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, 666 + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, 667 + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, 668 + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, 669 + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, 670 + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, 671 + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, 672 + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, 673 + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, 674 + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, 675 + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, 676 + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, 677 + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, 678 + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, 679 + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, 680 + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, 681 + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, 682 + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, 683 + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, 684 + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, 685 + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, 686 + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, 687 + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, 688 + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, 689 + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, 690 + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, 691 + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, 692 + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, 693 + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, 694 + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, 695 + ] 696 + 697 + [[package]] 698 + name = "pyyaml" 699 + version = "6.0.3" 700 + source = { registry = "https://pypi.org/simple" } 701 + sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } 702 + wheels = [ 703 + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, 704 + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, 705 + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, 706 + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, 707 + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, 708 + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, 709 + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, 710 + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, 711 + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, 712 + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, 713 + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, 714 + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, 715 + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, 716 + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, 717 + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, 718 + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, 719 + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, 720 + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, 721 + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, 722 + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, 723 + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, 724 + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, 725 + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, 726 + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, 727 + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, 728 + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, 729 + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, 730 + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, 731 + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, 732 + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, 733 + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, 734 + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, 735 + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, 736 + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, 737 + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, 738 + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, 739 + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, 740 + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, 741 + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, 742 + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, 743 + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, 744 + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, 745 + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, 746 + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, 747 + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, 748 + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, 749 + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, 750 + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, 751 + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, 752 + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, 753 + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, 754 + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, 755 + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, 756 + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, 757 + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, 758 + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, 759 + ] 760 + 761 + [[package]] 762 + name = "regex" 763 + version = "2025.9.18" 764 + source = { registry = "https://pypi.org/simple" } 765 + sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } 766 + wheels = [ 767 + { url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788", size = 484829, upload-time = "2025-09-19T00:35:05.215Z" }, 768 + { url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4", size = 288993, upload-time = "2025-09-19T00:35:08.154Z" }, 769 + { url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61", size = 286624, upload-time = "2025-09-19T00:35:09.717Z" }, 770 + { url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251", size = 780473, upload-time = "2025-09-19T00:35:11.013Z" }, 771 + { url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746", size = 849290, upload-time = "2025-09-19T00:35:12.348Z" }, 772 + { url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2", size = 897335, upload-time = "2025-09-19T00:35:14.058Z" }, 773 + { url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0", size = 789946, upload-time = "2025-09-19T00:35:15.403Z" }, 774 + { url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8", size = 780787, upload-time = "2025-09-19T00:35:17.061Z" }, 775 + { url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea", size = 773632, upload-time = "2025-09-19T00:35:18.57Z" }, 776 + { url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8", size = 844104, upload-time = "2025-09-19T00:35:20.259Z" }, 777 + { url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25", size = 834794, upload-time = "2025-09-19T00:35:22.002Z" }, 778 + { url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29", size = 778535, upload-time = "2025-09-19T00:35:23.298Z" }, 779 + { url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444", size = 264115, upload-time = "2025-09-19T00:35:25.206Z" }, 780 + { url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450", size = 276143, upload-time = "2025-09-19T00:35:26.785Z" }, 781 + { url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442", size = 268473, upload-time = "2025-09-19T00:35:28.39Z" }, 782 + { url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a", size = 484832, upload-time = "2025-09-19T00:35:30.011Z" }, 783 + { url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8", size = 288994, upload-time = "2025-09-19T00:35:31.733Z" }, 784 + { url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414", size = 286619, upload-time = "2025-09-19T00:35:33.221Z" }, 785 + { url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a", size = 792454, upload-time = "2025-09-19T00:35:35.361Z" }, 786 + { url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4", size = 858723, upload-time = "2025-09-19T00:35:36.949Z" }, 787 + { url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a", size = 905899, upload-time = "2025-09-19T00:35:38.723Z" }, 788 + { url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f", size = 798981, upload-time = "2025-09-19T00:35:40.416Z" }, 789 + { url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a", size = 781900, upload-time = "2025-09-19T00:35:42.077Z" }, 790 + { url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9", size = 852952, upload-time = "2025-09-19T00:35:43.751Z" }, 791 + { url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2", size = 844355, upload-time = "2025-09-19T00:35:45.309Z" }, 792 + { url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95", size = 787254, upload-time = "2025-09-19T00:35:46.904Z" }, 793 + { url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07", size = 264129, upload-time = "2025-09-19T00:35:48.597Z" }, 794 + { url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9", size = 276160, upload-time = "2025-09-19T00:36:00.45Z" }, 795 + { url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df", size = 268471, upload-time = "2025-09-19T00:36:02.149Z" }, 796 + { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" }, 797 + { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" }, 798 + { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" }, 799 + { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" }, 800 + { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" }, 801 + { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" }, 802 + { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" }, 803 + { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" }, 804 + { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" }, 805 + { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" }, 806 + { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" }, 807 + { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" }, 808 + { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" }, 809 + { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" }, 810 + { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" }, 811 + { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" }, 812 + { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" }, 813 + { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" }, 814 + { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" }, 815 + { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" }, 816 + { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" }, 817 + { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" }, 818 + { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" }, 819 + { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" }, 820 + { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" }, 821 + { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" }, 822 + { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" }, 823 + { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" }, 824 + { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" }, 825 + { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" }, 826 + { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" }, 827 + { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" }, 828 + { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" }, 829 + { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" }, 830 + { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" }, 831 + { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" }, 832 + { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" }, 833 + { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" }, 834 + { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" }, 835 + { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" }, 836 + { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" }, 837 + { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" }, 838 + { url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129", size = 486130, upload-time = "2025-09-19T00:37:14.527Z" }, 839 + { url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea", size = 289539, upload-time = "2025-09-19T00:37:16.356Z" }, 840 + { url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1", size = 287233, upload-time = "2025-09-19T00:37:18.025Z" }, 841 + { url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47", size = 797876, upload-time = "2025-09-19T00:37:19.609Z" }, 842 + { url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379", size = 863385, upload-time = "2025-09-19T00:37:21.65Z" }, 843 + { url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203", size = 910220, upload-time = "2025-09-19T00:37:23.723Z" }, 844 + { url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164", size = 801827, upload-time = "2025-09-19T00:37:25.684Z" }, 845 + { url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb", size = 786843, upload-time = "2025-09-19T00:37:27.62Z" }, 846 + { url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743", size = 857430, upload-time = "2025-09-19T00:37:29.362Z" }, 847 + { url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282", size = 848612, upload-time = "2025-09-19T00:37:31.42Z" }, 848 + { url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773", size = 787967, upload-time = "2025-09-19T00:37:34.019Z" }, 849 + { url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788", size = 269847, upload-time = "2025-09-19T00:37:35.759Z" }, 850 + { url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3", size = 278755, upload-time = "2025-09-19T00:37:37.367Z" }, 851 + { url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d", size = 271873, upload-time = "2025-09-19T00:37:39.125Z" }, 852 + { url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306", size = 489773, upload-time = "2025-09-19T00:37:40.968Z" }, 853 + { url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946", size = 291221, upload-time = "2025-09-19T00:37:42.901Z" }, 854 + { url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f", size = 289268, upload-time = "2025-09-19T00:37:44.823Z" }, 855 + { url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95", size = 806659, upload-time = "2025-09-19T00:37:46.684Z" }, 856 + { url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b", size = 871701, upload-time = "2025-09-19T00:37:48.882Z" }, 857 + { url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3", size = 913742, upload-time = "2025-09-19T00:37:51.015Z" }, 858 + { url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571", size = 811117, upload-time = "2025-09-19T00:37:52.686Z" }, 859 + { url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad", size = 794647, upload-time = "2025-09-19T00:37:54.626Z" }, 860 + { url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494", size = 866747, upload-time = "2025-09-19T00:37:56.367Z" }, 861 + { url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b", size = 853434, upload-time = "2025-09-19T00:37:58.39Z" }, 862 + { url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41", size = 798024, upload-time = "2025-09-19T00:38:00.397Z" }, 863 + { url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096", size = 273029, upload-time = "2025-09-19T00:38:02.383Z" }, 864 + { url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a", size = 282680, upload-time = "2025-09-19T00:38:04.102Z" }, 865 + { url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01", size = 273034, upload-time = "2025-09-19T00:38:05.807Z" }, 866 + ] 867 + 868 + [[package]] 869 + name = "requests" 870 + version = "2.32.5" 871 + source = { registry = "https://pypi.org/simple" } 872 + dependencies = [ 873 + { name = "certifi" }, 874 + { name = "charset-normalizer" }, 875 + { name = "idna" }, 876 + { name = "urllib3" }, 877 + ] 878 + sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } 879 + wheels = [ 880 + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, 881 + ] 882 + 883 + [[package]] 884 + name = "safetensors" 885 + version = "0.6.2" 886 + source = { registry = "https://pypi.org/simple" } 887 + sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } 888 + wheels = [ 889 + { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, 890 + { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, 891 + { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, 892 + { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, 893 + { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, 894 + { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, 895 + { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, 896 + { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, 897 + { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, 898 + { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, 899 + { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, 900 + { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, 901 + { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, 902 + { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, 903 + ] 904 + 905 + [[package]] 906 + name = "server" 907 + version = "0.1.0" 908 + source = { editable = "." } 909 + dependencies = [ 910 + { name = "fastapi" }, 911 + { name = "mlx-lm" }, 912 + { name = "uvicorn" }, 913 + ] 914 + 915 + [package.metadata] 916 + requires-dist = [ 917 + { name = "fastapi" }, 918 + { name = "mlx-lm" }, 919 + { name = "uvicorn" }, 920 + ] 921 + 922 + [[package]] 923 + name = "sniffio" 924 + version = "1.3.1" 925 + source = { registry = "https://pypi.org/simple" } 926 + sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 927 + wheels = [ 928 + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 929 + ] 930 + 931 + [[package]] 932 + name = "starlette" 933 + version = "0.48.0" 934 + source = { registry = "https://pypi.org/simple" } 935 + dependencies = [ 936 + { name = "anyio" }, 937 + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 938 + ] 939 + sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } 940 + wheels = [ 941 + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, 942 + ] 943 + 944 + [[package]] 945 + name = "tokenizers" 946 + version = "0.22.1" 947 + source = { registry = "https://pypi.org/simple" } 948 + dependencies = [ 949 + { name = "huggingface-hub" }, 950 + ] 951 + sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9", size = 363123, upload-time = "2025-09-19T09:49:23.424Z" } 952 + wheels = [ 953 + { url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73", size = 3069318, upload-time = "2025-09-19T09:49:11.848Z" }, 954 + { url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc", size = 2926478, upload-time = "2025-09-19T09:49:09.759Z" }, 955 + { url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a", size = 3256994, upload-time = "2025-09-19T09:48:56.701Z" }, 956 + { url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7", size = 3153141, upload-time = "2025-09-19T09:48:59.749Z" }, 957 + { url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21", size = 3508049, upload-time = "2025-09-19T09:49:05.868Z" }, 958 + { url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214", size = 3710730, upload-time = "2025-09-19T09:49:01.832Z" }, 959 + { url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f", size = 3412560, upload-time = "2025-09-19T09:49:03.867Z" }, 960 + { url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4", size = 3250221, upload-time = "2025-09-19T09:49:07.664Z" }, 961 + { url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879", size = 9345569, upload-time = "2025-09-19T09:49:14.214Z" }, 962 + { url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446", size = 9271599, upload-time = "2025-09-19T09:49:16.639Z" }, 963 + { url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a", size = 9533862, upload-time = "2025-09-19T09:49:19.146Z" }, 964 + { url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390", size = 9681250, upload-time = "2025-09-19T09:49:21.501Z" }, 965 + { url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82", size = 2472003, upload-time = "2025-09-19T09:49:27.089Z" }, 966 + { url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684, upload-time = "2025-09-19T09:49:24.953Z" }, 967 + ] 968 + 969 + [[package]] 970 + name = "tqdm" 971 + version = "4.67.1" 972 + source = { registry = "https://pypi.org/simple" } 973 + dependencies = [ 974 + { name = "colorama", marker = "sys_platform == 'win32'" }, 975 + ] 976 + sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } 977 + wheels = [ 978 + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, 979 + ] 980 + 981 + [[package]] 982 + name = "transformers" 983 + version = "4.57.1" 984 + source = { registry = "https://pypi.org/simple" } 985 + dependencies = [ 986 + { name = "filelock" }, 987 + { name = "huggingface-hub" }, 988 + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, 989 + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, 990 + { name = "packaging" }, 991 + { name = "pyyaml" }, 992 + { name = "regex" }, 993 + { name = "requests" }, 994 + { name = "safetensors" }, 995 + { name = "tokenizers" }, 996 + { name = "tqdm" }, 997 + ] 998 + sdist = { url = "https://files.pythonhosted.org/packages/d6/68/a39307bcc4116a30b2106f2e689130a48de8bd8a1e635b5e1030e46fcd9e/transformers-4.57.1.tar.gz", hash = "sha256:f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55", size = 10142511, upload-time = "2025-10-14T15:39:26.18Z" } 999 + wheels = [ 1000 + { url = "https://files.pythonhosted.org/packages/71/d3/c16c3b3cf7655a67db1144da94b021c200ac1303f82428f2beef6c2e72bb/transformers-4.57.1-py3-none-any.whl", hash = "sha256:b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267", size = 11990925, upload-time = "2025-10-14T15:39:23.085Z" }, 1001 + ] 1002 + 1003 + [[package]] 1004 + name = "typing-extensions" 1005 + version = "4.15.0" 1006 + source = { registry = "https://pypi.org/simple" } 1007 + sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } 1008 + wheels = [ 1009 + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, 1010 + ] 1011 + 1012 + [[package]] 1013 + name = "typing-inspection" 1014 + version = "0.4.2" 1015 + source = { registry = "https://pypi.org/simple" } 1016 + dependencies = [ 1017 + { name = "typing-extensions" }, 1018 + ] 1019 + sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } 1020 + wheels = [ 1021 + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, 1022 + ] 1023 + 1024 + [[package]] 1025 + name = "urllib3" 1026 + version = "2.5.0" 1027 + source = { registry = "https://pypi.org/simple" } 1028 + sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } 1029 + wheels = [ 1030 + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, 1031 + ] 1032 + 1033 + [[package]] 1034 + name = "uvicorn" 1035 + version = "0.38.0" 1036 + source = { registry = "https://pypi.org/simple" } 1037 + dependencies = [ 1038 + { name = "click" }, 1039 + { name = "h11" }, 1040 + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 1041 + ] 1042 + sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } 1043 + wheels = [ 1044 + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, 1045 + ]