stackcraft-clef-flash-lora / code /reports /training-launch-source.json
nima1's picture
Publish verified checkpoint and losslessly compressed study evidence
4be6a52 verified
Raw History Blame Contribute Delete
190 kB
{
"source_commit": "d9fcd05c02e9327d4d791f2338725092cede8bdd",
"purpose": "Exact launch source for audit; current runnable code adds source-manifest provenance without changing training math.",
"files": {
"scripts/train_clef.py": {
"sha256": "e83586cf0fd0d7bb8c4aa15f9b858b9f9ffdb915622e355deb9513788d5054aa",
"utf8": "\"\"\"Train the fixed Stackcraft study for one or two epochs; no test-set access.\n\nRequires the M4 feasibility gate and a GPU admitted by the parent workflow.\nThis script never stops services, rents compute, or selects a checkpoint.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport hashlib\nimport importlib.metadata\nimport json\nimport math\nimport random\nimport subprocess\nimport time\nfrom pathlib import Path\nfrom typing import Any\n\nfrom stackcraft.clef import ClefPlayer, encode_observation\nfrom stackcraft.data import audit_dataset\nfrom stackcraft.players import observe\nfrom stackcraft.schema import GameState\n\nSTUDY_HASHES = {\n \"train\": \"edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8\",\n \"validation\": \"eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc\",\n}\nSTUDY_COUNTS = {\"train\": 827, \"validation\": 215}\nTRAINING_SEED = 42\nLORA_RANK = 4\n\n\ndef write_json(path: Path, value: Any) -> None:\n temporary = path.with_suffix(path.suffix + \".tmp\")\n temporary.write_text(json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + \"\\n\")\n temporary.replace(path)\n\n\ndef load_study(directory: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:\n \"\"\"Read/audit only the fixed train and validation files, never test trajectories.\"\"\"\n manifest_path = directory / \"manifest.json\"\n manifest = json.loads(manifest_path.read_text())\n records = {}\n for split in (\"train\", \"validation\"):\n raw = (directory / f\"{split}.jsonl\").read_bytes()\n digest = hashlib.sha256(raw).hexdigest()\n if digest != STUDY_HASHES[split]:\n raise ValueError(f\"{split} file does not match the frozen study-v1 SHA256\")\n records[split] = [json.loads(line) for line in raw.decode().splitlines()]\n if len(records[split]) != STUDY_COUNTS[split]:\n raise ValueError(f\"{split} size differs from the frozen study-v1 count\")\n audit_dataset(records, manifest)\n metadata = {\n \"dataset_manifest_sha256\": hashlib.sha256(manifest_path.read_bytes()).hexdigest(),\n \"dataset_split_sha256\": dict(STUDY_HASHES),\n \"dataset_counts\": dict(STUDY_COUNTS),\n \"dataset_source_commit\": manifest[\"source_commit\"],\n \"dataset_config_sha256\": manifest[\"config_sha256\"],\n \"test_trajectories_used\": False,\n \"validation_used_for_training\": False,\n }\n return records[\"train\"], metadata\n\n\ndef accumulation_groups(\n count: int, accumulation: int, *, epoch: int, seed: int = TRAINING_SEED\n) -> list[tuple[int, ...]]:\n \"\"\"Shuffle each complete epoch reproducibly and retain the final partial group.\"\"\"\n if count < 1 or accumulation < 1 or epoch < 1:\n raise ValueError(\"count, accumulation and epoch must be positive\")\n indices = list(range(count))\n random.Random(seed + epoch - 1).shuffle(indices)\n return [tuple(indices[start : start + accumulation]) for start in range(0, count, accumulation)]\n\n\ndef row_observation(row: dict[str, Any]):\n raw = row[\"observation\"]\n return observe(\n GameState(tuple(tuple(r) for r in raw[\"board\"]), 0, 0, raw[\"current\"], raw[\"next_piece\"])\n )\n\n\ndef train_epoch(\n player: Any,\n rows: list[dict[str, Any]],\n optimizer: Any,\n *,\n epoch: int,\n accumulation: int,\n mode: str,\n output: Path,\n) -> dict[str, Any]:\n \"\"\"Batch-one native training, averaging gradients over each actual group size.\"\"\"\n import torch\n\n from stackcraft.training import decision_loss\n\n model = player.model\n model.train()\n if mode == \"head\":\n model.language_model.eval()\n parameters = [parameter for parameter in model.parameters() if parameter.requires_grad]\n device = next(model.parameters()).device\n cuda = device.type == \"cuda\"\n groups = accumulation_groups(len(rows), accumulation, epoch=epoch)\n order = [rows[index][\"id\"] for group in groups for index in group]\n order_sha = hashlib.sha256(json.dumps(order, separators=(\",\", \":\")).encode()).hexdigest()\n write_json(output / f\"epoch-{epoch:02d}-order.json\", {\"row_ids\": order, \"sha256\": order_sha})\n total_loss = 0.0\n microstep = 0\n started = time.monotonic()\n with (output / f\"epoch-{epoch:02d}-events.jsonl\").open(\"x\") as log:\n for update, group in enumerate(groups, 1):\n optimizer.zero_grad(set_to_none=True)\n group_started = time.monotonic()\n for row_index in group:\n row = rows[row_index]\n step_started = time.monotonic()\n encoded = encode_observation(\n row_observation(row),\n player.processor.tokenizer,\n player.native,\n player.max_length,\n )\n batch = player.native.collate_records(\n [encoded], player.processor.tokenizer.pad_token_id, device\n )\n logits = model(batch)[0][0]\n loss = decision_loss(logits, encoded, row[\"action_id\"])\n if not torch.isfinite(loss):\n raise RuntimeError(f\"nonfinite training loss for {row['id']}\")\n # The last group has three rows in study-v1; divide by three, not eight.\n (loss / len(group)).backward()\n if cuda:\n torch.cuda.synchronize(device)\n value = float(loss.detach())\n total_loss += value\n microstep += 1\n event = {\n \"event\": \"microstep\",\n \"epoch\": epoch,\n \"microstep\": microstep,\n \"optimizer_step\": update,\n \"row_id\": row[\"id\"],\n \"tokens\": len(encoded.input_ids),\n \"loss\": value,\n \"accumulation_group_size\": len(group),\n \"seconds\": time.monotonic() - step_started,\n \"peak_allocated_bytes\": torch.cuda.max_memory_allocated(device) if cuda else 0,\n \"peak_reserved_bytes\": torch.cuda.max_memory_reserved(device) if cuda else 0,\n }\n log.write(json.dumps(event, allow_nan=False) + \"\\n\")\n log.flush()\n print(json.dumps(event, allow_nan=False), flush=True)\n del loss, logits, batch\n # The global norm is nonfinite if any gradient is NaN or Inf. This also\n # checks accumulated gradients before clipping and before optimizer.step.\n norm = torch.nn.utils.clip_grad_norm_(parameters, 1.0, error_if_nonfinite=True)\n if norm <= 0:\n raise RuntimeError(\"all trainable gradients are zero\")\n optimizer.step()\n if cuda:\n torch.cuda.synchronize(device)\n event = {\n \"event\": \"optimizer_step\",\n \"epoch\": epoch,\n \"optimizer_step\": update,\n \"microsteps\": len(group),\n \"gradient_norm_before_clip\": float(norm),\n \"seconds\": time.monotonic() - group_started,\n }\n log.write(json.dumps(event, allow_nan=False) + \"\\n\")\n log.flush()\n model.zero_grad(set_to_none=True)\n model.eval()\n return {\n \"epoch\": epoch,\n \"examples\": microstep,\n \"optimizer_steps\": len(groups),\n \"mean_training_loss\": total_loss / microstep,\n \"shuffle_order_sha256\": order_sha,\n \"seconds\": time.monotonic() - started,\n \"peak_allocated_bytes\": torch.cuda.max_memory_allocated(device) if cuda else 0,\n \"peak_reserved_bytes\": torch.cuda.max_memory_reserved(device) if cuda else 0,\n }\n\n\ndef source_metadata() -> dict[str, Any]:\n root = Path(__file__).resolve().parents[1]\n commit = subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"], cwd=root, text=True).strip()\n dirty = subprocess.check_output([\"git\", \"status\", \"--porcelain\"], cwd=root, text=True).strip()\n files = [\n Path(__file__).resolve(),\n root / \"src/stackcraft/training.py\",\n root / \"src/stackcraft/clef.py\",\n root / \"src/stackcraft/data.py\",\n ]\n return {\n \"source_commit\": commit,\n \"source_dirty\": bool(dirty),\n \"source_hashes\": {\n str(path.relative_to(root)): hashlib.sha256(path.read_bytes()).hexdigest()\n for path in files\n },\n }\n\n\ndef main(argv: list[str] | None = None) -> None:\n parser = argparse.ArgumentParser(description=__doc__)\n parser.add_argument(\"--output\", type=Path, required=True)\n parser.add_argument(\"--dataset\", type=Path, default=Path(\"data/study-v1\"))\n parser.add_argument(\"--mode\", choices=(\"lora\", \"head\"), default=\"lora\")\n parser.add_argument(\"--epochs\", type=int, choices=(1, 2), default=1)\n parser.add_argument(\"--learning-rate\", type=float, default=1e-5)\n parser.add_argument(\"--accumulation\", type=int, default=8)\n parser.add_argument(\"--max-length\", type=int, default=4096)\n args = parser.parse_args(argv)\n if not math.isfinite(args.learning_rate) or args.learning_rate <= 0:\n parser.error(\"--learning-rate must be finite and positive\")\n if args.accumulation < 1 or args.max_length < 1:\n parser.error(\"--accumulation and --max-length must be positive\")\n if args.output.exists():\n parser.error(\"output already exists; choose a new directory\")\n rows, dataset_metadata = load_study(args.dataset)\n args.output.mkdir(parents=True, exist_ok=False)\n config = {\n \"mode\": args.mode,\n \"epochs\": args.epochs,\n \"learning_rate\": args.learning_rate,\n \"seed\": TRAINING_SEED,\n \"rank\": LORA_RANK if args.mode == \"lora\" else None,\n \"batch_size\": 1,\n \"gradient_accumulation\": args.accumulation,\n \"max_length\": args.max_length,\n \"optimizer\": \"AdamW\",\n \"weight_decay\": 0.01,\n \"clip_gradient_norm\": 1.0,\n \"label_smoothing\": 0.05,\n \"brier_weight\": 0.1,\n \"selection\": \"external validation only; this script does not choose a checkpoint\",\n }\n metadata = {**dataset_metadata, **source_metadata(), \"config\": config}\n write_json(args.output / \"run_config.json\", metadata)\n report: dict[str, Any] = {\"status\": \"running\", \"epochs\": [], **metadata}\n write_json(args.output / \"report.json\", report)\n started = time.monotonic()\n try:\n import torch\n\n from stackcraft.training import parameter_hashes, prepare_trainable, save_checkpoint\n\n if not torch.cuda.is_available():\n raise RuntimeError(\"CUDA is required for the real study training run\")\n free, total = torch.cuda.mem_get_info()\n if free < 25 * 1024**3:\n raise RuntimeError(f\"requires at least 25 GiB free before loading; available={free}\")\n random.seed(TRAINING_SEED)\n torch.manual_seed(TRAINING_SEED)\n torch.cuda.manual_seed_all(TRAINING_SEED)\n torch.set_num_threads(8)\n torch.backends.cuda.matmul.allow_tf32 = False\n torch.backends.cudnn.benchmark = False\n torch.backends.cudnn.deterministic = True\n report.update(\n gpu=torch.cuda.get_device_name(),\n initial_free_vram=free,\n total_vram=total,\n package_versions={\n package: importlib.metadata.version(package)\n for package in (\"torch\", \"transformers\", \"peft\", \"safetensors\")\n },\n )\n player = ClefPlayer.from_pretrained(trust_pinned_code=True, max_length=args.max_length)\n prepare_trainable(player.model, mode=args.mode, rank=LORA_RANK)\n trainable_before = parameter_hashes(player.model, trainable=True)\n frozen_before = parameter_hashes(player.model, trainable=False)\n optimizer = torch.optim.AdamW(\n [parameter for parameter in player.model.parameters() if parameter.requires_grad],\n lr=args.learning_rate,\n weight_decay=0.01,\n )\n report[\"trainable_parameters\"] = sum(\n parameter.numel() for parameter in player.model.parameters() if parameter.requires_grad\n )\n write_json(args.output / \"report.json\", report)\n for epoch in range(1, args.epochs + 1):\n torch.cuda.reset_peak_memory_stats()\n outcome = train_epoch(\n player,\n rows,\n optimizer,\n epoch=epoch,\n accumulation=args.accumulation,\n mode=args.mode,\n output=args.output,\n )\n checkpoint = args.output / f\"epoch-{epoch:02d}\"\n save_checkpoint(player.model, checkpoint, extra_metadata={**metadata, **outcome})\n # Fixed training positions are used only for serialization parity.\n # Validation selection remains external; no held-out test row is read.\n player.model.eval()\n reference_rows = rows[:4]\n write_json(\n checkpoint / \"reference.json\",\n {\n \"row_ids\": [row[\"id\"] for row in reference_rows],\n \"dataset_manifest_sha256\": metadata[\"dataset_manifest_sha256\"],\n \"dataset_train_sha256\": metadata[\"dataset_split_sha256\"][\"train\"],\n \"probabilities\": [\n player.choose(row_observation(row)).probabilities for row in reference_rows\n ],\n \"absolute_tolerance\": 1e-4,\n \"max_length\": args.max_length,\n },\n )\n outcome[\"checkpoint\"] = str(checkpoint)\n report[\"epochs\"].append(outcome)\n write_json(args.output / \"report.json\", report)\n after = parameter_hashes(player.model, trainable=True)\n changed = [name for name in trainable_before if trainable_before[name] != after[name]]\n if not any(name.startswith(\"head.\") for name in changed):\n raise RuntimeError(\"decision-head parameters did not change\")\n if args.mode == \"lora\" and not any(\"lora_\" in name for name in changed):\n raise RuntimeError(\"LoRA parameters did not change\")\n if parameter_hashes(player.model, trainable=False) != frozen_before:\n raise RuntimeError(\"frozen backbone parameters changed\")\n report.update(\n status=\"trained-awaiting-external-validation\",\n changed_trainable_parameter_names=changed,\n frozen_parameters_unchanged=True,\n )\n except BaseException as error:\n report.update(status=\"failed\", error=f\"{type(error).__name__}: {error}\")\n raise\n finally:\n report[\"elapsed_seconds\"] = time.monotonic() - started\n write_json(args.output / \"report.json\", report)\n\n\nif __name__ == \"__main__\":\n main()\n"
},
"src/stackcraft/clef.py": {
"sha256": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
"utf8": "\"\"\"Pinned native Clef integration; importing this module needs no ML packages.\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport importlib\nimport json\nimport sys\nfrom pathlib import Path\nfrom types import ModuleType\nfrom typing import Any\n\nfrom stackcraft.players import Decision, Observation, validate_decision\nfrom stackcraft.schema import RULES_VERSION\n\nMODEL_ID = \"Cloudflare/clef-flash\"\nMODEL_REVISION = \"17f0b0ad64efb65d273590632833508766b2aae6\"\nSOURCE_SHA256 = \"0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3\"\nENCODING_VERSION = \"stackcraft-clef-v1\"\nQUESTION_ID = \"placement\"\nDEFAULT_MAX_LENGTH = 4096\n\n_INSTRUCTIONS = (\n \"Choose the legal placement that maximizes total lines cleared over the game. \"\n \"Avoid holes and high stacks so future pieces can be placed. Only the current \"\n \"piece and exactly one next piece are known. Consider all supplied placements.\"\n)\n\n\ndef observation_record(observation: Observation) -> dict[str, Any]:\n \"\"\"Build one native choice question from visible state; never include a seed.\"\"\"\n if observation.rules_version != RULES_VERSION:\n raise ValueError(\"unsupported observation rules version\")\n actions = observation.legal_actions\n if not actions:\n raise ValueError(\"cannot ask Clef to choose with no legal placements\")\n if len({action.id for action in actions}) != len(actions):\n raise ValueError(\"legal placement IDs must be unique\")\n return {\n \"model\": MODEL_ID,\n \"state\": {\n \"encoding_version\": ENCODING_VERSION,\n \"rules_version\": RULES_VERSION,\n \"board_rows\": [\n \"\".join(\"#\" if cell else \".\" for cell in row) for row in observation.board\n ],\n \"current_piece\": observation.current,\n \"next_piece\": observation.next_piece,\n \"coordinates\": (\n \"10 columns x=0..9 left to right; 20 rows y=0..19 top to bottom. \"\n \"board_rows are top to bottom; . is empty and # is occupied. \"\n \"Placement cells use absolute [x,y] coordinates.\"\n ),\n \"rules\": (\n \"Place the current four-cell piece at one supplied legal landing. \"\n \"A vertical hard drop starts fully inside row 0; no tucks, wall kicks, \"\n \"hold or gravity timer. Full rows clear simultaneously; rows above fall. \"\n \"Score for 1/2/3/4 cleared rows is 100/300/500/800. \"\n \"The next piece becomes current. No legal placement means game over. \"\n \"Future pieces beyond the one preview are unknown.\"\n ),\n },\n \"questions\": {\n QUESTION_ID: {\n \"type\": \"choice\",\n \"instructions\": _INSTRUCTIONS,\n \"criteria\": {\n action.id: {\n \"rotation\": action.rotation,\n \"column\": action.x,\n \"landing_row\": action.y,\n \"cells\": [list(cell) for cell in action.cells],\n }\n for action in actions\n },\n }\n },\n }\n\n\ndef _render(value: Any) -> str:\n return (\n value\n if isinstance(value, str)\n else json.dumps(value, ensure_ascii=False, separators=(\",\", \":\"), sort_keys=True)\n )\n\n\ndef complete_token_count(tokenizer: Any, native: Any, record: dict[str, Any]) -> int:\n \"\"\"Count exact text segments used by the pinned native encoder, before encoding.\n\n Tokenizing the joined text is NOT equivalent: native encode_record tokenizes\n each segment separately. This implementation is coupled to SOURCE_SHA256.\n Only the one-question, text-only Stackcraft record schema is supported.\n \"\"\"\n if record.get(\"images\") or record.get(\"videos\"):\n raise ValueError(\"Stackcraft Clef encoding is text-only\")\n questions = record.get(\"questions\", {})\n if list(questions) != [QUESTION_ID] or questions[QUESTION_ID].get(\"type\") != \"choice\":\n raise ValueError(\"expected the single Stackcraft placement choice question\")\n question = questions[QUESTION_ID]\n segments = [\n \"\\n\\nSCHEMA FIELDS:\\n\",\n f\"\\nFIELD 1\\nID: {QUESTION_ID}\\nTYPE: choice\\nINSTRUCTION: \",\n _render(question[\"instructions\"]),\n \"\\nALLOWED OPTIONS:\\n\",\n ]\n for index, (option_id, description) in enumerate(sorted(question[\"criteria\"].items())):\n semantics = {\"option_id\": option_id}\n if description is not None:\n semantics[\"description\"] = description\n segments.extend((f\"OPTION {index + 1}: \", _render(semantics), \"\\n\"))\n segments.extend(\n (\n \"END FIELD\\n\",\n f\"<|im_start|>system\\n{native.SYSTEM_PROMPT}<|im_end|>\\n<|im_start|>user\\nSTATE:\\n\",\n \"\\n<|im_end|>\\n<|im_start|>assistant\\n<think>\\n\\n</think>\\n\\nJOINT SCHEMA DECISIONS:\",\n _render(record[\"state\"]),\n )\n )\n return sum(len(tokenizer(segment, add_special_tokens=False).input_ids) for segment in segments)\n\n\ndef encode_observation(\n observation: Observation, tokenizer: Any, native: Any, max_length: int = DEFAULT_MAX_LENGTH\n) -> Any:\n \"\"\"Reject overlong input before native encode_record can truncate the board.\"\"\"\n if type(max_length) is not int or max_length < 1:\n raise ValueError(\"max_length must be a positive integer\")\n record = observation_record(observation)\n required = complete_token_count(tokenizer, native, record)\n if required > max_length:\n raise ValueError(\n f\"complete Stackcraft state and choices require {required} tokens; \"\n f\"max_length={max_length}; refusing to truncate\"\n )\n encoded = native.encode_record(tokenizer, record, max_length=max_length)\n if len(encoded.input_ids) != required:\n raise ValueError(\"native encoding length differs from preflight; source contract changed\")\n expected_ids = tuple(sorted(action.id for action in observation.legal_actions))\n if (\n len(encoded.questions) != 1\n or encoded.questions[0].question_id != QUESTION_ID\n or encoded.questions[0].option_ids != expected_ids\n ):\n raise ValueError(\"native encoded option IDs differ from complete legal action set\")\n return encoded\n\n\ndef import_pinned_source(path: Path, *, trust_pinned_code: bool = False) -> ModuleType:\n \"\"\"Execute only the explicitly trusted, reviewed native source bytes.\n\n A pinned revision limits changes; it does not make Python code a sandbox.\n The caller must deliberately accept executing the reviewed upstream module.\n \"\"\"\n if not trust_pinned_code:\n raise ValueError(\"native Clef loading requires trust_pinned_code=True\")\n source = path.read_bytes()\n if hashlib.sha256(source).hexdigest() != SOURCE_SHA256:\n raise ValueError(\"native Clef source SHA256 mismatch; refusing to execute\")\n name = f\"_stackcraft_clef_native_{SOURCE_SHA256}\"\n if name in sys.modules:\n return sys.modules[name]\n module = ModuleType(name)\n module.__file__ = str(path)\n sys.modules[name] = module # dataclasses resolves annotations through this registry.\n try:\n exec(compile(source, str(path), \"exec\"), module.__dict__)\n except BaseException:\n del sys.modules[name]\n raise\n return module\n\n\nclass ClefPlayer:\n \"\"\"Native single-record inference with unrounded probabilities.\n\n Direct construction also supports an explicitly supplied trained native model;\n such callers must set revision to the actual checkpoint identity. The factory\n below loads only the pinned, unchanged upstream release.\n \"\"\"\n\n name = \"clef-flash\"\n\n def __init__(\n self,\n model: Any,\n processor: Any,\n native: Any,\n *,\n revision: str,\n max_length: int = DEFAULT_MAX_LENGTH,\n ) -> None:\n if type(max_length) is not int or max_length < 1:\n raise ValueError(\"max_length must be a positive integer\")\n self.model = model.eval()\n self.processor = processor\n self.native = native\n self.revision = revision\n self.max_length = max_length\n self.last_input_tokens: int | None = None\n self.runtime_config: dict[str, Any] = {\n \"model_id\": MODEL_ID,\n \"revision\": revision,\n \"base_revision\": MODEL_REVISION,\n \"encoding_version\": ENCODING_VERSION,\n \"source_sha256\": SOURCE_SHA256,\n \"max_length\": max_length,\n \"dtype\": None,\n \"device\": None,\n }\n\n @classmethod\n def from_pretrained(\n cls,\n *,\n trust_pinned_code: bool = False,\n local_files_only: bool = True,\n device: str = \"cuda\",\n max_length: int = DEFAULT_MAX_LENGTH,\n ) -> ClefPlayer:\n \"\"\"Load a pinned local snapshot; downloading is separately opt-in.\n\n This loads real 9B weights onto device. It performs no GPU admission or\n workload management; callers must establish available memory beforehand.\n \"\"\"\n if not trust_pinned_code:\n raise ValueError(\"native Clef loading requires trust_pinned_code=True\")\n if type(max_length) is not int or max_length < 1:\n raise ValueError(\"max_length must be a positive integer\")\n hub = importlib.import_module(\"huggingface_hub\")\n snapshot = Path(\n hub.snapshot_download(\n repo_id=MODEL_ID,\n revision=MODEL_REVISION,\n local_files_only=local_files_only,\n )\n )\n native = import_pinned_source(\n snapshot / \"joint_schema_model.py\", trust_pinned_code=trust_pinned_code\n )\n config = json.loads((snapshot / \"config.json\").read_text())\n context = config[\"text_config\"][\"max_position_embeddings\"]\n if max_length > context:\n raise ValueError(f\"max_length={max_length} exceeds backbone context={context}\")\n torch = importlib.import_module(\"torch\")\n model, processor = native.load_release_model(snapshot, device=device, dtype=torch.bfloat16)\n return cls(\n model,\n processor,\n native,\n revision=f\"{MODEL_ID}@{MODEL_REVISION}:{ENCODING_VERSION}\",\n max_length=max_length,\n )\n\n def choose(self, observation: Observation) -> Decision:\n tokenizer = self.processor.tokenizer\n encoded = encode_observation(observation, tokenizer, self.native, self.max_length)\n pad_id = tokenizer.pad_token_id\n if pad_id is None:\n raise ValueError(\"native Clef tokenizer has no padding token\")\n torch = importlib.import_module(\"torch\")\n first_parameter = next(self.model.parameters())\n device = first_parameter.device\n batch = self.native.collate_records([encoded], pad_id, device)\n with torch.inference_mode():\n result = self.model(batch)\n if len(result) != 1 or len(result[0]) != 1:\n raise ValueError(\"native Clef must return one batch and one question\")\n logits = result[0][0]\n values = logits.float().softmax(-1).tolist()\n ids = encoded.questions[0].option_ids\n if len(values) != len(ids):\n raise ValueError(\"native Clef probability count differs from legal choices\")\n probabilities = dict(zip(ids, values, strict=True))\n # Preserve common engine ordering on exact ties, not native lexical order.\n selected = max(observation.legal_actions, key=lambda action: probabilities[action.id])\n decision = Decision(selected.id, probabilities)\n validate_decision(decision, observation)\n self.last_input_tokens = len(encoded.input_ids)\n self.runtime_config.update(\n dtype=str(first_parameter.dtype),\n device=str(device),\n )\n return decision\n"
},
"src/stackcraft/data.py": {
"sha256": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
"utf8": "\"\"\"Reproducible imitation data with episode splits and observation deduplication.\"\"\"\n\nimport hashlib\nimport json\nimport math\nfrom collections import Counter\nfrom dataclasses import asdict, dataclass\nfrom pathlib import Path\nfrom typing import Any\n\nfrom stackcraft.engine import legal_actions, new_game, step\nfrom stackcraft.expert import SearchExpert\nfrom stackcraft.players import HeuristicPlayer, Player, RandomPlayer, observe\nfrom stackcraft.schema import RULES_VERSION, GameState\n\nSCHEMA_VERSION = 1\nDEVELOPMENT_SEEDS = frozenset((*range(20), 1000))\n\n\ndef canonical_json(value: Any) -> str:\n return json.dumps(value, sort_keys=True, separators=(\",\", \":\"), allow_nan=False)\n\n\ndef digest(value: Any) -> str:\n return hashlib.sha256(canonical_json(value).encode()).hexdigest()\n\n\ndef source_hashes() -> dict[str, str]:\n root = Path(__file__).parent\n return {\n name: hashlib.sha256((root / name).read_bytes()).hexdigest()\n for name in (\n \"engine.py\",\n \"pieces.py\",\n \"schema.py\",\n \"players/__init__.py\",\n \"expert.py\",\n \"data.py\",\n )\n }\n\n\ndef observation_hash(observation: dict[str, Any]) -> str:\n \"\"\"Colors do not change dynamics. Keep all visible geometry and options.\"\"\"\n normalized = dict(observation)\n normalized[\"board\"] = [[int(cell != 0) for cell in row] for row in observation[\"board\"]]\n return digest(normalized)\n\n\n@dataclass(frozen=True)\nclass DatasetConfig:\n train_seeds: tuple[int, ...] = tuple(range(10000, 10024))\n validation_seeds: tuple[int, ...] = tuple(range(20000, 20006))\n reserved_test_seeds: tuple[int, ...] = tuple(range(30000, 30200))\n max_pieces: int = 40\n behavior_cycle: tuple[str, ...] = (\"random\", \"heuristic\", \"expert\")\n\n def __post_init__(self) -> None:\n pools = (self.train_seeds, self.validation_seeds, self.reserved_test_seeds)\n all_seeds = [seed for pool in pools for seed in pool]\n if any(not pool for pool in pools):\n raise ValueError(\"all seed pools must be nonempty\")\n if any(type(seed) is not int for seed in all_seeds):\n raise ValueError(\"seeds must be integers\")\n if len(set(all_seeds)) != len(all_seeds):\n raise ValueError(\"seed pools must be unique and disjoint\")\n if set(all_seeds) & DEVELOPMENT_SEEDS:\n raise ValueError(\"development seeds must not enter dataset pools\")\n if type(self.max_pieces) is not int or self.max_pieces < 1:\n raise ValueError(\"max_pieces must be a positive integer\")\n if not self.behavior_cycle or any(\n name not in (\"random\", \"heuristic\", \"expert\") for name in self.behavior_cycle\n ):\n raise ValueError(\"behavior_cycle must contain random, heuristic, or expert\")\n\n\n@dataclass(frozen=True)\nclass DatasetBundle:\n records: dict[str, list[dict[str, Any]]]\n manifest: dict[str, Any]\n\n\ndef _jsonl(records: list[dict[str, Any]]) -> str:\n return \"\".join(canonical_json(record) + \"\\n\" for record in records)\n\n\ndef generate_dataset(config: DatasetConfig, source_commit: str) -> DatasetBundle:\n \"\"\"Training owns duplicates before validation; conflicting labels are fatal.\"\"\"\n base_commit = source_commit.removesuffix(\"+working-tree\")\n if len(base_commit) != 40 or any(c not in \"0123456789abcdef\" for c in base_commit):\n raise ValueError(\"source_commit must be a full Git hash, optionally +working-tree\")\n teacher = SearchExpert()\n records: dict[str, list[dict[str, Any]]] = {\"train\": [], \"validation\": []}\n seen: dict[str, tuple[str, str, dict[str, int]]] = {}\n excluded = {split: {\"within_split\": 0, \"cross_split\": 0} for split in records}\n collected = dict.fromkeys(records, 0)\n for split, seeds in ((\"train\", config.train_seeds), (\"validation\", config.validation_seeds)):\n for episode_index, seed in enumerate(seeds):\n name = config.behavior_cycle[episode_index % len(config.behavior_cycle)]\n behavior: Player = (\n RandomPlayer(seed + 1_000_000)\n if name == \"random\"\n else HeuristicPlayer()\n if name == \"heuristic\"\n else teacher\n )\n state = new_game(seed)\n while not state.terminal and state.piece_index < config.max_pieces:\n observation = observe(state)\n values = teacher.action_values(observation)\n action_id = max(values, key=lambda action: values[action])\n raw_observation = json.loads(canonical_json(asdict(observation)))\n key = observation_hash(raw_observation)\n collected[split] += 1\n if key in seen:\n old_split, old_action, old_values = seen[key]\n if old_action != action_id or old_values != values:\n raise ValueError(\"duplicate observation has conflicting teacher labels\")\n excluded[split][\"within_split\" if old_split == split else \"cross_split\"] += 1\n else:\n seen[key] = (split, action_id, values)\n episode_id = f\"seed-{seed}\"\n records[split].append(\n {\n \"schema_version\": SCHEMA_VERSION,\n \"rules_version\": RULES_VERSION,\n \"id\": f\"{episode_id}-turn-{state.piece_index}\",\n \"split\": split,\n \"episode_id\": episode_id,\n \"seed\": seed,\n \"turn\": state.piece_index,\n \"behavior\": {\"name\": behavior.name, \"revision\": behavior.revision},\n \"observation\": raw_observation,\n \"observation_hash\": key,\n \"action_id\": action_id,\n \"action_values\": values,\n \"teacher_revision\": teacher.revision,\n \"source_commit\": source_commit,\n }\n )\n chosen = action_id if name == \"expert\" else behavior.choose(observation).action_id\n state = step(state, chosen).state\n manifest = {\n \"schema_version\": SCHEMA_VERSION,\n \"rules_version\": RULES_VERSION,\n \"source_commit\": source_commit,\n \"source_hashes\": source_hashes(),\n \"source_note\": \"File hashes identify source; commit may precede working-tree edits.\",\n \"teacher_revision\": teacher.revision,\n \"teacher\": {\"preview_pieces\": 1, \"search\": \"exhaustive legal placements\", \"optimal\": False},\n \"config\": json.loads(canonical_json(asdict(config))),\n \"seed_pools\": {\n \"train\": list(config.train_seeds),\n \"validation\": list(config.validation_seeds),\n \"reserved_test\": list(config.reserved_test_seeds),\n },\n \"test_trajectories_generated\": False,\n \"deduplication\": {\"normalization\": \"board occupancy\", \"exclusions\": excluded},\n \"splits\": {\n split: {\n \"collected\": collected[split],\n \"records\": len(rows),\n \"sha256\": hashlib.sha256(_jsonl(rows).encode()).hexdigest(),\n \"behavior_records\": dict(\n sorted(Counter(row[\"behavior\"][\"name\"] for row in rows).items())\n ),\n }\n for split, rows in records.items()\n },\n }\n manifest[\"config_sha256\"] = digest(manifest[\"config\"])\n audit_dataset(records, manifest)\n return DatasetBundle(records, manifest)\n\n\ndef audit_dataset(\n records: dict[str, list[dict[str, Any]]], manifest: dict[str, Any]\n) -> dict[str, int]:\n \"\"\"Check provenance, legal labels, stable tie-breaking, and split leakage.\"\"\"\n if set(records) != {\"train\", \"validation\"}:\n raise ValueError(\"dataset must contain train and validation only\")\n if manifest[\"test_trajectories_generated\"] is not False:\n raise ValueError(\"test trajectories must remain reserved\")\n config = DatasetConfig(**manifest[\"config\"])\n if manifest[\"config_sha256\"] != digest(manifest[\"config\"]):\n raise ValueError(\"configuration hash mismatch\")\n expected_pools = {\n \"train\": list(config.train_seeds),\n \"validation\": list(config.validation_seeds),\n \"reserved_test\": list(config.reserved_test_seeds),\n }\n if manifest[\"seed_pools\"] != expected_pools:\n raise ValueError(\"seed pool manifest differs from configuration\")\n seen_hashes: set[str] = set()\n seen_ids: set[str] = set()\n episode_splits: dict[str, str] = {}\n for split, rows in records.items():\n if manifest[\"splits\"][split][\"sha256\"] != hashlib.sha256(_jsonl(rows).encode()).hexdigest():\n raise ValueError(\"record content hash mismatch\")\n if manifest[\"splits\"][split][\"records\"] != len(rows):\n raise ValueError(\"record count mismatch\")\n for row in rows:\n if row[\"split\"] != split or row[\"seed\"] not in expected_pools[split]:\n raise ValueError(\"record is assigned to the wrong split\")\n if row[\"episode_id\"] != f\"seed-{row['seed']}\":\n raise ValueError(\"episode identifier differs from seed\")\n if row[\"id\"] != f\"{row['episode_id']}-turn-{row['turn']}\" or row[\"id\"] in seen_ids:\n raise ValueError(\"invalid or duplicate record ID\")\n if type(row[\"turn\"]) is not int or not 0 <= row[\"turn\"] < config.max_pieces:\n raise ValueError(\"turn exceeds episode limits\")\n seen_ids.add(row[\"id\"])\n if episode_splits.setdefault(row[\"episode_id\"], split) != split:\n raise ValueError(\"episode overlaps dataset splits\")\n for field in (\"schema_version\", \"rules_version\", \"source_commit\", \"teacher_revision\"):\n if row[field] != manifest[field]:\n raise ValueError(f\"record {field} differs from manifest\")\n observation = row[\"observation\"]\n if set(observation) != {\n \"board\",\n \"current\",\n \"next_piece\",\n \"legal_actions\",\n \"rules_version\",\n }:\n raise ValueError(\"observation contains unexpected or hidden fields\")\n state = GameState(\n tuple(tuple(r) for r in observation[\"board\"]),\n 0,\n 0,\n observation[\"current\"],\n observation[\"next_piece\"],\n )\n if observation != json.loads(canonical_json(asdict(observe(state)))):\n raise ValueError(\"observation contains incorrect legal actions or rules\")\n key = observation_hash(observation)\n if row[\"observation_hash\"] != key or key in seen_hashes:\n raise ValueError(\"observation hash mismatch or duplicate across dataset\")\n seen_hashes.add(key)\n values = row[\"action_values\"]\n actions = legal_actions(state)\n if (\n not actions\n or set(values) != {a.id for a in actions}\n or any(\n type(value) not in (int, float) or not math.isfinite(value)\n for value in values.values()\n )\n ):\n raise ValueError(\"teacher values must be finite and cover legal actions\")\n if row[\"action_id\"] != max(actions, key=lambda a: values[a.id]).id:\n raise ValueError(\"teacher label differs from action values or tie rule\")\n return {split: len(rows) for split, rows in records.items()}\n\n\ndef write_dataset(bundle: DatasetBundle, directory: Path) -> None:\n \"\"\"Write a verified immutable artifact directory; refuse existing files.\"\"\"\n audit_dataset(bundle.records, bundle.manifest)\n directory.mkdir(parents=True, exist_ok=True)\n paths = [directory / f\"{split}.jsonl\" for split in bundle.records]\n paths.append(directory / \"manifest.json\")\n if any(path.exists() for path in paths):\n raise FileExistsError(\"dataset artifact files already exist\")\n for split, rows in bundle.records.items():\n (directory / f\"{split}.jsonl\").write_text(_jsonl(rows))\n (directory / \"manifest.json\").write_text(canonical_json(bundle.manifest) + \"\\n\")\n"
},
"src/stackcraft/training.py": {
"sha256": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
"utf8": "\"\"\"Optional native Clef training primitives; imported only by ML workflows.\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport json\nimport math\nimport re\nfrom pathlib import Path\nfrom typing import Any, Literal\n\nimport torch\nfrom safetensors.torch import load_file, save_file\nfrom torch import nn\nfrom torch.nn import functional as functional\n\nfrom stackcraft.clef import ENCODING_VERSION, MODEL_ID, MODEL_REVISION, SOURCE_SHA256\n\nFORMAT_VERSION = 1\nHEAD_TYPE = \"native-joint-schema-fp32-gathered-rows-v1\"\n_TARGET = re.compile(\n r\"^model\\.language_model\\.layers\\.\\d+\\.\"\n r\"(?:self_attn|linear_attn|mlp)\\.\"\n r\"(?:q_proj|k_proj|v_proj|o_proj|in_proj_qkv|in_proj_z|in_proj_b|in_proj_a|\"\n r\"out_proj|gate_proj|up_proj|down_proj)$\"\n)\n\n\nclass GatheredFloat32Embedding:\n \"\"\"The pinned head only indexes this object; never materialize all rows in FP32.\"\"\"\n\n def __init__(self, weight: torch.Tensor) -> None:\n self.weight = weight\n\n def __getitem__(self, indices: torch.Tensor) -> torch.Tensor:\n # Both indexing and casting retain autograd edges when the input requires it.\n return self.weight[indices].float()\n\n\nclass FP32DecisionHead(nn.Module):\n \"\"\"Keep the upstream head unchanged while adapting its floating inputs.\"\"\"\n\n def __init__(self, native_head: nn.Module) -> None:\n super().__init__()\n self.native_head = native_head.float()\n\n def forward(\n self,\n hidden_states: torch.Tensor,\n input_ids: torch.Tensor,\n attention_mask: torch.Tensor,\n records: list[Any],\n output_embedding_weight: torch.Tensor,\n ) -> list[list[torch.Tensor]]:\n # Disable surrounding autocast so trainable head and its operations stay FP32.\n with torch.autocast(device_type=hidden_states.device.type, enabled=False):\n return self.native_head(\n hidden_states.float(),\n input_ids,\n attention_mask,\n records,\n GatheredFloat32Embedding(output_embedding_weight),\n )\n\n\ndef lora_target_modules(backbone: nn.Module) -> list[str]:\n \"\"\"Return full text-layer names; suffix-only matching can accidentally train vision.\"\"\"\n targets = [\n name\n for name, module in backbone.named_modules()\n if isinstance(module, nn.Linear) and _TARGET.fullmatch(name)\n ]\n if not targets:\n raise ValueError(\"no supported Qwen3.5 text-layer LoRA targets found\")\n return sorted(targets)\n\n\ndef prepare_trainable(model: Any, mode: Literal[\"head\", \"lora\"] = \"head\", rank: int = 4) -> Any:\n \"\"\"Prepare a pinned, already admitted/loaded native ClefModel in place.\n\n This function loads no model or weights. The caller must supply the pinned\n native model, e.g. ClefPlayer.from_pretrained(...).model after memory admission.\n Backbone dtype is preserved; the intended release loader supplies BF16.\n \"\"\"\n if mode not in (\"head\", \"lora\"):\n raise ValueError(\"training mode must be head or lora\")\n if type(rank) is not int or rank < 1:\n raise ValueError(\"LoRA rank must be a positive integer\")\n if isinstance(model.head, FP32DecisionHead) or hasattr(model, \"_stackcraft_training\"):\n raise ValueError(\"model is already prepared for Stackcraft training\")\n if hasattr(model.language_model, \"peft_config\"):\n raise ValueError(\"expected the unchanged backbone, not an existing PEFT model\")\n model.language_model.requires_grad_(False)\n targets: list[str] = []\n if mode == \"lora\":\n from peft import LoraConfig, get_peft_model\n\n targets = lora_target_modules(model.language_model)\n model.language_model = get_peft_model(\n model.language_model,\n LoraConfig(\n r=rank,\n lora_alpha=2 * rank,\n lora_dropout=0.0,\n target_modules=targets,\n bias=\"none\",\n ),\n )\n if hasattr(model.language_model, \"gradient_checkpointing_enable\"):\n model.language_model.gradient_checkpointing_enable(\n gradient_checkpointing_kwargs={\"use_reentrant\": False}\n )\n model.head = FP32DecisionHead(model.head)\n model.head.requires_grad_(True)\n model.train()\n if mode == \"head\":\n model.language_model.eval()\n model._stackcraft_training = {\n \"format_version\": FORMAT_VERSION,\n \"base_model\": MODEL_ID,\n \"base_revision\": MODEL_REVISION,\n \"native_source_sha256\": SOURCE_SHA256,\n \"encoding_version\": ENCODING_VERSION,\n \"head_type\": HEAD_TYPE,\n \"mode\": mode,\n \"lora\": (\n {\"rank\": rank, \"alpha\": 2 * rank, \"dropout\": 0.0, \"target_modules\": targets}\n if mode == \"lora\"\n else None\n ),\n \"loss\": {\"label_smoothing\": 0.05, \"brier_weight\": 0.1, \"brier_reduction\": \"sum_options\"},\n }\n return model\n\n\ndef decision_loss(\n logits: torch.Tensor,\n encoded_record: Any,\n target_action_id: str,\n *,\n label_smoothing: float = 0.05,\n brier_weight: float = 0.1,\n) -> torch.Tensor:\n \"\"\"One native choice: smoothed cross entropy plus multiclass Brier sum.\"\"\"\n if len(encoded_record.questions) != 1:\n raise ValueError(\"training records must contain exactly one choice question\")\n question = encoded_record.questions[0]\n if question.question_type != 1:\n raise ValueError(\"training question must be native choice type 1\")\n ids = question.option_ids\n if tuple(ids) != tuple(sorted(set(ids))):\n raise ValueError(\"encoded option IDs must be unique and lexicographically sorted\")\n if target_action_id not in ids:\n raise ValueError(\"target action is missing from native encoded option IDs\")\n if logits.ndim != 1 or logits.numel() != len(ids):\n raise ValueError(\"logits shape does not match the encoded choices\")\n if (\n not math.isfinite(label_smoothing)\n or not 0 <= label_smoothing <= 1\n or not math.isfinite(brier_weight)\n or brier_weight < 0\n ):\n raise ValueError(\"invalid label smoothing or Brier weight\")\n if not torch.isfinite(logits).all():\n raise ValueError(\"decision logits contain nonfinite values\")\n values = logits.float().unsqueeze(0)\n target = torch.tensor([ids.index(target_action_id)], device=values.device)\n cross_entropy = functional.cross_entropy(values, target, label_smoothing=label_smoothing)\n one_hot = functional.one_hot(target, num_classes=len(ids)).float()\n brier = (values.softmax(-1) - one_hot).square().sum(-1).mean()\n return cross_entropy + brier_weight * brier\n\n\ndef parameter_hashes(\n model: nn.Module, *, trainable: bool, chunk_elements: int = 1_048_576\n) -> dict[str, str]:\n \"\"\"Hash selected parameters exactly, moving only bounded chunks to CPU.\n\n Full frozen-backbone hashing is intentionally an explicit before/after audit,\n not a training-step operation. Dtype and shape are included in every digest.\n \"\"\"\n if type(chunk_elements) is not int or chunk_elements < 1:\n raise ValueError(\"chunk_elements must be a positive integer\")\n results = {}\n for name, parameter in model.named_parameters():\n if parameter.requires_grad != trainable:\n continue\n digest = hashlib.sha256()\n digest.update(f\"{parameter.dtype}:{tuple(parameter.shape)}:\".encode())\n flattened = parameter.detach().reshape(-1)\n for start in range(0, flattened.numel(), chunk_elements):\n chunk = flattened[start : start + chunk_elements].to(\"cpu\").contiguous()\n digest.update(chunk.view(torch.uint8).numpy().tobytes())\n results[name] = digest.hexdigest()\n return results\n\n\ndef save_checkpoint(\n model: Any, path: str | Path, *, extra_metadata: dict[str, Any] | None = None\n) -> dict[str, Any]:\n \"\"\"Save head and optional LoRA separately, never the frozen multi-GB backbone.\"\"\"\n if not isinstance(model.head, FP32DecisionHead) or not hasattr(model, \"_stackcraft_training\"):\n raise ValueError(\"model must be prepared before saving a training checkpoint\")\n destination = Path(path)\n destination.mkdir(parents=True, exist_ok=False)\n metadata = dict(model._stackcraft_training)\n metadata[\"extra\"] = extra_metadata or {}\n # Store native head keys, not wrapper-specific state_dict prefixes.\n head_state = {\n name: tensor.detach().cpu().contiguous()\n for name, tensor in model.head.native_head.state_dict().items()\n }\n save_file(head_state, destination / \"joint_head.safetensors\")\n metadata[\"head_shapes\"] = {name: list(tensor.shape) for name, tensor in head_state.items()}\n if metadata[\"mode\"] == \"lora\":\n model.language_model.save_pretrained(destination / \"adapter\", safe_serialization=True)\n adapter_path = destination / \"adapter\" / \"adapter_config.json\"\n adapter_config = json.loads(adapter_path.read_text())\n adapter_config.update(\n base_model_name_or_path=MODEL_ID,\n revision=MODEL_REVISION,\n target_modules=metadata[\"lora\"][\"target_modules\"],\n )\n adapter_path.write_text(json.dumps(adapter_config, indent=2, sort_keys=True) + \"\\n\")\n (destination / \"training_config.json\").write_text(\n json.dumps(metadata, indent=2, sort_keys=True, allow_nan=False) + \"\\n\"\n )\n return metadata\n\n\ndef load_checkpoint(model: Any, path: str | Path, *, trainable: bool = False) -> Any:\n \"\"\"Restore onto an unchanged pinned native base; reject incompatible metadata.\"\"\"\n source = Path(path)\n metadata = json.loads((source / \"training_config.json\").read_text())\n expected = {\n \"format_version\": FORMAT_VERSION,\n \"base_model\": MODEL_ID,\n \"base_revision\": MODEL_REVISION,\n \"native_source_sha256\": SOURCE_SHA256,\n \"encoding_version\": ENCODING_VERSION,\n \"head_type\": HEAD_TYPE,\n }\n for key, value in expected.items():\n if type(metadata.get(key)) is not type(value) or metadata[key] != value:\n raise ValueError(f\"checkpoint {key} is incompatible with this pinned native adapter\")\n mode = metadata.get(\"mode\")\n if mode not in (\"head\", \"lora\"):\n raise ValueError(\"checkpoint training mode is invalid\")\n if isinstance(model.head, FP32DecisionHead) or hasattr(model.language_model, \"peft_config\"):\n raise ValueError(\"checkpoint must load onto an unchanged native base\")\n head_state = load_file(source / \"joint_head.safetensors\", device=\"cpu\")\n shapes = {name: list(tensor.shape) for name, tensor in head_state.items()}\n base_shapes = {name: list(tensor.shape) for name, tensor in model.head.state_dict().items()}\n if shapes != metadata.get(\"head_shapes\") or shapes != base_shapes:\n raise ValueError(\"checkpoint head structure differs from metadata or native model\")\n if any(tensor.dtype != torch.float32 for tensor in head_state.values()):\n raise ValueError(\"checkpoint head tensors must be FP32\")\n model.language_model.requires_grad_(False)\n if mode == \"lora\":\n from peft import LoraConfig, PeftModel\n from peft.tuners.tuners_utils import check_target_module_exists\n\n lora = metadata.get(\"lora\")\n if not isinstance(lora, dict) or lora.get(\"target_modules\") != lora_target_modules(\n model.language_model\n ):\n raise ValueError(\"checkpoint LoRA targets differ from the native text backbone\")\n if (\n type(lora.get(\"rank\")) is not int\n or lora[\"rank\"] < 1\n or lora.get(\"alpha\") != 2 * lora[\"rank\"]\n or lora.get(\"dropout\") != 0.0\n ):\n raise ValueError(\n \"checkpoint LoRA rank, alpha or dropout violates the training contract\"\n )\n config = json.loads((source / \"adapter\" / \"adapter_config.json\").read_text())\n # PEFT 0.21.2 minimizes >=20 explicit module names to equivalent suffixes.\n # Compare their meaning on this exact unchanged backbone, not list spelling.\n # Enumerating ALL modules ensures an accidental vision/MTP/lm_head match\n # makes the sets unequal and is rejected before installing the adapter.\n saved_config = LoraConfig.from_pretrained(str(source / \"adapter\"))\n resolved_targets = sorted(\n name\n for name, _ in model.language_model.named_modules()\n if check_target_module_exists(saved_config, name)\n )\n if (\n config.get(\"r\") != lora.get(\"rank\")\n or config.get(\"lora_alpha\") != lora.get(\"alpha\")\n or config.get(\"lora_dropout\") != lora.get(\"dropout\")\n or resolved_targets != lora[\"target_modules\"]\n or config.get(\"bias\") != \"none\"\n or config.get(\"modules_to_save\") is not None\n or config.get(\"target_parameters\") is not None\n ):\n raise ValueError(\"saved adapter configuration differs from checkpoint metadata\")\n model.language_model = PeftModel.from_pretrained(\n model.language_model, source / \"adapter\", is_trainable=trainable\n )\n if trainable and hasattr(model.language_model, \"gradient_checkpointing_enable\"):\n model.language_model.gradient_checkpointing_enable(\n gradient_checkpointing_kwargs={\"use_reentrant\": False}\n )\n elif metadata.get(\"lora\") is not None:\n raise ValueError(\"head-only checkpoint must not contain LoRA configuration\")\n model.head = FP32DecisionHead(model.head)\n model.head.native_head.load_state_dict(head_state, strict=True)\n model.head.requires_grad_(trainable)\n model._stackcraft_training = {\n key: value for key, value in metadata.items() if key not in (\"extra\", \"head_shapes\")\n }\n model.train(trainable)\n if mode == \"head\":\n model.language_model.eval()\n return model\n"
},
"src/stackcraft/engine.py": {
"sha256": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
"utf8": "\"\"\"Pure, deterministic vertical-placement falling-block game rules.\"\"\"\n\nfrom dataclasses import replace\n\nfrom stackcraft.pieces import piece_at, rotations\nfrom stackcraft.schema import COLORS, HEIGHT, WIDTH, Board, Cells, GameState, Placement, Transition\n\n_SCORES = (0, 100, 300, 500, 800)\n\n\ndef new_game(seed: int) -> GameState:\n return GameState(\n board=tuple((0,) * WIDTH for _ in range(HEIGHT)),\n seed=seed,\n piece_index=0,\n current=piece_at(seed, 0),\n next_piece=piece_at(seed, 1),\n )\n\n\ndef _fits(state: GameState, cells: Cells, x: int, y: int) -> bool:\n return all(\n 0 <= x + dx < WIDTH and 0 <= y + dy < HEIGHT and state.board[y + dy][x + dx] == 0\n for dx, dy in cells\n )\n\n\ndef legal_actions(state: GameState) -> tuple[Placement, ...]:\n \"\"\"Enumerate rotation then column; each move starts fully inside row zero.\"\"\"\n if state.terminal:\n return ()\n result = []\n for rotation, cells in enumerate(rotations(state.current)):\n width = max(x for x, _ in cells) + 1\n for x in range(WIDTH - width + 1):\n if not _fits(state, cells, x, 0):\n continue\n y = 0\n while _fits(state, cells, x, y + 1):\n y += 1\n absolute = tuple((x + dx, y + dy) for dx, dy in cells)\n result.append(Placement(f\"r{rotation}x{x}\", rotation, x, y, absolute))\n return tuple(result)\n\n\ndef place(board: Board, piece: str, action: Placement) -> tuple[Board, int]:\n \"\"\"Place a previously validated action without reading any future pieces.\n\n Callers must supply an action from legal_actions for this board and piece.\n This low-level helper checks occupied/boundary cells but not hard-drop paths;\n user-controlled IDs must go through step instead.\n \"\"\"\n if piece not in COLORS:\n raise ValueError(f\"unknown piece: {piece!r}\")\n if len(set(action.cells)) != 4 or any(\n not (0 <= x < WIDTH and 0 <= y < HEIGHT) or board[y][x] for x, y in action.cells\n ):\n raise ValueError(\"placement cells must be four distinct empty in-bounds cells\")\n rows = [list(row) for row in board]\n for x, y in action.cells:\n rows[y][x] = COLORS[piece]\n remaining = [tuple(row) for row in rows if not all(row)]\n cleared = HEIGHT - len(remaining)\n if cleared > 4:\n raise ValueError(\"invalid starting board: more than four completed rows\")\n return tuple([(0,) * WIDTH] * cleared + remaining), cleared\n\n\ndef step(state: GameState, action_id: str) -> Transition:\n \"\"\"Validate, place, clear simultaneously, advance, and detect next top-out.\"\"\"\n action = next((move for move in legal_actions(state) if move.id == action_id), None)\n if action is None:\n raise ValueError(f\"illegal action {action_id!r}\")\n board, cleared = place(state.board, state.current, action)\n next_state = GameState(\n board=board,\n seed=state.seed,\n piece_index=state.piece_index + 1,\n current=state.next_piece,\n next_piece=piece_at(state.seed, state.piece_index + 2),\n score=state.score + _SCORES[cleared],\n lines=state.lines + cleared,\n )\n if not legal_actions(next_state):\n next_state = replace(next_state, terminal=True)\n return Transition(next_state, action, cleared)\n"
},
"src/stackcraft/pieces.py": {
"sha256": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
"utf8": "\"\"\"Normalized tetromino rotations and the versioned deterministic seven-bag.\"\"\"\n\nfrom functools import lru_cache\nfrom random import Random\n\nfrom stackcraft.schema import PIECES, RULES_VERSION, Cells\n\n_SHAPES: dict[str, Cells] = {\n \"I\": ((0, 0), (1, 0), (2, 0), (3, 0)),\n \"O\": ((0, 0), (1, 0), (0, 1), (1, 1)),\n \"T\": ((1, 0), (0, 1), (1, 1), (2, 1)),\n \"S\": ((1, 0), (2, 0), (0, 1), (1, 1)),\n \"Z\": ((0, 0), (1, 0), (1, 1), (2, 1)),\n \"J\": ((0, 0), (0, 1), (1, 1), (2, 1)),\n \"L\": ((2, 0), (0, 1), (1, 1), (2, 1)),\n}\n\n\ndef _normalize(cells: Cells) -> Cells:\n left = min(x for x, _ in cells)\n top = min(y for _, y in cells)\n return tuple(sorted((x - left, y - top) for x, y in cells))\n\n\n@lru_cache(maxsize=7)\ndef rotations(piece: str) -> tuple[Cells, ...]:\n \"\"\"Return distinct clockwise rotations, normalized to top-left (0, 0).\"\"\"\n if piece not in _SHAPES:\n raise ValueError(f\"unknown piece: {piece!r}\")\n current = _normalize(_SHAPES[piece])\n result: list[Cells] = []\n for _ in range(4):\n if current not in result:\n result.append(current)\n current = _normalize(tuple((-y, x) for x, y in current))\n return tuple(result)\n\n\n@lru_cache(maxsize=4096)\ndef _bag(seed: int, index: int) -> tuple[str, ...]:\n # Each bag has its own local PRNG: random access does not depend on call order.\n # Freeze the shuffle algorithm as Fisher-Yates using Random.random(), whose\n # compatible-seeder sequence Python guarantees, rather than randrange().\n rng = Random(f\"{RULES_VERSION}:{seed}:{index}\")\n bag = list(PIECES)\n for position in range(len(bag) - 1, 0, -1):\n other = int(rng.random() * (position + 1))\n bag[position], bag[other] = bag[other], bag[position]\n return tuple(bag)\n\n\ndef piece_at(seed: int, index: int) -> str:\n \"\"\"Read an indexed piece without revealing or advancing global RNG state.\"\"\"\n if type(seed) is not int:\n raise ValueError(\"seed must be an integer\")\n if type(index) is not int or index < 0:\n raise ValueError(\"piece index must be a nonnegative integer\")\n bag_index, offset = divmod(index, len(PIECES))\n return _bag(seed, bag_index)[offset]\n"
},
"src/stackcraft/schema.py": {
"sha256": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
"utf8": "\"\"\"Immutable types shared by the engine, players, replay, and HTTP service.\"\"\"\n\nfrom dataclasses import dataclass\n\nWIDTH = 10\nHEIGHT = 20\nRULES_VERSION = \"stackcraft-v1\"\nPIECES = (\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\")\nCOLORS = {piece: index + 1 for index, piece in enumerate(PIECES)}\ntype Cells = tuple[tuple[int, int], ...]\ntype Board = tuple[tuple[int, ...], ...]\n\n\n@dataclass(frozen=True)\nclass Placement:\n id: str\n rotation: int\n x: int\n y: int\n cells: Cells\n\n\n@dataclass(frozen=True)\nclass GameState:\n board: Board\n seed: int\n piece_index: int\n current: str\n next_piece: str\n score: int = 0\n lines: int = 0\n terminal: bool = False\n\n def __post_init__(self) -> None:\n if not isinstance(self.board, tuple) or len(self.board) != HEIGHT:\n raise ValueError(f\"board must contain {HEIGHT} immutable rows\")\n if any(\n not isinstance(row, tuple)\n or len(row) != WIDTH\n or any(type(cell) is not int or not 0 <= cell <= 7 for cell in row)\n for row in self.board\n ):\n raise ValueError(f\"board rows must contain {WIDTH} integer cells from 0 to 7\")\n if type(self.seed) is not int:\n raise ValueError(\"seed must be an integer\")\n for name in (\"piece_index\", \"score\", \"lines\"):\n value = getattr(self, name)\n if type(value) is not int or value < 0:\n raise ValueError(f\"{name} must be a nonnegative integer\")\n if self.current not in PIECES or self.next_piece not in PIECES:\n raise ValueError(\"current and next_piece must be tetromino names\")\n if type(self.terminal) is not bool:\n raise ValueError(\"terminal must be a boolean\")\n\n\n@dataclass(frozen=True)\nclass Transition:\n state: GameState\n action: Placement\n cleared: int\n"
},
"src/stackcraft/players/__init__.py": {
"sha256": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
"utf8": "\"\"\"Players receive visible state only; episode seeds stay in the runner.\"\"\"\n\nimport math\nimport random\nfrom dataclasses import dataclass\nfrom typing import Protocol\n\nfrom stackcraft.engine import legal_actions, place\nfrom stackcraft.schema import HEIGHT, RULES_VERSION, WIDTH, Board, GameState, Placement\n\n\n@dataclass(frozen=True)\nclass Observation:\n board: Board\n current: str\n next_piece: str\n legal_actions: tuple[Placement, ...]\n rules_version: str = RULES_VERSION\n\n\n@dataclass(frozen=True)\nclass Decision:\n action_id: str\n probabilities: dict[str, float] | None = None\n\n\nclass Player(Protocol):\n name: str\n revision: str\n\n def choose(self, observation: Observation) -> Decision: ...\n\n\ndef observe(state: GameState) -> Observation:\n \"\"\"Do not expose seed, piece index, or any hidden random-generator state.\"\"\"\n return Observation(state.board, state.current, state.next_piece, legal_actions(state))\n\n\ndef validate_decision(decision: Decision, observation: Observation) -> None:\n if not isinstance(decision, Decision):\n raise ValueError(\"player must return a Decision\")\n ids = {action.id for action in observation.legal_actions}\n if not isinstance(decision.action_id, str) or decision.action_id not in ids:\n raise ValueError(\"player selected an illegal action\")\n probabilities = decision.probabilities\n if probabilities is None:\n return\n if not isinstance(probabilities, dict) or probabilities.keys() != ids:\n raise ValueError(\"probabilities must cover every legal action exactly\")\n if any(\n type(value) not in (int, float) or not math.isfinite(value) or not 0 <= value <= 1\n for value in probabilities.values()\n ):\n raise ValueError(\"probabilities must be finite numbers between zero and one\")\n if not math.isclose(sum(probabilities.values()), 1.0, rel_tol=0, abs_tol=1e-6):\n raise ValueError(\"probabilities must sum to one\")\n\n\nclass RandomPlayer:\n \"\"\"Uniform legal placement with an RNG independent of the piece generator.\"\"\"\n\n name = \"random\"\n\n def __init__(self, seed: int = 0) -> None:\n if type(seed) is not int:\n raise ValueError(\"player RNG seed must be an integer\")\n self.revision = f\"random-v1-rng-{seed}\"\n self._rng = random.Random(seed)\n\n def choose(self, observation: Observation) -> Decision:\n if not observation.legal_actions:\n raise ValueError(\"cannot choose with no legal actions\")\n actions = observation.legal_actions\n return Decision(\n self._rng.choice(actions).id,\n {action.id: 1.0 / len(actions) for action in actions},\n )\n\n\n@dataclass(frozen=True)\nclass BoardFeatures:\n aggregate_height: int\n holes: int\n bumpiness: int\n\n\ndef board_features(board: Board) -> BoardFeatures:\n heights = []\n holes = 0\n for x in range(WIDTH):\n first = next((y for y in range(HEIGHT) if board[y][x]), HEIGHT)\n heights.append(HEIGHT - first)\n holes += sum(board[y][x] == 0 for y in range(first, HEIGHT))\n return BoardFeatures(\n sum(heights), holes, sum(abs(a - b) for a, b in zip(heights, heights[1:], strict=False))\n )\n\n\ndef board_value(board: Board, cleared: int) -> int:\n \"\"\"Fixed, untuned v1 weights: -height -4*holes -bumpiness +8*lines.\"\"\"\n features = board_features(board)\n return -features.aggregate_height - 4 * features.holes - features.bumpiness + 8 * cleared\n\n\nclass HeuristicPlayer:\n \"\"\"Greedy afterboard value; ties use the engine's rotation/column order.\"\"\"\n\n name = \"heuristic\"\n revision = \"heuristic-v1-height1-holes4-bump1-lines8\"\n\n def choose(self, observation: Observation) -> Decision:\n if not observation.legal_actions:\n raise ValueError(\"cannot choose with no legal actions\")\n action = max(\n observation.legal_actions,\n key=lambda action: board_value(*place(observation.board, observation.current, action)),\n )\n return Decision(action.id)\n"
},
"uv.lock": {
"sha256": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8",
"utf8": "version = 1\nrevision = 5\nrequires-python = \"==3.13.*\"\nresolution-markers = [\n \"sys_platform == 'linux'\",\n \"sys_platform != 'linux'\",\n]\n\n[[package]]\nname = \"accelerate\"\nversion = \"1.15.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"huggingface-hub\" },\n { name = \"numpy\" },\n { name = \"packaging\" },\n { name = \"psutil\" },\n { name = \"pyyaml\" },\n { name = \"safetensors\" },\n { name = \"torch\", version = \"2.14.1\", source = { registry = \"https://pypi.org/simple\" }, marker = \"sys_platform != 'linux'\" },\n { name = \"torch\", version = \"2.14.1+cu130\", source = { registry = \"https://download.pytorch.org/whl/cu130\" }, marker = \"sys_platform == 'linux'\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/f5/b5/1d3ed029ac71d3f2961346829a268da923698e9fd63f218f78841f216bfd/accelerate-1.15.0.tar.gz\", hash = \"sha256:5654f8c5eaa0d4fa68b33e287a97765da6849bf6d51dcac874e73fbbddfb6134\", size = 422615, upload-time = \"2026-09-09T13:04:49.078Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/8a/4c/34f0450479d01195027260da68d8a3880683f1640c3ca5adf64acb3185f1/accelerate-1.15.0-py3-none-any.whl\", hash = \"sha256:97eacca0b73e45cb867dbf8c5d5d4dc32219544300e0c8992c7334dc2ef33cec\", size = 394295, upload-time = \"2026-09-09T13:04:47.331Z\" },\n]\n\n[[package]]\nname = \"annotated-doc\"\nversion = \"0.0.5\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz\", hash = \"sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb\", size = 10758, upload-time = \"2026-07-28T13:50:58.129Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl\", hash = \"sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101\", size = 5302, upload-time = \"2026-07-28T13:50:57.239Z\" },\n]\n\n[[package]]\nname = \"annotated-types\"\nversion = \"0.8.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz\", hash = \"sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7\", size = 15893, upload-time = \"2026-07-23T20:16:13.995Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl\", hash = \"sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0\", size = 13427, upload-time = \"2026-07-23T20:16:12.938Z\" },\n]\n\n[[package]]\nname = \"anyio\"\nversion = \"4.15.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"idna\" },\n { name = \"typing-extensions\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz\", hash = \"sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94\", size = 276966, upload-time = \"2026-09-05T10:42:39.44Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl\", hash = \"sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101\", size = 132079, upload-time = \"2026-09-05T10:42:37.923Z\" },\n]\n\n[[package]]\nname = \"certifi\"\nversion = \"2026.7.22\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz\", hash = \"sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55\", size = 138112, upload-time = \"2026-07-22T03:35:12.644Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl\", hash = \"sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775\", size = 136983, upload-time = \"2026-07-22T03:35:11.276Z\" },\n]\n\n[[package]]\nname = \"click\"\nversion = \"8.5.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz\", hash = \"sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34\", size = 382235, upload-time = \"2026-08-26T13:33:14.56Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl\", hash = \"sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360\", size = 125251, upload-time = \"2026-08-26T13:33:12.928Z\" },\n]\n\n[[package]]\nname = \"colorama\"\nversion = \"0.4.6\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { 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\" }\nwheels = [\n { 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\" },\n]\n\n[[package]]\nname = \"cuda-bindings\"\nversion = \"13.4.3\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"cuda-pathfinder\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/f8/a9/c83eb5aa055a4b0c3776d83f6f88b9e778a6fe0415210977c889c6a0bb8a/cuda_bindings-13.4.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:d7c6c9f46fca7f3fc61959ef9a2398ac656172145b43f408e0a6492360cf1c0c\", size = 6316533, upload-time = \"2026-09-23T02:22:09.694Z\" },\n { url = \"https://files.pythonhosted.org/packages/8a/24/9c01edfd2210737ee9471b47db857a079e5a23f2677e5d9778c0ff23d099/cuda_bindings-13.4.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:1fd7d8459b364aedc11f3e59703453ced823135f78a9111ca70feef8d56d4d21\", size = 6929124, upload-time = \"2026-09-23T02:22:11.765Z\" },\n]\n\n[[package]]\nname = \"cuda-pathfinder\"\nversion = \"1.8.3\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/b9/fb/f8e1890428f9f590b4beebd63b068aac1ce32a3331510c847b9f9a78f261/cuda_pathfinder-1.8.3-py3-none-any.whl\", hash = \"sha256:e29e59829c297a7a5233bd9cc71094fc5bddbd076951482670178f9eade39b1f\", size = 62561, upload-time = \"2026-10-02T03:20:23.712Z\" },\n]\n\n[[package]]\nname = \"cuda-toolkit\"\nversion = \"13.0.3.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl\", hash = \"sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f\", size = 2512, upload-time = \"2026-04-14T00:50:08.173Z\" },\n]\n\n[package.optional-dependencies]\ncublas = [\n { name = \"nvidia-cublas\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-cuda-nvrtc\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncudart = [\n { name = \"nvidia-cuda-runtime\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncufft = [\n { name = \"nvidia-cufft\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-nvjitlink\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncufile = [\n { name = \"nvidia-cufile\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncupti = [\n { name = \"nvidia-cuda-cupti\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncurand = [\n { name = \"nvidia-curand\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncusolver = [\n { name = \"nvidia-cublas\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-cusolver\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-cusparse\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-nvjitlink\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\ncusparse = [\n { name = \"nvidia-cusparse\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n { name = \"nvidia-nvjitlink\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\nnvjitlink = [\n { name = \"nvidia-nvjitlink\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\nnvrtc = [\n { name = \"nvidia-cuda-nvrtc\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\nnvtx = [\n { name = \"nvidia-nvtx\", marker = \"platform_machine == 'aarch64' or platform_machine == 'x86_64'\" },\n]\n\n[[package]]\nname = \"fastapi\"\nversion = \"0.142.2\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"annotated-doc\" },\n { name = \"opentelemetry-api\" },\n { name = \"pydantic\" },\n { name = \"starlette\" },\n { name = \"typing-extensions\" },\n { name = \"typing-inspection\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/56/4f/f7c30a73127e0a8bbffe788369b8359e530b01ae06e2757936fa35bc5e6d/fastapi-0.142.2.tar.gz\", hash = \"sha256:06366626f2e70576367714d9ab2fe8472e6c8456dba69b399f9f797ab5e92570\", size = 466721, upload-time = \"2026-09-30T09:51:11.277Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/a0/b6/78aaf9141fb46742928c113f3cf6ef2259d538cb02b604b7656c1dc9883c/fastapi-0.142.2-py3-none-any.whl\", hash = \"sha256:bd5f4d81f1e93a88bcd77caf4dfe3c2dbffc3805407a0007e9a114c18b3a670b\", size = 144409, upload-time = \"2026-09-30T09:51:09.8Z\" },\n]\n\n[[package]]\nname = \"filelock\"\nversion = \"4.0.12\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/53/e4/34efcb869715cf299e47d1ac7b2624d2bcb6f2d3dffc2f0abe8417f65ab2/filelock-4.0.12.tar.gz\", hash = \"sha256:cf42711a7ac791818b299fab0332a088c65aeeefa36290de98db92c434303b0c\", size = 253880, upload-time = \"2026-10-05T13:17:43.677Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/da/a1/b047e1c486a2c66283633b7dbb73872ca16ca1ce6a426f70d4d037fc8e1c/filelock-4.0.12-py3-none-any.whl\", hash = \"sha256:5f17ee83ecee8a6f3e389c75822fb70a1c2f0506b99438a6dffa1d56793588c8\", size = 111732, upload-time = \"2026-10-05T13:17:42.361Z\" },\n]\n\n[[package]]\nname = \"fsspec\"\nversion = \"2026.9.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/77/cd/9be253869fc42e764de7f3dedd6969af7d44ff9c3375214a3442a6f3fc08/fsspec-2026.9.0.tar.gz\", hash = \"sha256:0f08147951c8cb31d844c3547d631053b127863b60be04cf06e121333ee0e2fe\", size = 333545, upload-time = \"2026-09-18T17:50:42.825Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/6c/c0/a98505f18594f1bce828bb159cec0fcf9860562f1a2c85913409fc8f3d9e/fsspec-2026.9.0-py3-none-any.whl\", hash = \"sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f\", size = 221738, upload-time = \"2026-09-18T17:50:41.341Z\" },\n]\n\n[[package]]\nname = \"h11\"\nversion = \"0.16.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { 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\" }\nwheels = [\n { 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\" },\n]\n\n[[package]]\nname = \"hf-xet\"\nversion = \"1.6.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz\", hash = \"sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef\", size = 920527, upload-time = \"2026-08-03T22:33:13.243Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl\", hash = \"sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d\", size = 4071729, upload-time = \"2026-08-03T22:33:00.721Z\" },\n { url = \"https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl\", hash = \"sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a\", size = 3876287, upload-time = \"2026-08-03T22:33:02.251Z\" },\n { url = \"https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f\", size = 4464663, upload-time = \"2026-08-03T22:33:03.802Z\" },\n { url = \"https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl\", hash = \"sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7\", size = 4262538, upload-time = \"2026-08-03T22:33:05.287Z\" },\n { url = \"https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl\", hash = \"sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb\", size = 4460520, upload-time = \"2026-08-03T22:33:06.81Z\" },\n { url = \"https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl\", hash = \"sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c\", size = 4675937, upload-time = \"2026-08-03T22:33:08.616Z\" },\n { url = \"https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl\", hash = \"sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b\", size = 4033128, upload-time = \"2026-08-03T22:33:10.171Z\" },\n { url = \"https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl\", hash = \"sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3\", size = 3859359, upload-time = \"2026-08-03T22:33:11.725Z\" },\n]\n\n[[package]]\nname = \"httpcore\"\nversion = \"1.0.9\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"certifi\" },\n { name = \"h11\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz\", hash = \"sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8\", size = 85484, upload-time = \"2025-04-24T22:06:22.219Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl\", hash = \"sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55\", size = 78784, upload-time = \"2025-04-24T22:06:20.566Z\" },\n]\n\n[[package]]\nname = \"httpcore2\"\nversion = \"2.13.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"h11\" },\n { name = \"truststore\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz\", hash = \"sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103\", size = 68071, upload-time = \"2026-09-23T07:47:22.372Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl\", hash = \"sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a\", size = 83423, upload-time = \"2026-09-23T07:47:19.365Z\" },\n]\n\n[[package]]\nname = \"httpx\"\nversion = \"0.28.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"anyio\" },\n { name = \"certifi\" },\n { name = \"httpcore\" },\n { name = \"idna\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz\", hash = \"sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc\", size = 141406, upload-time = \"2024-12-06T15:37:23.222Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl\", hash = \"sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad\", size = 73517, upload-time = \"2024-12-06T15:37:21.509Z\" },\n]\n\n[[package]]\nname = \"httpx2\"\nversion = \"2.13.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"anyio\", marker = \"sys_platform != 'emscripten'\" },\n { name = \"httpcore2\", marker = \"sys_platform != 'emscripten'\" },\n { name = \"httpx2-jsfetch\", marker = \"sys_platform == 'emscripten'\" },\n { name = \"idna\" },\n { name = \"truststore\", marker = \"sys_platform != 'emscripten'\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz\", hash = \"sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa\", size = 100405, upload-time = \"2026-09-23T07:47:23.052Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl\", hash = \"sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4\", size = 95597, upload-time = \"2026-09-23T07:47:20.995Z\" },\n]\n\n[[package]]\nname = \"httpx2-jsfetch\"\nversion = \"1.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz\", hash = \"sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60\", size = 6872, upload-time = \"2026-08-07T00:13:07.492Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl\", hash = \"sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32\", size = 6382, upload-time = \"2026-08-07T00:13:06.567Z\" },\n]\n\n[[package]]\nname = \"huggingface-hub\"\nversion = \"1.33.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"click\" },\n { name = \"filelock\" },\n { name = \"fsspec\" },\n { name = \"hf-xet\", marker = \"platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'\" },\n { name = \"httpx\" },\n { name = \"packaging\" },\n { name = \"pyyaml\" },\n { name = \"tqdm\" },\n { name = \"typing-extensions\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/25/2a/484d112c0d8fc5f665d7b65137ac9cdb2953c982391598c3597968a12ee7/huggingface_hub-1.33.0.tar.gz\", hash = \"sha256:367be21a201db9523eddf8aeac7048f2602c1b308691c97640d5e72ed188007e\", size = 1043548, upload-time = \"2026-09-24T09:49:29.971Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/fc/16/963096d224b80909432dc16561a615fd33d2d13beef3ce4c63fa25e40867/huggingface_hub-1.33.0-py3-none-any.whl\", hash = \"sha256:04e434b06e100eddbce9a6e817d72693a7884b10a79bd67ab48080d5c07eb899\", size = 846435, upload-time = \"2026-09-24T09:49:28.059Z\" },\n]\n\n[[package]]\nname = \"idna\"\nversion = \"3.20\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz\", hash = \"sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44\", size = 216463, upload-time = \"2026-09-17T14:11:04.752Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl\", hash = \"sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c\", size = 69583, upload-time = \"2026-09-17T14:11:03.168Z\" },\n]\n\n[[package]]\nname = \"iniconfig\"\nversion = \"2.3.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz\", hash = \"sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730\", size = 20503, upload-time = \"2025-10-18T21:55:43.219Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl\", hash = \"sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12\", size = 7484, upload-time = \"2025-10-18T21:55:41.639Z\" },\n]\n\n[[package]]\nname = \"jinja2\"\nversion = \"3.1.6\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"markupsafe\" },\n]\nsdist = { 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\" }\nwheels = [\n { 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\" },\n]\n\n[[package]]\nname = \"markdown-it-py\"\nversion = \"4.2.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"mdurl\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz\", hash = \"sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49\", size = 82454, upload-time = \"2026-05-07T12:08:28.36Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl\", hash = \"sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a\", size = 91687, upload-time = \"2026-05-07T12:08:27.182Z\" },\n]\n\n[[package]]\nname = \"markupsafe\"\nversion = \"3.0.4\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz\", hash = \"sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6\", size = 153777, upload-time = \"2026-10-02T23:07:22.29Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/6d/18/4bc5ba32499e87bb2b0ef5b3a9bb9c00a131fa961ddf0be548cb550f548b/markupsafe-3.0.4-cp313-cp313-android_24_arm64_v8a.whl\", hash = \"sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1\", size = 12786, upload-time = \"2026-10-02T23:05:08.709Z\" },\n { url = \"https://files.pythonhosted.org/packages/4e/6f/17f0c099bf25f3e31e63cc19244d9f6af861a9a4ab778c203997903cfdd0/markupsafe-3.0.4-cp313-cp313-android_24_x86_64.whl\", hash = \"sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1\", size = 12510, upload-time = \"2026-10-02T23:05:09.93Z\" },\n { url = \"https://files.pythonhosted.org/packages/11/af/1a141081b905036ee904ec4bd945e1f70b4e1b32d33c4e59e8cf1d58b247/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphoneos.whl\", hash = \"sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96\", size = 11583, upload-time = \"2026-10-02T23:05:10.884Z\" },\n { url = \"https://files.pythonhosted.org/packages/e7/0a/a89385ae590232622a03e091805cff12f24fabe6c11e0e8bae096cece81c/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl\", hash = \"sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148\", size = 11801, upload-time = \"2026-10-02T23:05:11.913Z\" },\n { url = \"https://files.pythonhosted.org/packages/ed/85/ea548dc013962eb73653124bc595635fbf9e0fa41d1f181a967ccb784dfb/markupsafe-3.0.4-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl\", hash = \"sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e\", size = 11443, upload-time = \"2026-10-02T23:05:12.887Z\" },\n { url = \"https://files.pythonhosted.org/packages/cc/72/15f2e5ec9cf2eb00d5cdfe968d94e4156a7bd7303832c3f3b2c403a36839/markupsafe-3.0.4-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248\", size = 11665, upload-time = \"2026-10-02T23:05:13.829Z\" },\n { url = \"https://files.pythonhosted.org/packages/ca/e0/4030bea613677e333c8a2c901fd405055f657f9d06acba5b7357984b6ef7/markupsafe-3.0.4-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72\", size = 12061, upload-time = \"2026-10-02T23:05:14.807Z\" },\n { url = \"https://files.pythonhosted.org/packages/f3/a5/28b76a7449eb702966b88bef599e2360b411fbb3afeee8fe560939be06ec/markupsafe-3.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2\", size = 24395, upload-time = \"2026-10-02T23:05:15.909Z\" },\n { url = \"https://files.pythonhosted.org/packages/07/6c/21232811afc3a063b5e934b1ae2efda52f46154ec382f585149c020e61fe/markupsafe-3.0.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85\", size = 27183, upload-time = \"2026-10-02T23:05:16.976Z\" },\n { url = \"https://files.pythonhosted.org/packages/14/38/6ccdfa5b59049cb36fb80cbc80aee9cf1fc9bb77d1335ad435f2070b08cf/markupsafe-3.0.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde\", size = 24772, upload-time = \"2026-10-02T23:05:18.209Z\" },\n { url = \"https://files.pythonhosted.org/packages/63/e0/cec6865dfe88cb48fedd4b20aed6af5158e41092adcbf3e028bcc6ec2108/markupsafe-3.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6\", size = 23017, upload-time = \"2026-10-02T23:05:19.286Z\" },\n { url = \"https://files.pythonhosted.org/packages/ee/76/6ed4940bb7648a9aac457c14f870cfdd5105f139a0fb1f29cd61fafa47d1/markupsafe-3.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f\", size = 22036, upload-time = \"2026-10-02T23:05:20.352Z\" },\n { url = \"https://files.pythonhosted.org/packages/a1/4f/ed476226d4fe46a09090a36025bf319296810028df55eb12f1253b540f3a/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39\", size = 23786, upload-time = \"2026-10-02T23:05:21.576Z\" },\n { url = \"https://files.pythonhosted.org/packages/9a/35/66ff30450e35ef5fba9ebc930c9411747e537fd9447b65e44f5007e2b84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_armv7l.whl\", hash = \"sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee\", size = 22672, upload-time = \"2026-10-02T23:05:22.922Z\" },\n { url = \"https://files.pythonhosted.org/packages/32/0b/72f45ce4b4efcbca4b80cf1b06703eff0be8d37e82abb78f66c85a7ead1e/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_ppc64le.whl\", hash = \"sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2\", size = 24107, upload-time = \"2026-10-02T23:05:24.175Z\" },\n { url = \"https://files.pythonhosted.org/packages/d2/03/71776e5fdcba04614b384cc102e8a4198208579d896fd1394cb7cb9aa900/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_riscv64.whl\", hash = \"sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46\", size = 21615, upload-time = \"2026-10-02T23:05:25.215Z\" },\n { url = \"https://files.pythonhosted.org/packages/ab/5f/801ce02a02e7aee0f784b1ec7843026178f6adeb9c93ac67eb1992a9a84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17\", size = 23076, upload-time = \"2026-10-02T23:05:26.423Z\" },\n { url = \"https://files.pythonhosted.org/packages/4a/85/c43776625428f3bb4a61e8633940400e3efe6409e3c6f5bff26de5e45618/markupsafe-3.0.4-cp313-cp313-win32.whl\", hash = \"sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0\", size = 14118, upload-time = \"2026-10-02T23:05:27.716Z\" },\n { url = \"https://files.pythonhosted.org/packages/6f/36/163da64de88a13db79214ef75fa041be7fa13bdb42261cf5b7484de14bfb/markupsafe-3.0.4-cp313-cp313-win_amd64.whl\", hash = \"sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5\", size = 14313, upload-time = \"2026-10-02T23:05:28.749Z\" },\n { url = \"https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl\", hash = \"sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc\", size = 14189, upload-time = \"2026-10-02T23:05:29.917Z\" },\n]\n\n[[package]]\nname = \"mdurl\"\nversion = \"0.1.2\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz\", hash = \"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba\", size = 8729, upload-time = \"2022-08-14T12:40:10.846Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl\", hash = \"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8\", size = 9979, upload-time = \"2022-08-14T12:40:09.779Z\" },\n]\n\n[[package]]\nname = \"mpmath\"\nversion = \"1.3.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz\", hash = \"sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f\", size = 508106, upload-time = \"2023-03-07T16:47:11.061Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl\", hash = \"sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c\", size = 536198, upload-time = \"2023-03-07T16:47:09.197Z\" },\n]\n\n[[package]]\nname = \"networkx\"\nversion = \"3.7\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz\", hash = \"sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a\", size = 1866482, upload-time = \"2026-09-21T16:45:16.974Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl\", hash = \"sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0\", size = 2142205, upload-time = \"2026-09-21T16:45:14.609Z\" },\n]\n\n[[package]]\nname = \"numpy\"\nversion = \"2.5.3\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz\", hash = \"sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563\", size = 20791231, upload-time = \"2026-09-06T16:27:47.073Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034\", size = 16991061, upload-time = \"2026-09-06T16:25:00.411Z\" },\n { url = \"https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e\", size = 12003676, upload-time = \"2026-09-06T16:25:03.475Z\" },\n { url = \"https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl\", hash = \"sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09\", size = 5439695, upload-time = \"2026-09-06T16:25:05.978Z\" },\n { url = \"https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl\", hash = \"sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958\", size = 6779395, upload-time = \"2026-09-06T16:25:08.599Z\" },\n { url = \"https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b\", size = 15681750, upload-time = \"2026-09-06T16:25:11.173Z\" },\n { url = \"https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321\", size = 16708577, upload-time = \"2026-09-06T16:25:14.171Z\" },\n { url = \"https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231\", size = 17042047, upload-time = \"2026-09-06T16:25:17.582Z\" },\n { url = \"https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0\", size = 18465724, upload-time = \"2026-09-06T16:25:20.949Z\" },\n { url = \"https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl\", hash = \"sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f\", size = 6129003, upload-time = \"2026-09-06T16:25:23.664Z\" },\n { url = \"https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl\", hash = \"sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec\", size = 12560965, upload-time = \"2026-09-06T16:25:26.602Z\" },\n { url = \"https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl\", hash = \"sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204\", size = 10482343, upload-time = \"2026-09-06T16:25:29.772Z\" },\n]\n\n[[package]]\nname = \"nvidia-cublas\"\nversion = \"13.1.1.3\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"nvidia-cuda-nvrtc\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl\", hash = \"sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5\", size = 542848918, upload-time = \"2026-04-08T18:46:22.985Z\" },\n { url = \"https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl\", hash = \"sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436\", size = 423138758, upload-time = \"2026-04-08T18:46:58.655Z\" },\n]\n\n[[package]]\nname = \"nvidia-cuda-cupti\"\nversion = \"13.0.85\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl\", hash = \"sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151\", size = 10310827, upload-time = \"2025-09-04T08:26:42.012Z\" },\n { url = \"https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl\", hash = \"sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8\", size = 10715597, upload-time = \"2025-09-04T08:26:51.312Z\" },\n]\n\n[[package]]\nname = \"nvidia-cuda-nvrtc\"\nversion = \"13.0.88\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl\", hash = \"sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575\", size = 90215200, upload-time = \"2025-09-04T08:28:44.204Z\" },\n { url = \"https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b\", size = 43015449, upload-time = \"2025-09-04T08:28:20.239Z\" },\n]\n\n[[package]]\nname = \"nvidia-cuda-runtime\"\nversion = \"13.0.96\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55\", size = 2261060, upload-time = \"2025-10-09T08:55:15.78Z\" },\n { url = \"https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548\", size = 2243632, upload-time = \"2025-10-09T08:55:36.117Z\" },\n]\n\n[[package]]\nname = \"nvidia-cudnn-cu13\"\nversion = \"9.24.0.43\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"nvidia-cublas\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl\", hash = \"sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f\", size = 650976863, upload-time = \"2026-07-02T16:23:39.248Z\" },\n { url = \"https://files.pythonhosted.org/packages/5c/ba/791cffd048fe5b044e620df55267e3e95c0e6e07d50b41e377c03dfc910f/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_x86_64.whl\", hash = \"sha256:71f181cd810e90f9b6023b01186fe82d13d65f0ec098581ee201d39fad769e4b\", size = 553099438, upload-time = \"2026-07-02T16:27:42.58Z\" },\n]\n\n[[package]]\nname = \"nvidia-cufft\"\nversion = \"12.0.0.61\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"nvidia-nvjitlink\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5\", size = 214053554, upload-time = \"2025-09-04T08:31:38.196Z\" },\n { url = \"https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3\", size = 214085489, upload-time = \"2025-09-04T08:31:56.044Z\" },\n]\n\n[[package]]\nname = \"nvidia-cufile\"\nversion = \"1.15.1.6\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44\", size = 1223672, upload-time = \"2025-09-04T08:32:22.779Z\" },\n { url = \"https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl\", hash = \"sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1\", size = 1136992, upload-time = \"2025-09-04T08:32:14.119Z\" },\n]\n\n[[package]]\nname = \"nvidia-curand\"\nversion = \"10.4.0.35\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl\", hash = \"sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a\", size = 61962106, upload-time = \"2025-08-04T10:21:41.128Z\" },\n { url = \"https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl\", hash = \"sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc\", size = 59544258, upload-time = \"2025-08-04T10:22:03.992Z\" },\n]\n\n[[package]]\nname = \"nvidia-cusolver\"\nversion = \"12.0.4.66\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"nvidia-cublas\" },\n { name = \"nvidia-cusparse\" },\n { name = \"nvidia-nvjitlink\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl\", hash = \"sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2\", size = 223467760, upload-time = \"2025-09-04T08:33:04.222Z\" },\n { url = \"https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl\", hash = \"sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112\", size = 200941980, upload-time = \"2025-09-04T08:33:22.767Z\" },\n]\n\n[[package]]\nname = \"nvidia-cusparse\"\nversion = \"12.6.3.3\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"nvidia-nvjitlink\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c\", size = 162155568, upload-time = \"2025-09-04T08:33:42.864Z\" },\n { url = \"https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b\", size = 145942937, upload-time = \"2025-09-04T08:33:58.029Z\" },\n]\n\n[[package]]\nname = \"nvidia-cusparselt-cu13\"\nversion = \"0.8.1\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl\", hash = \"sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f\", size = 221051344, upload-time = \"2025-09-05T18:49:51.289Z\" },\n { url = \"https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl\", hash = \"sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0\", size = 170148586, upload-time = \"2025-09-05T18:50:50.248Z\" },\n]\n\n[[package]]\nname = \"nvidia-nccl-cu13\"\nversion = \"2.30.7\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/d1/21/a73174c6157101bdf1ffc22b517f76ff0082613989dd9bc8f43e8034caac/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_aarch64.whl\", hash = \"sha256:ca786ffa5a647c75d4d1f5cc72a6c4f537947e2ba8823d7c8aaf768e7a7b9f77\", size = 215983881, upload-time = \"2026-06-09T03:23:15.633Z\" },\n { url = \"https://files.pythonhosted.org/packages/3f/34/c500f90c7ae641b8e0f98965b36b8a7ac79cc8b296e8d251fe3eb592ee54/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_x86_64.whl\", hash = \"sha256:cefa7fdb9710efd0f39c5f1be1d61ff6fc9a996c451265bd7fbdcf9455ed4b50\", size = 215965170, upload-time = \"2026-06-09T03:23:39.73Z\" },\n]\n\n[[package]]\nname = \"nvidia-nvjitlink\"\nversion = \"13.4.92\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/1d/6b/eef7a9e32872b8f41e145bf10cddc9af26e153c338852811fe9a9baddf9e/nvidia_nvjitlink-13.4.92-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl\", hash = \"sha256:e0391f24ed94ec879b84e3da4d4ec320c879aff681f2c7a638462f7199284323\", size = 42452378, upload-time = \"2026-09-16T20:45:29.042Z\" },\n { url = \"https://files.pythonhosted.org/packages/1f/a8/1cbd4014898af8b419e69b0d7dbc63da2121ee92d92b47d59f4fe9075349/nvidia_nvjitlink-13.4.92-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:25f74fad0d654271c921ac4dca614bd6258bc21791242fc7b2289dad7ae9c099\", size = 40420120, upload-time = \"2026-09-16T20:45:19.163Z\" },\n]\n\n[[package]]\nname = \"nvidia-nvshmem-cu13\"\nversion = \"3.4.5\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9\", size = 60215947, upload-time = \"2025-09-06T00:32:20.022Z\" },\n { url = \"https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl\", hash = \"sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80\", size = 60412546, upload-time = \"2025-09-06T00:32:41.564Z\" },\n]\n\n[[package]]\nname = \"nvidia-nvtx\"\nversion = \"13.0.85\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl\", hash = \"sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4\", size = 148047, upload-time = \"2025-09-04T08:29:01.761Z\" },\n { url = \"https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl\", hash = \"sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6\", size = 148878, upload-time = \"2025-09-04T08:28:53.627Z\" },\n]\n\n[[package]]\nname = \"opentelemetry-api\"\nversion = \"1.45.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"typing-extensions\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/1f/dc/e12c1fe1ed8a7b7149777127b1a0e12ce5bd5a81d97408bedc2128c260f5/opentelemetry_api-1.45.0.tar.gz\", hash = \"sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7\", size = 72115, upload-time = \"2026-09-25T12:32:28.399Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/44/b9/040d1a1c7836922828e6480cd2366bb8fe0ebf75b413d2bb51a9b0e7f78f/opentelemetry_api-1.45.0-py3-none-any.whl\", hash = \"sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3\", size = 60020, upload-time = \"2026-09-25T12:32:03.192Z\" },\n]\n\n[[package]]\nname = \"packaging\"\nversion = \"26.3\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz\", hash = \"sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79\", size = 313412, upload-time = \"2026-08-04T18:15:28.737Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl\", hash = \"sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c\", size = 129956, upload-time = \"2026-08-04T18:15:27.159Z\" },\n]\n\n[[package]]\nname = \"peft\"\nversion = \"0.21.2\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"accelerate\" },\n { name = \"huggingface-hub\" },\n { name = \"numpy\" },\n { name = \"packaging\" },\n { name = \"psutil\" },\n { name = \"pyyaml\" },\n { name = \"safetensors\" },\n { name = \"torch\", version = \"2.14.1\", source = { registry = \"https://pypi.org/simple\" }, marker = \"sys_platform != 'linux'\" },\n { name = \"torch\", version = \"2.14.1+cu130\", source = { registry = \"https://download.pytorch.org/whl/cu130\" }, marker = \"sys_platform == 'linux'\" },\n { name = \"tqdm\" },\n { name = \"transformers\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/80/af/2e08abf1cd3b8792a02f5116808f2398c2f77ecdff801ced2ce16007a6f9/peft-0.21.2.tar.gz\", hash = \"sha256:b803ccfb3f3f316004d850284306687833a2235ea278fb56abc856203456142e\", size = 983614, upload-time = \"2026-10-01T10:26:36.129Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/70/0b/59441cdbfdd342ed03c08af90a2fe16173f0cd48ab219f523b39ca059a79/peft-0.21.2-py3-none-any.whl\", hash = \"sha256:106ab6077ff72c54d21577f9af5970e34bac7582cb14209f7b2b511e322a4eae\", size = 835427, upload-time = \"2026-10-01T10:26:34.085Z\" },\n]\n\n[[package]]\nname = \"pillow\"\nversion = \"12.3.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz\", hash = \"sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce\", size = 47025035, upload-time = \"2026-07-01T11:56:38.965Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl\", hash = \"sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89\", size = 4161684, upload-time = \"2026-07-01T11:54:25.934Z\" },\n { url = \"https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl\", hash = \"sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace\", size = 4255487, upload-time = \"2026-07-01T11:54:27.935Z\" },\n { url = \"https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl\", hash = \"sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec\", size = 3696433, upload-time = \"2026-07-01T11:54:29.813Z\" },\n { url = \"https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66\", size = 5345889, upload-time = \"2026-07-01T11:54:31.97Z\" },\n { url = \"https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35\", size = 4780109, upload-time = \"2026-07-01T11:54:34.026Z\" },\n { url = \"https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65\", size = 6263736, upload-time = \"2026-07-01T11:54:36.131Z\" },\n { url = \"https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3\", size = 6937129, upload-time = \"2026-07-01T11:54:38.216Z\" },\n { url = \"https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a\", size = 6339562, upload-time = \"2026-07-01T11:54:40.354Z\" },\n { url = \"https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e\", size = 7049439, upload-time = \"2026-07-01T11:54:42.489Z\" },\n { url = \"https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl\", hash = \"sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f\", size = 6473287, upload-time = \"2026-07-01T11:54:44.9Z\" },\n { url = \"https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl\", hash = \"sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8\", size = 7239691, upload-time = \"2026-07-01T11:54:47.141Z\" },\n { url = \"https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl\", hash = \"sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b\", size = 2568185, upload-time = \"2026-07-01T11:54:49.137Z\" },\n]\n\n[[package]]\nname = \"pluggy\"\nversion = \"1.6.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz\", hash = \"sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3\", size = 69412, upload-time = \"2025-05-15T12:30:07.975Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl\", hash = \"sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746\", size = 20538, upload-time = \"2025-05-15T12:30:06.134Z\" },\n]\n\n[[package]]\nname = \"psutil\"\nversion = \"7.2.2\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz\", hash = \"sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372\", size = 493740, upload-time = \"2026-01-28T18:14:54.428Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl\", hash = \"sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b\", size = 130595, upload-time = \"2026-01-28T18:14:57.293Z\" },\n { url = \"https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl\", hash = \"sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea\", size = 131082, upload-time = \"2026-01-28T18:14:59.732Z\" },\n { url = \"https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63\", size = 181476, upload-time = \"2026-01-28T18:15:01.884Z\" },\n { url = \"https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312\", size = 184062, upload-time = \"2026-01-28T18:15:04.436Z\" },\n { url = \"https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl\", hash = \"sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b\", size = 139893, upload-time = \"2026-01-28T18:15:06.378Z\" },\n { url = \"https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl\", hash = \"sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9\", size = 135589, upload-time = \"2026-01-28T18:15:08.03Z\" },\n { url = \"https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl\", hash = \"sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486\", size = 129090, upload-time = \"2026-01-28T18:15:22.168Z\" },\n { url = \"https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl\", hash = \"sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979\", size = 129859, upload-time = \"2026-01-28T18:15:23.795Z\" },\n { url = \"https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9\", size = 155560, upload-time = \"2026-01-28T18:15:25.976Z\" },\n { url = \"https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e\", size = 156997, upload-time = \"2026-01-28T18:15:27.794Z\" },\n { url = \"https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl\", hash = \"sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8\", size = 148972, upload-time = \"2026-01-28T18:15:29.342Z\" },\n { url = \"https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl\", hash = \"sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc\", size = 148266, upload-time = \"2026-01-28T18:15:31.597Z\" },\n { url = \"https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl\", hash = \"sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988\", size = 137737, upload-time = \"2026-01-28T18:15:33.849Z\" },\n { url = \"https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl\", hash = \"sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee\", size = 134617, upload-time = \"2026-01-28T18:15:36.514Z\" },\n]\n\n[[package]]\nname = \"pydantic\"\nversion = \"2.13.5\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"annotated-types\" },\n { name = \"pydantic-core\" },\n { name = \"typing-extensions\" },\n { name = \"typing-inspection\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz\", hash = \"sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08\", size = 845750, upload-time = \"2026-08-28T14:04:00.916Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl\", hash = \"sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73\", size = 472589, upload-time = \"2026-08-28T14:03:59.136Z\" },\n]\n\n[[package]]\nname = \"pydantic-core\"\nversion = \"2.46.5\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"typing-extensions\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz\", hash = \"sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc\", size = 472262, upload-time = \"2026-08-28T10:01:31.677Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl\", hash = \"sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0\", size = 2075885, upload-time = \"2026-08-28T09:58:47.856Z\" },\n { url = \"https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff\", size = 1922768, upload-time = \"2026-08-28T09:58:49.58Z\" },\n { url = \"https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\", hash = \"sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931\", size = 1951241, upload-time = \"2026-08-28T09:58:51.511Z\" },\n { url = \"https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\", hash = \"sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f\", size = 2031975, upload-time = \"2026-08-28T09:58:53.166Z\" },\n { url = \"https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\", hash = \"sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038\", size = 2208542, upload-time = \"2026-08-28T09:58:55.017Z\" },\n { url = \"https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl\", hash = \"sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f\", size = 2264692, upload-time = \"2026-08-28T09:58:56.767Z\" },\n { url = \"https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\", hash = \"sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1\", size = 2066633, upload-time = \"2026-08-28T09:58:58.531Z\" },\n { url = \"https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl\", hash = \"sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761\", size = 2105235, upload-time = \"2026-08-28T09:59:00.421Z\" },\n { url = \"https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl\", hash = \"sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5\", size = 2157367, upload-time = \"2026-08-28T09:59:02.442Z\" },\n { url = \"https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl\", hash = \"sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e\", size = 2158420, upload-time = \"2026-08-28T09:59:04.2Z\" },\n { url = \"https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl\", hash = \"sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed\", size = 2309588, upload-time = \"2026-08-28T09:59:06.048Z\" },\n { url = \"https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl\", hash = \"sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519\", size = 2341866, upload-time = \"2026-08-28T09:59:08.539Z\" },\n { url = \"https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl\", hash = \"sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea\", size = 1938580, upload-time = \"2026-08-28T09:59:10.402Z\" },\n { url = \"https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl\", hash = \"sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5\", size = 2041980, upload-time = \"2026-08-28T09:59:12.396Z\" },\n { url = \"https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl\", hash = \"sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575\", size = 1997213, upload-time = \"2026-08-28T09:59:14.245Z\" },\n]\n\n[[package]]\nname = \"pygments\"\nversion = \"2.21.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz\", hash = \"sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c\", size = 5005329, upload-time = \"2026-08-17T08:02:48.824Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl\", hash = \"sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9\", size = 1250147, upload-time = \"2026-08-17T08:02:44.912Z\" },\n]\n\n[[package]]\nname = \"pytest\"\nversion = \"9.1.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"colorama\", marker = \"sys_platform == 'win32'\" },\n { name = \"iniconfig\" },\n { name = \"packaging\" },\n { name = \"pluggy\" },\n { name = \"pygments\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz\", hash = \"sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313\", size = 1636369, upload-time = \"2026-06-19T10:58:32.857Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl\", hash = \"sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c\", size = 386536, upload-time = \"2026-06-19T10:58:31.347Z\" },\n]\n\n[[package]]\nname = \"pyyaml\"\nversion = \"6.0.3\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { 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\" }\nwheels = [\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n { 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\" },\n]\n\n[[package]]\nname = \"regex\"\nversion = \"2026.9.29\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/fc/f2/af1da9d3ceed77bfcdce40427d49ba0be94e4fe84245e3bfef68c10e75b6/regex-2026.9.29.tar.gz\", hash = \"sha256:8b5fcc4771732191b2b7d1dd68d8f0353f47f8d90b6150f6dce58bf1112442cb\", size = 419199, upload-time = \"2026-09-29T00:49:58.298Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/79/d5/6080f7d1a6e7e36aa720f806ac93c035ba39c209ae6cc510e8ef4c0279c6/regex-2026.9.29-cp313-cp313-macosx_10_13_universal2.whl\", hash = \"sha256:f1a0d5117230dd46b399a30a38afa44f79c99f3168988fdc4f425c3f928b39df\", size = 497622, upload-time = \"2026-09-29T00:47:08.251Z\" },\n { url = \"https://files.pythonhosted.org/packages/00/71/c87fc7a2e21a42f9d57489db32951c37eef56d153840459a80d464f0321d/regex-2026.9.29-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:f0fe9834e5aeccaf19a0d8feb296d66a24be1a7c9922002f842a682cd5abb787\", size = 296281, upload-time = \"2026-09-29T00:47:09.764Z\" },\n { url = \"https://files.pythonhosted.org/packages/11/9e/aa0f4cde3bc4688c1d58b0cd8415edd708339bc0bc401a195b0b1e8c8f0c/regex-2026.9.29-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:c90fcf7804ea0a54b896ce0f2b9565350220b8d4890fd0db461a476a4c687963\", size = 293472, upload-time = \"2026-09-29T00:47:11.723Z\" },\n { url = \"https://files.pythonhosted.org/packages/90/d4/e835c487850ed922a8d6074f953b888c8ea99775c76b9ed5f8a4d72eab92/regex-2026.9.29-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:e11edba5bc344a32b029a7af9d4b3173982dd79eeafa0b9dbd787364414b0509\", size = 805966, upload-time = \"2026-09-29T00:47:13.235Z\" },\n { url = \"https://files.pythonhosted.org/packages/2c/57/ba8809847fbae8d2cbc71367c6ded510a7ec88bf52493c65efc1acf4effb/regex-2026.9.29-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:bb90e7177944b6684738c1fc36aabd2dd00d1de3be7dbe09f91e196f1bc0dc81\", size = 878346, upload-time = \"2026-09-29T00:47:14.877Z\" },\n { url = \"https://files.pythonhosted.org/packages/1a/52/e3da19fc3cc15ef67ab67e121e87887c3bccfdb683a7a9ec557c460ca5b7/regex-2026.9.29-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:d06fcdecc10fc7954d7c8f27a03c96055fe525274dc84a7b0dbdc3d6b9e03dab\", size = 919250, upload-time = \"2026-09-29T00:47:16.622Z\" },\n { url = \"https://files.pythonhosted.org/packages/9a/8e/c1ed81f55f992f6aa0b699a592a50c1ce9e6d44ff1aee2c14c0537dcef9c/regex-2026.9.29-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:d49c18f1ea294cf4adde2e5ac256e98c82ea9d708462ce4bf799dffa7cfe8a2c\", size = 807902, upload-time = \"2026-09-29T00:47:18.268Z\" },\n { url = \"https://files.pythonhosted.org/packages/ad/bc/5a6886eb470e41040e21e05b75024a18b6ebfe7ea400b72094a60f949101/regex-2026.9.29-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:3e778bfccd63075167709136afbc251c1f683758d5bf49c803c60ac3f894ce6b\", size = 783514, upload-time = \"2026-09-29T00:47:19.921Z\" },\n { url = \"https://files.pythonhosted.org/packages/cb/52/6d951d453b023c6edb880f1ba474291b53b8ce1cc438b96a9db6d791d991/regex-2026.9.29-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:686ac5350fceae63830bb98805fcb8039325bf4c06d9f6f048ff65229d5bffa5\", size = 793466, upload-time = \"2026-09-29T00:47:21.552Z\" },\n { url = \"https://files.pythonhosted.org/packages/99/b9/d5a41adc08360f5eee0dc4846c578f002366947211fc8af5a69a64ee7b9f/regex-2026.9.29-cp313-cp313-musllinux_1_2_ppc64le.whl\", hash = \"sha256:26ec4ccce55aa533fbd603d08911b01101a8fcfec987845ac3ae2c7087b2bde3\", size = 869464, upload-time = \"2026-09-29T00:47:23.276Z\" },\n { url = \"https://files.pythonhosted.org/packages/4b/32/d76c9d91f5d798e2e9e67f6f85ec4ae35445ac425f7454797311cecb80ca/regex-2026.9.29-cp313-cp313-musllinux_1_2_riscv64.whl\", hash = \"sha256:a655d34b2a6943af32401f3d94f72e9d731f6ad16285815550bf2b4ee69d420a\", size = 770278, upload-time = \"2026-09-29T00:47:25.193Z\" },\n { url = \"https://files.pythonhosted.org/packages/24/00/aeebdb540c620a0f7317f6d6fad80a47729ecf0599a24b5c34ec155351f5/regex-2026.9.29-cp313-cp313-musllinux_1_2_s390x.whl\", hash = \"sha256:0c992c19cd45058a4b92f68f139c93db168b48fb1f322c9a7cd620806afb6b51\", size = 861949, upload-time = \"2026-09-29T00:47:27.005Z\" },\n { url = \"https://files.pythonhosted.org/packages/12/62/d0314bcedfd3586197e4596931fa220260eb2385bf53184e5b9ae67db24b/regex-2026.9.29-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:ebb8912f565b8cdbbf27debfe00df04202c20e2f651b9e32767930c5eace3621\", size = 796602, upload-time = \"2026-09-29T00:47:29.233Z\" },\n { url = \"https://files.pythonhosted.org/packages/ae/c7/d5a8c13a613facb03e0fb55c1ebaaf7bb35d8e2c1abe8bef8dca809fc1d9/regex-2026.9.29-cp313-cp313-win32.whl\", hash = \"sha256:4d7d93613b01b0199961330e49cfc52d479b3d5776c56c691db31130c0a07d91\", size = 269306, upload-time = \"2026-09-29T00:47:31.14Z\" },\n { url = \"https://files.pythonhosted.org/packages/80/a7/bf93a3a6afa5f7bc16b7afb94ae581b01cae620b8ad56bd8f9572a985959/regex-2026.9.29-cp313-cp313-win_amd64.whl\", hash = \"sha256:61956f074ecd123f55adca68ee3eab46e6a07ad3f8e64e6db95dfacb444f55c4\", size = 280307, upload-time = \"2026-09-29T00:47:32.709Z\" },\n { url = \"https://files.pythonhosted.org/packages/b2/7d/388274e53605a86297f433a08102a7bbdcf9379d47683d307ccaefd88e2c/regex-2026.9.29-cp313-cp313-win_arm64.whl\", hash = \"sha256:bfc71e6d970419c1309b3640305298643e2a734cad3f7cfb6d2ddee4175ab53d\", size = 279599, upload-time = \"2026-09-29T00:47:34.674Z\" },\n]\n\n[[package]]\nname = \"rich\"\nversion = \"15.0.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"markdown-it-py\" },\n { name = \"pygments\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz\", hash = \"sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36\", size = 230680, upload-time = \"2026-04-12T08:24:00.75Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl\", hash = \"sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb\", size = 310654, upload-time = \"2026-04-12T08:24:02.83Z\" },\n]\n\n[[package]]\nname = \"ruff\"\nversion = \"0.16.10\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/c4/49/23802c45f093eb14bde54b141d2b2f058edfa63a06db7beed047308cc08f/ruff-0.16.10.tar.gz\", hash = \"sha256:eff4728c4eaae93f0955cd264d24b2ab348e74bf59986ccf282ba6dc16b3b017\", size = 4958724, upload-time = \"2026-10-01T18:03:21.697Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/2f/21/ebce22e1d90cdb2cd691026b9c6e9bec6499f0b396089481755b5d49efff/ruff-0.16.10-py3-none-linux_armv6l.whl\", hash = \"sha256:488b0fe3f3574210e5cf80d9f59b9e3ab17a127a8155de3f307b392589cfb511\", size = 10095557, upload-time = \"2026-10-01T18:02:36.072Z\" },\n { url = \"https://files.pythonhosted.org/packages/cb/98/a54de85876a8b2612bfa0d84c7b9abfb39c6a3354aee7800b09c1649b9e3/ruff-0.16.10-py3-none-macosx_10_12_x86_64.whl\", hash = \"sha256:e748ff95c934c4e978783b8e687bc174e7bd84e8ad24e3243e1ecfcda5e0282d\", size = 10340577, upload-time = \"2026-10-01T18:02:39.258Z\" },\n { url = \"https://files.pythonhosted.org/packages/9f/16/1a5a4a2657effe4806110f2b907313802f1367fcdbb29e8122766e407fab/ruff-0.16.10-py3-none-macosx_11_0_arm64.whl\", hash = \"sha256:3031a4a2e8e7b8a46f70be45f198c35a11ece509a94b80334d8d397a33c67550\", size = 9774282, upload-time = \"2026-10-01T18:02:41.79Z\" },\n { url = \"https://files.pythonhosted.org/packages/6e/fb/470085af734da396e68cd80fb0a3e7c109a459716ae59588c0f6fab8a17d/ruff-0.16.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\", hash = \"sha256:494401c86df4c4c25f69b9419605d944467ee98c42fb6ad405ef4fa40b8fb67d\", size = 9920895, upload-time = \"2026-10-01T18:02:44.458Z\" },\n { url = \"https://files.pythonhosted.org/packages/57/de/f10cffe4f88a37ea6615ec460f01bf76f0bc1477737a35d9ee61a630a78b/ruff-0.16.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\", hash = \"sha256:d203abc0ff2b773ee33d00ab8df0bb67046fbc7c07b119332f08b9b345cf8221\", size = 9902707, upload-time = \"2026-10-01T18:02:47.041Z\" },\n { url = \"https://files.pythonhosted.org/packages/ff/44/3fdcedf83ae60ef837dd239170e480dce606a9142cfa2db911afdf855fd9/ruff-0.16.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl\", hash = \"sha256:bd83d1235a5258d318477bc5b576303974cbdef5df0c01a1bff14efcc23bd12a\", size = 10619287, upload-time = \"2026-10-01T18:02:49.485Z\" },\n { url = \"https://files.pythonhosted.org/packages/c1/62/02e76a5574002153618eafbb70e159468addc72a5d2c488e65cbb4639d2d/ruff-0.16.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\", hash = \"sha256:bc2610fb269fa56dd8a68669ae470fa6272902668c0fc2ebc3aa112b2633d5b8\", size = 11339942, upload-time = \"2026-10-01T18:02:52.008Z\" },\n { url = \"https://files.pythonhosted.org/packages/6b/c4/cde27d47ad8d4126c587e608c67c46e7feac11763f606d261a1bc8a489e6/ruff-0.16.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl\", hash = \"sha256:3e70175e29cc94c26ea296c80e470180b744b7419026898e58f520c6ab32578e\", size = 10934316, upload-time = \"2026-10-01T18:02:54.811Z\" },\n { url = \"https://files.pythonhosted.org/packages/e4/03/17234145f302a645a123e8c3bb2411ecf4669fbc350de3b0d803230a1729/ruff-0.16.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\", hash = \"sha256:f33f43a864a8483eebd160e713336c8bab02c934feaff0a33cf5ccb41546d09a\", size = 10387968, upload-time = \"2026-10-01T18:02:57.497Z\" },\n { url = \"https://files.pythonhosted.org/packages/71/29/2493af60240ee7644b38a4b821f5f9c3b5a4fa3178770fe1d0c685217395/ruff-0.16.10-py3-none-manylinux_2_31_riscv64.whl\", hash = \"sha256:1dfc6f0088149fb6a362c1c446bcbb3fd2157b3852fe2fa68409276eab9ad9b3\", size = 10537906, upload-time = \"2026-10-01T18:03:00.006Z\" },\n { url = \"https://files.pythonhosted.org/packages/2e/9a/f56b28f3b143bb9e518c34fb89ab191b626bca8b70dbf8af0d2e2d473572/ruff-0.16.10-py3-none-musllinux_1_2_aarch64.whl\", hash = \"sha256:6553498afc35f580f036030795810b9e6bcea31604b0fd9e8d352795473042e3\", size = 10012938, upload-time = \"2026-10-01T18:03:03.006Z\" },\n { url = \"https://files.pythonhosted.org/packages/c2/c4/fca37362848ea4d4d80712df13632e645e7c7cfb1bdedf140699ac7a090b/ruff-0.16.10-py3-none-musllinux_1_2_armv7l.whl\", hash = \"sha256:a3b8471dea115d37f123882be852bed13403746d3a76c11de4a19ec5f9ff5a03\", size = 9897945, upload-time = \"2026-10-01T18:03:05.818Z\" },\n { url = \"https://files.pythonhosted.org/packages/7d/c7/e0bc57664d6e0c61fd22f620af260fe9662d7e1ddb320ea7f160e76ef165/ruff-0.16.10-py3-none-musllinux_1_2_i686.whl\", hash = \"sha256:92e59a70bcbd9d3a5483656da906ec28edfdacfce00afd99edb8b4e9d15644be\", size = 10333134, upload-time = \"2026-10-01T18:03:08.25Z\" },\n { url = \"https://files.pythonhosted.org/packages/21/aa/5c9f3b68737c0e4a33a1db5dfab44f7b786d96ca91a7233112ddab1e6dc2/ruff-0.16.10-py3-none-musllinux_1_2_x86_64.whl\", hash = \"sha256:7ae7375f803b5520dc9f546bed7e3a0acb70b91812e9bb4b19927de22f25b77d\", size = 10741702, upload-time = \"2026-10-01T18:03:10.775Z\" },\n { url = \"https://files.pythonhosted.org/packages/78/fa/0f9c2020dc316c53d983be011720b8157cc052b97e3f4dfa7db6d0f880a6/ruff-0.16.10-py3-none-win32.whl\", hash = \"sha256:2a12e01cb9156c10c466f63b46eaae5ecea28dfbd21b5836353ae498e7d1349a\", size = 10139176, upload-time = \"2026-10-01T18:03:13.267Z\" },\n { url = \"https://files.pythonhosted.org/packages/99/29/cfb0df9448d4d4ad48c2de029ada9ebd71baa6da983a6c77ee6c6cd0fe82/ruff-0.16.10-py3-none-win_amd64.whl\", hash = \"sha256:97f2015c92aa97105b0eab19eb5d224884399281cfc5da86a92db4ab5e7fb2ca\", size = 10584734, upload-time = \"2026-10-01T18:03:16.006Z\" },\n { url = \"https://files.pythonhosted.org/packages/fc/05/c16957eb287c3fc062e032619a25868d93d408a844b725bcf514f7a378ff/ruff-0.16.10-py3-none-win_arm64.whl\", hash = \"sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b\", size = 10366440, upload-time = \"2026-10-01T18:03:19.04Z\" },\n]\n\n[[package]]\nname = \"safetensors\"\nversion = \"0.8.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz\", hash = \"sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d\", size = 325846, upload-time = \"2026-06-09T07:52:25.563Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl\", hash = \"sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0\", size = 473568, upload-time = \"2026-06-09T07:52:18.829Z\" },\n { url = \"https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl\", hash = \"sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25\", size = 484562, upload-time = \"2026-06-09T07:52:17.518Z\" },\n { url = \"https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\", hash = \"sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235\", size = 502844, upload-time = \"2026-06-09T07:52:07.624Z\" },\n { url = \"https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\", hash = \"sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0\", size = 511823, upload-time = \"2026-06-09T07:52:09.565Z\" },\n { url = \"https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\", hash = \"sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98\", size = 633461, upload-time = \"2026-06-09T07:52:11.128Z\" },\n { url = \"https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl\", hash = \"sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358\", size = 545148, upload-time = \"2026-06-09T07:52:13.603Z\" },\n { url = \"https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\", hash = \"sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774\", size = 516040, upload-time = \"2026-06-09T07:52:16.29Z\" },\n { url = \"https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl\", hash = \"sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78\", size = 513832, upload-time = \"2026-06-09T07:52:12.37Z\" },\n { url = \"https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl\", hash = \"sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca\", size = 559930, upload-time = \"2026-06-09T07:52:14.743Z\" },\n { url = \"https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl\", hash = \"sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4\", size = 678670, upload-time = \"2026-06-09T07:52:20.032Z\" },\n { url = \"https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl\", hash = \"sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc\", size = 786679, upload-time = \"2026-06-09T07:52:21.322Z\" },\n { url = \"https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl\", hash = \"sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d\", size = 765683, upload-time = \"2026-06-09T07:52:22.594Z\" },\n { url = \"https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl\", hash = \"sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846\", size = 722361, upload-time = \"2026-06-09T07:52:23.924Z\" },\n { url = \"https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl\", hash = \"sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d\", size = 342401, upload-time = \"2026-06-09T07:52:28.895Z\" },\n { url = \"https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl\", hash = \"sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f\", size = 355540, upload-time = \"2026-06-09T07:52:27.832Z\" },\n { url = \"https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl\", hash = \"sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452\", size = 340500, upload-time = \"2026-06-09T07:52:26.745Z\" },\n]\n\n[[package]]\nname = \"setuptools\"\nversion = \"84.0.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz\", hash = \"sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73\", size = 1168449, upload-time = \"2026-08-08T18:27:58.365Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl\", hash = \"sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670\", size = 818216, upload-time = \"2026-08-08T18:27:56.719Z\" },\n]\n\n[[package]]\nname = \"shellingham\"\nversion = \"1.5.4\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz\", hash = \"sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de\", size = 10310, upload-time = \"2023-10-24T04:13:40.426Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl\", hash = \"sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686\", size = 9755, upload-time = \"2023-10-24T04:13:38.866Z\" },\n]\n\n[[package]]\nname = \"stackcraft\"\nversion = \"0.1.0\"\nsource = { editable = \".\" }\ndependencies = [\n { name = \"fastapi\" },\n { name = \"uvicorn\" },\n]\n\n[package.optional-dependencies]\nml = [\n { name = \"huggingface-hub\" },\n { name = \"peft\" },\n { name = \"pillow\" },\n { name = \"safetensors\" },\n { name = \"torch\", version = \"2.14.1\", source = { registry = \"https://pypi.org/simple\" }, marker = \"sys_platform != 'linux'\" },\n { name = \"torch\", version = \"2.14.1+cu130\", source = { registry = \"https://download.pytorch.org/whl/cu130\" }, marker = \"sys_platform == 'linux'\" },\n { name = \"torchvision\", version = \"0.29.1\", source = { registry = \"https://pypi.org/simple\" }, marker = \"sys_platform != 'linux'\" },\n { name = \"torchvision\", version = \"0.29.1+cu130\", source = { registry = \"https://download.pytorch.org/whl/cu130\" }, marker = \"sys_platform == 'linux'\" },\n { name = \"transformers\" },\n]\n\n[package.dev-dependencies]\ndev = [\n { name = \"httpx2\" },\n { name = \"pytest\" },\n { name = \"ruff\" },\n { name = \"ty\" },\n]\n\n[package.metadata]\nrequires-dist = [\n { name = \"fastapi\" },\n { name = \"huggingface-hub\", marker = \"extra == 'ml'\", specifier = \">=1.33,<2\" },\n { name = \"peft\", marker = \"extra == 'ml'\", specifier = \"==0.21.2\" },\n { name = \"pillow\", marker = \"extra == 'ml'\", specifier = \">=12,<13\" },\n { name = \"safetensors\", marker = \"extra == 'ml'\", specifier = \">=0.8,<1\" },\n { name = \"torch\", marker = \"sys_platform == 'linux' and extra == 'ml'\", specifier = \"==2.14.1\", index = \"https://download.pytorch.org/whl/cu130\" },\n { name = \"torch\", marker = \"sys_platform != 'linux' and extra == 'ml'\", specifier = \"==2.14.1\" },\n { name = \"torchvision\", marker = \"sys_platform == 'linux' and extra == 'ml'\", index = \"https://download.pytorch.org/whl/cu130\" },\n { name = \"torchvision\", marker = \"sys_platform != 'linux' and extra == 'ml'\" },\n { name = \"transformers\", marker = \"extra == 'ml'\", specifier = \"==5.18.0\" },\n { name = \"uvicorn\" },\n]\nprovides-extras = [\"ml\"]\n\n[package.metadata.requires-dev]\ndev = [\n { name = \"httpx2\", specifier = \">=2.13.1\" },\n { name = \"pytest\" },\n { name = \"ruff\" },\n { name = \"ty\" },\n]\n\n[[package]]\nname = \"starlette\"\nversion = \"1.7.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"anyio\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/7b/2b/3850dc6bf7ef71b088962eba31dafc6cffd2f96e577ebb0bb316df96da3e/starlette-1.7.0.tar.gz\", hash = \"sha256:c79f74ea63cff761804fbbfb182f1e0b440c2d07b164d24700c5a1bab5d6ff5d\", size = 2736246, upload-time = \"2026-09-23T07:30:26.35Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/4e/d6/1ec1b290f9e0fb067899b61e1d37a30c923068bad260b216dbe37a7d2967/starlette-1.7.0-py3-none-any.whl\", hash = \"sha256:67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e\", size = 78980, upload-time = \"2026-09-23T07:30:24.567Z\" },\n]\n\n[[package]]\nname = \"sympy\"\nversion = \"1.14.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"mpmath\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz\", hash = \"sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517\", size = 7793921, upload-time = \"2025-04-27T18:05:01.611Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl\", hash = \"sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5\", size = 6299353, upload-time = \"2025-04-27T18:04:59.103Z\" },\n]\n\n[[package]]\nname = \"tokenizers\"\nversion = \"0.23.2\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"huggingface-hub\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/18/1e/bc6587c5ab643b2e17776cace9070a2ae73549c86bffac9934a600bf3c31/tokenizers-0.23.2.tar.gz\", hash = \"sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac\", size = 385745, upload-time = \"2026-09-03T08:55:42.89Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/4d/ed/8a443528baa6fac8dfe8c3b75b038c63ac92bb539bcabe311e227c718173/tokenizers-0.23.2-cp310-abi3-macosx_10_12_x86_64.whl\", hash = \"sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90\", size = 3148852, upload-time = \"2026-09-03T08:55:30.874Z\" },\n { url = \"https://files.pythonhosted.org/packages/67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl\", hash = \"sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf\", size = 3101593, upload-time = \"2026-09-03T08:55:28.587Z\" },\n { url = \"https://files.pythonhosted.org/packages/2e/4d/8f569ed49372a3ed8e57099bd515055fd48d7c95912c4307cda6973c2168/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\", hash = \"sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2\", size = 3516830, upload-time = \"2026-09-03T08:55:14.741Z\" },\n { url = \"https://files.pythonhosted.org/packages/2a/de/e2f14c8919d5bf51874051d00d6c7b7e0e8bde6c6a2dbeddda7f642896ff/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\", hash = \"sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5\", size = 3407975, upload-time = \"2026-09-03T08:55:16.842Z\" },\n { url = \"https://files.pythonhosted.org/packages/c5/bd/93c69152d02ef06ce47aed8b2bf4952dcf733c935a62791873932b2934d9/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl\", hash = \"sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb\", size = 3748165, upload-time = \"2026-09-03T08:55:24.769Z\" },\n { url = \"https://files.pythonhosted.org/packages/2d/b7/56b84b80bc96942bba8eb23751a9e8a1fce4faaf4390425e7083f721c98c/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\", hash = \"sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7\", size = 4024165, upload-time = \"2026-09-03T08:55:18.806Z\" },\n { url = \"https://files.pythonhosted.org/packages/9b/8a/0175e216f005c2fe08238292663aa41e4c802b216e71047a69a0e9fc6fa3/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl\", hash = \"sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703\", size = 3591899, upload-time = \"2026-09-03T08:55:22.752Z\" },\n { url = \"https://files.pythonhosted.org/packages/2c/ca/ca6b93c7820df123b2662a9469e8facc826ccc94e98fdd0d615f6431e73a/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\", hash = \"sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305\", size = 3386843, upload-time = \"2026-09-03T08:55:26.584Z\" },\n { url = \"https://files.pythonhosted.org/packages/e9/a4/4f9106d317b14a80aefea9f0e3a8d07ef25f856a7607eb7f5ab894281fcb/tokenizers-0.23.2-cp310-abi3-manylinux_2_31_riscv64.whl\", hash = \"sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78\", size = 3577314, upload-time = \"2026-09-03T08:55:20.825Z\" },\n { url = \"https://files.pythonhosted.org/packages/8d/6a/1552b70fb0d9ab074fd3fc961435d01364e79c9058481822c3af6e8d402c/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_aarch64.whl\", hash = \"sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40\", size = 9967367, upload-time = \"2026-09-03T08:55:33.188Z\" },\n { url = \"https://files.pythonhosted.org/packages/06/01/3ccb3a956c7528b2507b8a9714155c4baf86af593039db6ea375dd0c96c3/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_armv7l.whl\", hash = \"sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835\", size = 9811886, upload-time = \"2026-09-03T08:55:35.642Z\" },\n { url = \"https://files.pythonhosted.org/packages/fa/73/7038e612d48bda1599457f712f6bd3854eae1a9dc9c13aa47f835349db48/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_i686.whl\", hash = \"sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef\", size = 10146224, upload-time = \"2026-09-03T08:55:38.391Z\" },\n { url = \"https://files.pythonhosted.org/packages/b5/d8/8e9e4e0b287a338d8f88976729628c9d22e8a54cfaf9777018a7f7cb58a0/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_x86_64.whl\", hash = \"sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718\", size = 10256304, upload-time = \"2026-09-03T08:55:40.977Z\" },\n { url = \"https://files.pythonhosted.org/packages/f3/1f/c79a01f671a49728ebb0b61f7ff9ea45663b66cab40bc0858e9859b25c16/tokenizers-0.23.2-cp310-abi3-win32.whl\", hash = \"sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a\", size = 2592809, upload-time = \"2026-09-03T08:55:48.02Z\" },\n { url = \"https://files.pythonhosted.org/packages/db/f7/0a69ac6b82dbccf3f71add938a161c497952749294b8dd6dfe03a819dc40/tokenizers-0.23.2-cp310-abi3-win_amd64.whl\", hash = \"sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde\", size = 2863236, upload-time = \"2026-09-03T08:55:46.193Z\" },\n { url = \"https://files.pythonhosted.org/packages/d7/b0/dee84cb44175be1b4c35bd2f770727494e78f0bb38e571a623ade94dbebb/tokenizers-0.23.2-cp310-abi3-win_arm64.whl\", hash = \"sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa\", size = 2729352, upload-time = \"2026-09-03T08:55:44.345Z\" },\n]\n\n[[package]]\nname = \"torch\"\nversion = \"2.14.1\"\nsource = { registry = \"https://pypi.org/simple\" }\nresolution-markers = [\n \"sys_platform != 'linux'\",\n]\ndependencies = [\n { name = \"filelock\" },\n { name = \"fsspec\" },\n { name = \"jinja2\" },\n { name = \"networkx\" },\n { name = \"setuptools\" },\n { name = \"sympy\" },\n { name = \"typing-extensions\" },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/7d/11/faaca4f8541c45127b7e0d6bb141221fe944c8466d89986b8466c6c195f9/torch-2.14.1-cp313-cp313-macosx_14_0_arm64.whl\", hash = \"sha256:dbe359d705f4d67236743794c296c6dee93a922fd8117eff8c3e880d7d0fb2b9\", size = 127315286, upload-time = \"2026-09-30T17:52:27.43Z\" },\n { url = \"https://files.pythonhosted.org/packages/7b/4b/32c00cacbe682a4c08d9c6c3e7a7116c912184cc7155f06c64f545db97d1/torch-2.14.1-cp313-cp313-win_amd64.whl\", hash = \"sha256:711713391d26a1ce5e9fbc6c996d954a8c12e8825374cc77b809a6af29539b8c\", size = 124113754, upload-time = \"2026-09-30T17:52:33.474Z\" },\n]\n\n[[package]]\nname = \"torch\"\nversion = \"2.14.1+cu130\"\nsource = { registry = \"https://download.pytorch.org/whl/cu130\" }\nresolution-markers = [\n \"sys_platform == 'linux'\",\n]\ndependencies = [\n { name = \"cuda-bindings\" },\n { name = \"cuda-toolkit\", extra = [\"cublas\", \"cudart\", \"cufft\", \"cufile\", \"cupti\", \"curand\", \"cusolver\", \"cusparse\", \"nvjitlink\", \"nvrtc\", \"nvtx\"] },\n { name = \"filelock\" },\n { name = \"fsspec\" },\n { name = \"jinja2\" },\n { name = \"networkx\" },\n { name = \"nvidia-cudnn-cu13\" },\n { name = \"nvidia-cusparselt-cu13\" },\n { name = \"nvidia-nccl-cu13\" },\n { name = \"nvidia-nvshmem-cu13\" },\n { name = \"setuptools\" },\n { name = \"sympy\" },\n { name = \"triton\" },\n { name = \"typing-extensions\" },\n]\nwheels = [\n { url = \"https://download-r2.pytorch.org/whl/cu130/torch-2.14.1%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl\", hash = \"sha256:36d7d3065e072a8b190460327bc68afd0f91592af2433f65bd5dd7d86738f38d\", upload-time = \"2026-09-30T20:26:24Z\" },\n { url = \"https://download-r2.pytorch.org/whl/cu130/torch-2.14.1%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl\", hash = \"sha256:3a78ea0a4d6605c1e351b33ab8aabe44ddb815239cec00cc6f85555d26c0a5a5\", upload-time = \"2026-09-30T20:26:48Z\" },\n]\n\n[[package]]\nname = \"torchvision\"\nversion = \"0.29.1\"\nsource = { registry = \"https://pypi.org/simple\" }\nresolution-markers = [\n \"sys_platform != 'linux'\",\n]\ndependencies = [\n { name = \"numpy\" },\n { name = \"pillow\" },\n { name = \"torch\", version = \"2.14.1\", source = { registry = \"https://pypi.org/simple\" } },\n]\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/8b/85/33819e5ed0caf494a5c0caa6c72bcd9d5bd22c68480d636288832d9d5f55/torchvision-0.29.1-cp313-cp313-macosx_14_0_arm64.whl\", hash = \"sha256:b0b3de76f2ee5678b7950ba9971cf72d9c5887cd7993db61788552f371686fed\", size = 1813847, upload-time = \"2026-09-30T17:57:17.586Z\" },\n { url = \"https://files.pythonhosted.org/packages/6f/d7/08452a8497aab125557b7698886990eb1ab3d3a923dedcd88f309a727fe5/torchvision-0.29.1-cp313-cp313-win_amd64.whl\", hash = \"sha256:41338cd71864419de9ae1e1b0058f6b8645f0548370c4750c7694ffa9bff611c\", size = 1376795, upload-time = \"2026-09-30T17:57:12.792Z\" },\n]\n\n[[package]]\nname = \"torchvision\"\nversion = \"0.29.1+cu130\"\nsource = { registry = \"https://download.pytorch.org/whl/cu130\" }\nresolution-markers = [\n \"sys_platform == 'linux'\",\n]\ndependencies = [\n { name = \"numpy\" },\n { name = \"pillow\" },\n { name = \"torch\", version = \"2.14.1+cu130\", source = { registry = \"https://download.pytorch.org/whl/cu130\" } },\n]\nwheels = [\n { url = \"https://download-r2.pytorch.org/whl/cu130/torchvision-0.29.1%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl\", hash = \"sha256:fe6b3eec6932af8556b00f7588433a83069dc58c729f33fa868f75eb0e970eaa\", upload-time = \"2026-09-30T15:46:27Z\" },\n { url = \"https://download-r2.pytorch.org/whl/cu130/torchvision-0.29.1%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl\", hash = \"sha256:47f000acb2449b281d43707c90116a30c7fa002c7f7bbf6976bcc2562dd5787b\", upload-time = \"2026-09-30T15:46:27Z\" },\n]\n\n[[package]]\nname = \"tqdm\"\nversion = \"4.70.1\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"colorama\", marker = \"sys_platform == 'win32'\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz\", hash = \"sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4\", size = 171846, upload-time = \"2026-09-11T07:25:16.601Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl\", hash = \"sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73\", size = 80199, upload-time = \"2026-09-11T07:25:14.599Z\" },\n]\n\n[[package]]\nname = \"transformers\"\nversion = \"5.18.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"huggingface-hub\" },\n { name = \"numpy\" },\n { name = \"packaging\" },\n { name = \"pyyaml\" },\n { name = \"regex\" },\n { name = \"safetensors\" },\n { name = \"tokenizers\" },\n { name = \"tqdm\" },\n { name = \"typer\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/9e/6e/5a50ca8aff5fdd4ff21271198d2fb9e1f11bcd0ea279576199c0caa34cba/transformers-5.18.0.tar.gz\", hash = \"sha256:d89c206e42e841af7cd314b3d2b21c03e977d819cdb4aef65ada048769a644df\", size = 10028745, upload-time = \"2026-09-30T16:46:00.575Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/2d/3b/f84d7d12886147c5267216ed6c57d1163e68d24a89b2ad29d835a7089463/transformers-5.18.0-py3-none-any.whl\", hash = \"sha256:d79e5a515a572ee3deb33eb0d578be47d91e70c75d54cf21bf6eede6edef14a9\", size = 12579646, upload-time = \"2026-09-30T16:45:57.51Z\" },\n]\n\n[[package]]\nname = \"triton\"\nversion = \"3.8.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/87/4d/4c564374bcdadb166fccbf3e45aee0d4a473f88d341761bd2fefe3b8e8c1/triton-3.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:b7004666652f500ed854a86988e4b3d69d247188b5d2092b5df1e44f4a954099\", size = 226476793, upload-time = \"2026-08-28T16:08:30.956Z\" },\n { url = \"https://files.pythonhosted.org/packages/b0/b6/3394d5548404c1cabd1dadadd28d0b3f9478db1dff8180da53bb3f0a1e19/triton-3.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:1f0497218e26b7d79773ad9c2a3fa3b539ee69f587a13fac2e552b1d322a8015\", size = 247975122, upload-time = \"2026-08-28T15:56:04.112Z\" },\n]\n\n[[package]]\nname = \"truststore\"\nversion = \"0.10.4\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz\", hash = \"sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301\", size = 26169, upload-time = \"2025-08-12T18:49:02.73Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl\", hash = \"sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981\", size = 18660, upload-time = \"2025-08-12T18:49:01.46Z\" },\n]\n\n[[package]]\nname = \"ty\"\nversion = \"0.0.84\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz\", hash = \"sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67\", size = 7409537, upload-time = \"2026-09-24T13:16:24.367Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl\", hash = \"sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695\", size = 13935333, upload-time = \"2026-09-24T13:15:31.167Z\" },\n { url = \"https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl\", hash = \"sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17\", size = 13511328, upload-time = \"2026-09-24T13:15:34.446Z\" },\n { url = \"https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl\", hash = \"sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a\", size = 13445445, upload-time = \"2026-09-24T13:15:37.476Z\" },\n { url = \"https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\", hash = \"sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc\", size = 13471235, upload-time = \"2026-09-24T13:15:40.64Z\" },\n { url = \"https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\", hash = \"sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355\", size = 13680704, upload-time = \"2026-09-24T13:15:43.817Z\" },\n { url = \"https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl\", hash = \"sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546\", size = 14518923, upload-time = \"2026-09-24T13:15:46.802Z\" },\n { url = \"https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\", hash = \"sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df\", size = 15027731, upload-time = \"2026-09-24T13:15:49.755Z\" },\n { url = \"https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl\", hash = \"sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e\", size = 14748260, upload-time = \"2026-09-24T13:15:52.713Z\" },\n { url = \"https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\", hash = \"sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7\", size = 14205858, upload-time = \"2026-09-24T13:15:55.677Z\" },\n { url = \"https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl\", hash = \"sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a\", size = 14573196, upload-time = \"2026-09-24T13:15:59.165Z\" },\n { url = \"https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl\", hash = \"sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9\", size = 13480760, upload-time = \"2026-09-24T13:16:02.313Z\" },\n { url = \"https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl\", hash = \"sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95\", size = 13696346, upload-time = \"2026-09-24T13:16:05.391Z\" },\n { url = \"https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl\", hash = \"sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4\", size = 13959410, upload-time = \"2026-09-24T13:16:08.661Z\" },\n { url = \"https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl\", hash = \"sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07\", size = 14350294, upload-time = \"2026-09-24T13:16:11.865Z\" },\n { url = \"https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl\", hash = \"sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba\", size = 13176439, upload-time = \"2026-09-24T13:16:15.049Z\" },\n { url = \"https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl\", hash = \"sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d\", size = 13962879, upload-time = \"2026-09-24T13:16:18.191Z\" },\n { url = \"https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl\", hash = \"sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da\", size = 13656453, upload-time = \"2026-09-24T13:16:21.51Z\" },\n]\n\n[[package]]\nname = \"typer\"\nversion = \"0.27.2\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"annotated-doc\" },\n { name = \"colorama\", marker = \"sys_platform == 'win32'\" },\n { name = \"rich\" },\n { name = \"shellingham\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz\", hash = \"sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945\", size = 204045, upload-time = \"2026-08-28T10:26:55.046Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl\", hash = \"sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d\", size = 123130, upload-time = \"2026-08-28T10:26:53.752Z\" },\n]\n\n[[package]]\nname = \"typing-extensions\"\nversion = \"4.16.0\"\nsource = { registry = \"https://pypi.org/simple\" }\nsdist = { url = \"https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz\", hash = \"sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5\", size = 113555, upload-time = \"2026-07-02T08:40:05.92Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl\", hash = \"sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8\", size = 45571, upload-time = \"2026-07-02T08:40:04.659Z\" },\n]\n\n[[package]]\nname = \"typing-inspection\"\nversion = \"0.4.4\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"typing-extensions\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz\", hash = \"sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47\", size = 76928, upload-time = \"2026-08-12T12:37:25.997Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl\", hash = \"sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147\", size = 14750, upload-time = \"2026-08-12T12:37:24.648Z\" },\n]\n\n[[package]]\nname = \"uvicorn\"\nversion = \"0.54.0\"\nsource = { registry = \"https://pypi.org/simple\" }\ndependencies = [\n { name = \"click\" },\n { name = \"h11\" },\n]\nsdist = { url = \"https://files.pythonhosted.org/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz\", hash = \"sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620\", size = 112283, upload-time = \"2026-09-25T06:52:37.601Z\" }\nwheels = [\n { url = \"https://files.pythonhosted.org/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl\", hash = \"sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf\", size = 87427, upload-time = \"2026-09-25T06:52:35.829Z\" },\n]\n"
}
}
}