File size: 189,573 Bytes
4be6a52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  "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"
    }
  }
}