diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..a2e31128c7c95069b8bdea5b6d471325d9213253 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.safetensors filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..cd52324cfae719548ecb79e2b4c42a3fad8bbc30 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Doccy Pty Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.json b/MANIFEST.json new file mode 100644 index 0000000000000000000000000000000000000000..6bff5b64495e4236fe5b0a4e7feed17ea334eb82 --- /dev/null +++ b/MANIFEST.json @@ -0,0 +1,829 @@ +{ + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "approved": true, + "base_weights_referenced_not_redistributed": [ + { + "copyright": "2026 Alibaba Cloud", + "license": "Apache-2.0", + "purpose": "the operator downloads these themselves; this repository ships only an adapter", + "repo": "Qwen/Qwen3.8-27B", + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0" + } + ], + "blockers": [], + "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "contract": "solomon-v1", + "descriptor": { + "path": "release/solomon-release.json", + "sha256": "7a8b02bc00465fb3a06e59604af4ab07e5b290d45b293e7ef72d4c5c7bf4a596" + }, + "excluded_categories": [ + "panels and document corpora (data/**, **/authoring/**, **/*.jsonl)", + "datasets and training inputs of every kind", + "caches, states, rendered images, score archives, annotation outputs and logs", + "test files, evaluation-panel builders and qualification machinery", + "acceptance fixture documents and any padded development document", + "base model weights (referenced by pinned revision, never redistributed)", + "GPU launch scripts, ops/ tooling, internal plans and internal cards" + ], + "files": [ + { + "destination": ".gitattributes", + "generated_by": "huggingface-cli / already committed in the clone", + "kind": "git_lfs_config", + "note": "tracks *.safetensors, *.bin, *.npz, *.pt, *.tar through git-lfs; the adapter (.safetensors) and the heads (.npz) are both covered, so no new LFS pattern is needed", + "staged_bytes": 217, + "staged_sha256": "8baff4be52225e26dd13dffb05fed3d0db821911c36d7ffd6f6d00d02ca421c7" + }, + { + "destination": "README.md", + "generated_by": "ops/solomon_package.py (authored for an external audience; every figure read from a measured artifact at generation time)", + "kind": "model_card", + "staged_bytes": 43920, + "staged_sha256": "09c96e67b59006ca0eb53113b71c08c5ab830781f86291b8721b173dcb861bc0" + }, + { + "destination": "LICENSE", + "generated_by": "ops/solomon_package.py (Apache-2.0 body from the archived upstream text, our copyright in the appendix)", + "kind": "project_license", + "staged_bytes": 11344, + "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457" + }, + { + "destination": "NOTICE", + "generated_by": "ops/solomon_package.py", + "kind": "attribution_notice", + "staged_bytes": 5125, + "staged_sha256": "e0ff128e3b74f42d2f8de164da975d192192d9cb010330d6cf1a0fd73685bb74" + }, + { + "destination": "MODIFICATIONS.md", + "generated_by": "ops/solomon_package.py", + "kind": "change_notice_apache_4b", + "staged_bytes": 7207, + "staged_sha256": "c51ba45105ebd0a41181ca7b35f41795a3e73c86b1cd41d8d7e9e3cadac3737a" + }, + { + "destination": "requirements.lock", + "generated_by": "ops/solomon_package.py", + "kind": "dependency_pins", + "staged_bytes": 3943, + "staged_sha256": "56144aef9d15db53f7b33fd1c80e7a8d078b70800fa24d3ecc72d86e14cc2898" + }, + { + "destination": "MANIFEST.json", + "generated_by": "ops/solomon_package.py", + "kind": "manifest" + }, + { + "destination": "licenses/Qwen-Apache-2.0.txt", + "expected_bytes": 11544, + "expected_sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "kind": "upstream_model_license", + "staged_bytes": 11544, + "staged_sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a" + }, + { + "destination": "adapter/adapter.safetensors", + "expected_bytes": 870363376, + "expected_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "git_lfs": true, + "kind": "lora_adapter", + "staged_bytes": 870363376, + "staged_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0" + }, + { + "destination": "adapter/heads.npz", + "expected_bytes": 1643162, + "expected_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "git_lfs": true, + "kind": "answer_heads", + "staged_bytes": 1643162, + "staged_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + { + "destination": "adapter/config.json", + "generated_by": "ops/solomon_package.py", + "kind": "adapter_config", + "staged_bytes": 2240, + "staged_sha256": "7e78a6017d4400843dabf33acc9e1d43255f0f90021518eb36e991c492544436" + }, + { + "destination": "serving/serving-binding.json", + "kind": "serving_binding", + "note": "default serving binding (bf16)", + "staged_bytes": 6249, + "staged_sha256": "11b9352ab72b9e05a3d5ddbecc5a17f462c33cb6cc09dd401e062fc44dfb0831" + }, + { + "destination": "serving/serving-binding-fp32.json", + "expected_sha256": "eed6de24fafcbdd542858f2687cb1ca6f5e524cd9780390f4148330a0863a193", + "kind": "serving_binding", + "note": "serving binding for precision=fp32", + "staged_bytes": 6203, + "staged_sha256": "eed6de24fafcbdd542858f2687cb1ca6f5e524cd9780390f4148330a0863a193" + }, + { + "destination": "serving/serving-binding-int8.json", + "expected_sha256": "a711eca4c70bdcb22d41127af49ae8b90c1e23b54db4fb5308d9cb0e05fe40ea", + "kind": "serving_binding", + "note": "serving binding for precision=int8", + "staged_bytes": 6353, + "staged_sha256": "a711eca4c70bdcb22d41127af49ae8b90c1e23b54db4fb5308d9cb0e05fe40ea" + }, + { + "destination": "serving/readout-temperature-v3.json", + "expected_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "kind": "calibration_artifact", + "staged_bytes": 3370, + "staged_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9" + }, + { + "destination": "serving/evidence-head.safetensors", + "expected_bytes": 31461804, + "expected_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "git_lfs": true, + "kind": "evidence_head_weights", + "note": "relevance head weights (float32)", + "staged_bytes": 31461804, + "staged_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c" + }, + { + "destination": "serving/evidence-head.json", + "expected_bytes": 3106, + "expected_sha256": "b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244", + "kind": "evidence_head_config", + "note": "relevance head config incl. lexical_residual alpha", + "staged_bytes": 3106, + "staged_sha256": "b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244" + }, + { + "destination": "serving/evidence-policy.json", + "expected_bytes": 969, + "expected_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84", + "kind": "evidence_policy", + "note": "ranked-pointer serving policy (top 3, suppressed when not stated)", + "staged_bytes": 969, + "staged_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84" + }, + { + "destination": "serving/selection.json", + "generated_by": "ops/solomon_package.py", + "kind": "readout_selection", + "staged_bytes": 796, + "staged_sha256": "7f58b2e65e37df6409f2ac5fb774c7792fa93899269fdf76dc3446aacfae127f" + }, + { + "destination": "serving/service-export.json", + "generated_by": "ops/solomon_package.py", + "kind": "qualification_envelope", + "staged_bytes": 607, + "staged_sha256": "40ba3d4d2252554ff4e12a8f761097558af653be3ff23829e4c935bb3b0df4a8" + }, + { + "destination": "src/solomon/__init__.py", + "kind": "serving_code", + "staged_bytes": 1157, + "staged_sha256": "1e9e7993cd182b5ccac3eab801277cb00c802c75281bee81571890c6fdaed2c6" + }, + { + "destination": "src/solomon/api.py", + "kind": "serving_code", + "staged_bytes": 4284, + "staged_sha256": "21836850e511062c34c9a1c23cd78ae5ab00ea4b23c7bc1f79dc8838f1b41655" + }, + { + "destination": "src/solomon/binding.py", + "kind": "serving_code", + "staged_bytes": 13767, + "staged_sha256": "09aaede5bc00b9cd77af024c92b53e7b020b7b38724a88aafa52ef8d61958f5c" + }, + { + "destination": "src/solomon/calibration.py", + "kind": "serving_code", + "staged_bytes": 11273, + "staged_sha256": "4e29eb430e33c7cbf074c5be153fe858210c6d4def6462c2bf9761251e91dee6" + }, + { + "destination": "src/solomon/engine.py", + "kind": "serving_code", + "staged_bytes": 7404, + "staged_sha256": "839442be642ac6449b32492b2ca0a4bce65de2d6095f097b053ed667304ec9fb" + }, + { + "destination": "src/solomon/engine_contract.py", + "kind": "serving_code", + "staged_bytes": 18263, + "staged_sha256": "3df6f2e515b765cbb07d5a281328989443795d7fdc920dc21756b4230ddf1fd5" + }, + { + "destination": "src/solomon/engine_cuda.py", + "kind": "serving_code", + "staged_bytes": 12700, + "staged_sha256": "28572bc9bafae9c9bd9ad42d4de2dc267ad083f126379beaf486bd4b1795641a" + }, + { + "destination": "src/solomon/engine_numerics.py", + "kind": "serving_code", + "staged_bytes": 3558, + "staged_sha256": "ff6779232b56469d2273cc548f4de7f7a0f6a15731c7c0986e453a2dd1180e5f" + }, + { + "destination": "src/solomon/engine_reasoning.py", + "kind": "serving_code", + "staged_bytes": 9209, + "staged_sha256": "87f417015f45293283bd3a2738c5745e5a2aac7a805ae89403fdc1fb7b226bb4" + }, + { + "destination": "src/solomon/engine_reference.py", + "kind": "serving_code", + "staged_bytes": 13037, + "staged_sha256": "5487a5b0536e481a8285ccd82dff7ff389340d4f59c36653d358088b6c4050c8" + }, + { + "destination": "src/solomon/evidence.py", + "kind": "serving_code", + "staged_bytes": 7617, + "staged_sha256": "a0ece458b5c0546c1f80b48cea87ebc1c3732e83f2c0f8e58969645bc8bde2f7" + }, + { + "destination": "src/solomon/evidence_head.py", + "kind": "serving_code", + "staged_bytes": 19646, + "staged_sha256": "b68b93af8d956fc503c1a04bb188fc9f370a27aada4cef0a026f298c2f42dbf3" + }, + { + "destination": "src/solomon/evidence_packages.py", + "kind": "serving_code", + "staged_bytes": 24688, + "staged_sha256": "8e555f09c7fa96e1e135d5bb49d388b66ffbd4fc40fc1659b4cb7396ca3728c5" + }, + { + "destination": "src/solomon/evidence_selector.py", + "kind": "serving_code", + "staged_bytes": 18296, + "staged_sha256": "3cb6c6637c665dcb6ab401228a399ca8d5714bc9f91a2635d14b334abdf8b8de" + }, + { + "destination": "src/solomon/heads.py", + "kind": "serving_code", + "staged_bytes": 13978, + "staged_sha256": "1934d3c50c57622fa2159211b5c9da024ab888ad6ba49d8b7e82365539a785a4" + }, + { + "destination": "src/solomon/prompts_two_letter.py", + "kind": "serving_code", + "staged_bytes": 4169, + "staged_sha256": "12e19c094e48be2881cbad79f5c1ea0ffefffdcb308013d1b45925d32b6aae6f" + }, + { + "destination": "src/solomon/readout.py", + "kind": "serving_code", + "staged_bytes": 14094, + "staged_sha256": "59f7f941262eba4f912dbadf4c0140f7cbb5997736f9dc3c7bae5f856763fc77" + }, + { + "destination": "src/solomon/reliability.py", + "kind": "serving_code", + "staged_bytes": 1523, + "staged_sha256": "08cc3b22371a314b06bc89a7807c7c0581063f8a70a86ad92b62eae442bad42a" + }, + { + "destination": "src/solomon/retrieval.py", + "kind": "serving_code", + "staged_bytes": 9155, + "staged_sha256": "2b7bd6603cb6df3bfe63496dd475206ff17655d48916cf974860adf335e9b155" + }, + { + "destination": "src/solomon/routing.py", + "kind": "serving_code", + "staged_bytes": 3038, + "staged_sha256": "9901da2c3d3a0e8dc453dae91f6c68c7a32b609418fb6e8fc41e95e3601b3f77" + }, + { + "destination": "src/solomon/semantics.py", + "kind": "serving_code", + "staged_bytes": 2910, + "staged_sha256": "86e1833b7f693e914cc55744ed5565ae0d436424c262f8773bb944a9f5ac65da" + }, + { + "destination": "src/solomon/service.py", + "kind": "serving_code", + "staged_bytes": 39483, + "staged_sha256": "8096810e0bd318255568817d0f022be8aed8e0999b7d01cf5fd2bbafb5dcc351" + }, + { + "destination": "src/solomon/service_answers.py", + "kind": "serving_code", + "staged_bytes": 20064, + "staged_sha256": "9075e9b9e7a713a9f843267dc5aebe8d6b9fc4dcaadbd9800c964d966e83fb06" + }, + { + "destination": "src/solomon/service_checked.py", + "kind": "serving_code", + "staged_bytes": 7596, + "staged_sha256": "cff9d85ea8dcb3f431dd2497a636f4d02cdc2c5d2add6f12aab9889bb29e9082" + }, + { + "destination": "src/solomon/service_evidence.py", + "kind": "serving_code", + "staged_bytes": 7758, + "staged_sha256": "a2021700c36d9844f607497b7ab0d098e1f51fd775c6eb2a77940fa590335603" + }, + { + "destination": "src/solomon/service_heads.py", + "kind": "serving_code", + "staged_bytes": 9364, + "staged_sha256": "6dc2a0da037aea32c9bba0c3e5924c8f33779eed9b6f02c66465b3a9b960fb9c" + }, + { + "destination": "src/solomon/service_packages.py", + "kind": "serving_code", + "staged_bytes": 13181, + "staged_sha256": "5f30eaaac5daa80f1fc8399828ddd1c7128a34e79fe88a3440656e76629ac8d8" + }, + { + "destination": "src/solomon/service_states.py", + "kind": "serving_code", + "staged_bytes": 10657, + "staged_sha256": "329ecca03d06a53c3aa15568784b70cdde7c4ea63c3283e4aaec3f9018defdda" + }, + { + "destination": "src/solomon/serving.py", + "kind": "serving_code", + "staged_bytes": 4009, + "staged_sha256": "978a6f1d1193dd535a5fc03fa393b6421a2cd7c85bb77b1dfa796b7fcfb92836" + }, + { + "destination": "src/solomon/units.py", + "kind": "serving_code", + "staged_bytes": 11852, + "staged_sha256": "0b2fbf586cf73a9f54b4fed5a7e996cc8b97196ac8ee2980888a7d8cb5713d79" + }, + { + "destination": "mlx/.gitignore", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 126, + "staged_sha256": "dde5ff0f5e7a2c741e6ec76777be6a2e1d3679815bdf8f44d8ca2cee2ce0e351" + }, + { + "destination": "mlx/CONVERTER-SOURCE.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1515, + "staged_sha256": "60bc159ff656411b5d7fbec93fc696fc616bac88f9ad1c0e87390a003111c9c0" + }, + { + "destination": "mlx/LICENSE", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 11344, + "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457" + }, + { + "destination": "mlx/MODIFICATIONS.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1616, + "staged_sha256": "ff60b513fd97c5cbaa06af65972b234a6479216e94546f4852dd70f0dbf278ca" + }, + { + "destination": "mlx/NOTICE", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 3897, + "staged_sha256": "7c62f7b3fde4f2f80f869be0bf592facd5e4a4d38dec826c1f6953bede6255ca" + }, + { + "destination": "mlx/README.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 7417, + "staged_sha256": "586f53fd31ea723be1cbf0a2b5f35ed196960a231e132d8fd27e5842cc2b46a8" + }, + { + "destination": "mlx/bf16/LICENSE", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 11344, + "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457" + }, + { + "destination": "mlx/bf16/MODIFICATIONS.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 4195, + "staged_sha256": "eda88d63f04f03cb169579d527ceea4eb71bf44eae5c305bc688fc0e2a3fdc78" + }, + { + "destination": "mlx/bf16/NOTICE", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 3897, + "staged_sha256": "7c62f7b3fde4f2f80f869be0bf592facd5e4a4d38dec826c1f6953bede6255ca" + }, + { + "destination": "mlx/bf16/README.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 3063, + "staged_sha256": "1e3aa92ff4687d6e46520473bcc9c207b492d9239f1f882bff185c716ecee320" + }, + { + "destination": "mlx/bf16/base-manifest.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 6434, + "staged_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d" + }, + { + "destination": "mlx/bf16/binding.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 12306, + "staged_sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530" + }, + { + "destination": "mlx/bf16/conversion.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 6934, + "staged_sha256": "fb3db797acdf96c9eca2b6dfef2d6d385705aeade387011d45ea16b91b510c66" + }, + { + "destination": "mlx/docs/UPSTREAM-MODIFICATIONS.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 4195, + "staged_sha256": "eda88d63f04f03cb169579d527ceea4eb71bf44eae5c305bc688fc0e2a3fdc78" + }, + { + "destination": "mlx/docs/VALIDATION-20260921.md", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2928, + "staged_sha256": "86afe1be02ed55a75c2b2a1663898f20cc536371f5dcd2df0f701f7a30ae9a47" + }, + { + "destination": "mlx/examples/decide.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 763, + "staged_sha256": "1a004e8a474f0458a4b84024098dfcb44ec5a24a795d742d266e7557ac9fd170" + }, + { + "destination": "mlx/pyproject.toml", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1115, + "staged_sha256": "e7822f6addbb909f4424084e3cca5eb11a7f5cb929b4b5742b1690a2bed756b5" + }, + { + "destination": "mlx/requirements.cloud.lock", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 74492, + "staged_sha256": "4b60f433f1f67289db1db68630d011aea1565b50cf518c1e7461d7c2b0e2599b" + }, + { + "destination": "mlx/requirements.lock", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1338, + "staged_sha256": "cb2a81cc73252bc54570e035483d68e24253c98223e0e65373df5995bd99dc5a" + }, + { + "destination": "mlx/scripts/benchmark.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 5074, + "staged_sha256": "7a9ee62cae81bf4d05d5ad17760bfa510886812a017eb0bc8d9cab68dcd1cab6" + }, + { + "destination": "mlx/scripts/benchmark_chunks.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1897, + "staged_sha256": "216e10981bdff42d04269082ae2317ab25a161c80504813aa74ee1a55b7ca167" + }, + { + "destination": "mlx/scripts/check_cuda_parity.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 7614, + "staged_sha256": "082ae6c535f52d6b386d3348222515ef564b38efaf59fd8d6b262d00867b0a24" + }, + { + "destination": "mlx/scripts/score_parity.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 542, + "staged_sha256": "ddc84f869bccd5d2c270f6abf2a0862c59ec7c0ebdc3497959ba35c16bb11b7a" + }, + { + "destination": "mlx/scripts/validate_api.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 5852, + "staged_sha256": "f29c49ac600314491c36c43b780b92958dea56253377b4b64a2da7ecb4b57a28" + }, + { + "destination": "mlx/src/solomon_mlx/__init__.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 132, + "staged_sha256": "714a5439b33780d250c404938cd062c0d6809f83f21b59f89997bd168469925a" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/__init__.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 113, + "staged_sha256": "77defd15cc47661e0e9a31275e7fd59e979f3f9a6429671d8199f6002dca6c2c" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/contract.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 6474, + "staged_sha256": "7c6607179a028f30b462349a86f9cf7d0ec2652e0a1c7c8c24649b6ce71df697" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/evidence.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 7730, + "staged_sha256": "18a4978a27d6bbd3f7daaa34836d1820303c4ff581b0dfda877aab586392293b" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/evidence_v3.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 24783, + "staged_sha256": "6ff3da1e920c082e9b3629ca0230007262f2ce69fd921b89dd4b3e67511b3a64" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/prompts.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 4693, + "staged_sha256": "8bfb5a12d625664c8830f5cf243aac04d1e973bfe4f5ecfc8221021f09c318df" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/retrieval.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 9261, + "staged_sha256": "11e7bbb1a84ba837eac3768d6e882f44a557be25d62729c8a39dd5cb870c0932" + }, + { + "destination": "mlx/src/solomon_mlx/_vendor/semantics.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 3051, + "staged_sha256": "b580d3c114536a6faf58bcd92d2c61028f80891a19d78d6a17ed585948abfc15" + }, + { + "destination": "mlx/src/solomon_mlx/api.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 13772, + "staged_sha256": "bae9fdd3ef6b395142ec5c099635cc9ba90f7f33c7423f99caf6009f51d4fd3c" + }, + { + "destination": "mlx/src/solomon_mlx/artifacts.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2614, + "staged_sha256": "0b3d102a2a04d88b2a1f3eb8a22506f626191615d779bfd53a4377824e6c239b" + }, + { + "destination": "mlx/src/solomon_mlx/budget.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2875, + "staged_sha256": "f72614686c19e1d923e6bc23d4353e8b1ac150863565939ae4a3831bc4ecd211" + }, + { + "destination": "mlx/src/solomon_mlx/cli.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2990, + "staged_sha256": "0a6be4cd03621963ec5f73a8af71224cb8b40ae88232477d8370b7d3b9b75021" + }, + { + "destination": "mlx/src/solomon_mlx/engine.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 15359, + "staged_sha256": "13c27c3fbca576ae0d70262c2a75c9382df3b4fcfd8a11996d125750883b648a" + }, + { + "destination": "mlx/src/solomon_mlx/evaluation.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 13906, + "staged_sha256": "bb9f50d184527e21139b9ffb5e9d1613bc30822ccd7cf06b7b7f8b1cc8b50674" + }, + { + "destination": "mlx/src/solomon_mlx/prepare.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 5513, + "staged_sha256": "dda9879a9ddc1c3f6dcf0df52b3a244b2ca7920946bbd29ddc8e1c0f9944b887" + }, + { + "destination": "mlx/src/solomon_mlx_hub/__init__.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 248, + "staged_sha256": "9132679f7a5826d1f472cef6a42068ead04b9c4737d2c5fa667d8cb8437212e2" + }, + { + "destination": "mlx/src/solomon_mlx_hub/__main__.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1852, + "staged_sha256": "86cf93d7c2a2342e1f9babc4981f4dbd1255db08d79406ec0c3faaff91762f1c" + }, + { + "destination": "mlx/src/solomon_mlx_hub/data/base-manifest.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 6434, + "staged_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d" + }, + { + "destination": "mlx/src/solomon_mlx_hub/data/release.json", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 8582, + "staged_sha256": "3b07f07cdb19897ecec0694cfe2eb745a8d8e99c573ec74dc2411b965715f70a" + }, + { + "destination": "mlx/src/solomon_mlx_hub/prepare.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 8707, + "staged_sha256": "9d433eb7b242aaa83e1f3c3f4a2b9f99cf410299f99c133732f6ff5559a9b202" + }, + { + "destination": "mlx/tests/test_artifacts_and_evaluation.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2745, + "staged_sha256": "10bbfb9b9e7d66c7dae6fd1e2ca2980572e4a81f93f847c3a0f49525d8053a23" + }, + { + "destination": "mlx/tests/test_budget.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 780, + "staged_sha256": "4faef6c3ad8aa65df057babb86d4092943a4a262bb859445ee1dde1e29a826d5" + }, + { + "destination": "mlx/tests/test_contract.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 3393, + "staged_sha256": "b4ac6dba4ac70f1f8c8c145fba62d276d3af9dc7ab833652c92f85a3048e5f26" + }, + { + "destination": "mlx/tests/test_hub.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 7227, + "staged_sha256": "5926ceb8e970f1cc3c7a0b9fda2d35638f31eb12009d7232fabde4308247c633" + }, + { + "destination": "mlx/tests/test_metal_primitives.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 1704, + "staged_sha256": "2d88596fba6cb6b16acb9af958add22e9b21d367dbd4fcc277878973a5e3c30a" + }, + { + "destination": "mlx/tests/test_parity.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 763, + "staged_sha256": "77487c307fe272a99e1f17b9e26e30dc987127a7c47fc7c2c8a545bca174b881" + }, + { + "destination": "mlx/tests/test_processor.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 853, + "staged_sha256": "521586bf405dced3dc8ebe890c2ad3f42dd99792c08fd84e9d0a4398642b3727" + }, + { + "destination": "mlx/tests/test_tiny_decoder.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 2369, + "staged_sha256": "e2a7496551da6d6690ee55c2d299a4759122bcfa77d48f0424bac2892a555dcf" + }, + { + "destination": "mlx/tests/test_tiny_library.py", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 6658, + "staged_sha256": "03a2887be6713671ce1307d24c1e41e5d5025423c24230c4d7e99743a9b50bc8" + }, + { + "destination": "mlx/uv.lock", + "generated_by": "the MLX packaging step (separate owner); staged in place", + "kind": "mlx_package", + "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/", + "staged_bytes": 285374, + "staged_sha256": "e413d49af059f183f7fd4a39fccae8cc2c3908a4e7562926ea8de248880ddbc7" + } + ], + "hash_or_size_mismatch": [], + "heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "license": "apache-2.0", + "live_acceptance_passed_for_this_binding": true, + "missing_sources": [], + "model": "Solomon", + "not_shipped_though_statically_reachable": [ + "a smoke test carrying an embedded synthetic document", + "the answer-head training loop", + "the benchmark-panel builder (panel authoring and validation, not a serving file)", + "the evaluation harness", + "the superseded training-side confidence layer", + "the training-side calibration fit", + "the training/evaluation data module" + ], + "not_staged": [], + "owner_release_decision_recorded": true, + "policy": "DEFAULT DENY. Only the destinations listed in `files` may exist in the repository. `ops/solomon_package.py --enforce` walks the working tree and fails on anything else.", + "publishing_performed": false, + "readout": "four_collapsed", + "repo_id": "DoccyHealth/Solomon", + "runtime_fingerprint": "7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea", + "schema": "solomon.distribution-manifest.v1", + "serving_binding_sha256": "0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8", + "staged_count": 104, + "staged_into": "release/Solomon", + "totals": { + "bytes_from_disk": 903843866, + "entries": 104, + "ready": 104 + }, + "version": "1.1.0" +} diff --git a/MODIFICATIONS.md b/MODIFICATIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..eeba3944e50da105463e30484c555a9853d4654e --- /dev/null +++ b/MODIFICATIONS.md @@ -0,0 +1,118 @@ +# Statement of changes + +Apache License 2.0, section 4(b): prominent notice that files carry modifications. + +## What is modified + +**No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited, +renamed, patched or redistributed in this repository, with one exception: the +upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt` +(sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is +unmodified and carries the upstream copyright. + +The modification this work carries is not an edit to a source file. It is a set +of **trained parameters applied to the base model at inference time**, plus an +original serving layer that reads the model's logits. Concretely: + +| Change | Artifact | sha256 | +|---|---|---| +| LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0` | +| Trained linear answer heads | `adapter/heads.npz` | `f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab` | +| Readout calibration, one positive scalar per answer type | `serving/readout-temperature-v3.json` | file `1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9` / payload `945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b` | +| Runtime identity binding (bf16, default) | `serving/serving-binding.json` | payload `0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8` | +| Runtime identity binding (fp32) | `serving/serving-binding-fp32.json` | payload `517f263000cf65457751c4fba519221d48ac550e060b241f198b007ae88c59db` | +| Runtime identity binding (int8) | `serving/serving-binding-int8.json` | payload `b550254777ceb3f53e7e10e15f7dc9f80f620ddd9c1ed69592190da5f2107f16` | +| Experimental evidence head weights | `serving/evidence-head.safetensors` | `5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c` | +| Experimental evidence head config | `serving/evidence-head.json` | `b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244` | +| Experimental evidence policy | `serving/evidence-policy.json` | `866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84` | +| Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` | + +Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision +`1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0. + +The v1.1 adapter and heads were trained on 21 September 2026 (v1.0: 16 to 20 September 2026). The shipped artifacts are +identified by the checksums in the table above and in `MANIFEST.json`. + +## Which files carry a change notice + +| File | Why | +|---|---| +| `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy | +| `MODIFICATIONS.md` | this file | +| `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body | +| `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums | +| `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one | + +No file under `src/` carries an upstream change notice, because no file under +`src/` contains upstream code. Every file there is original work, written for +this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by +the copyright line in `NOTICE`. + +All of the changes described above — the adapter, the heads, the calibration, the +serving binding and the serving layer — are Copyright 2026 +Doccy Pty Ltd and licensed under Apache-2.0. + +## Third-party text scan + +Before release, **every file staged into this repository was scanned for text +originating in third-party source documents.** The scan compared normalised +6-gram and 8-gram shingles of every staged text file against: + +1. the 42 third-party source records the training and evaluation panels were + built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0, + OGL v3.0 and US-government public-domain assertions); and +2. every generated panel and document corpus on disk. + +**Result: no third-party document text is present in any shipped file.** The +only matches were: + +* the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which + matches an Apache-2.0 licence document held in the evaluation corpus and is + required to be here verbatim; and +* the phrase *"A missing fact is not a negative fact"*, which is **our own + prompt-template wording** appearing in our own evaluation panels, not + third-party text entering our prompts. + +Acceptance fixtures are excluded from this repository entirely. The fixture +documents used in live acceptance are original synthetic text authored for this +project and held in tooling that is not distributed. + +Re-run the scan with `ops/solomon_package.py --scan` in the source project. + +## Maintainer notes + +These warnings used to live in a `PUSH.md` that carried its own instruction to be +deleted before the repository was made public. The repository is public now, so +that file is gone — from the manifest and from the tree — and the parts of it that +are still true are kept here. + +**Never mutate a published revision.** The serving binding pins by hash and +consumers pin by revision. If something is wrong with a published revision, push a +**new** revision and a **new** tag. Do not force-push over a revision that has +already been fetched: a consumer who pinned it would silently get different weights +under a hash they already trusted. + +**Do not trust a fetch tool's exit code.** The remote-volume fetch used to retrieve the adapter PRINTS "No such file or directory" AND EXITS 0 on a wrong path: a failed download is indistinguishable from a successful one by return code. Never trust the exit status; check that the file exists and that its sha256 matches before treating a fetch as done. +The 870 MB adapter in this repository was fetched that way, and the only thing that +made the fetch trustworthy was re-hashing the file afterwards. Verify what you +downloaded from here the same way: + +```sh +shasum -a 256 adapter/adapter.safetensors # d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0 +shasum -a 256 adapter/heads.npz # f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab +``` + +`MANIFEST.json` carries the size and sha256 of every file here. + +**Both weight files must go through git-lfs.** `.gitattributes` tracks +`*.safetensors` and `*.npz`; confirm with `git lfs ls-files` before committing. An +870 MB blob committed outside LFS has to be undone by rewriting history. + +**Every generated file here comes from `release/solomon-release.json`.** Editing a +generated file by hand breaks the manifest hash and is caught by +`ops/solomon_package.py --enforce` as `DRIFTED_SINCE_STAGING`. Change the descriptor +or the generator and re-stage. + +**A calibration may not be carried onto a different adapter.** The temperatures were +fitted on this model's logits. Refit and register them for a new adapter, or ship no +calibration and serve at T = 1.0, which is always permitted. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..9f9515fc00cb1bbf70ff9aca90f8eb7a1f8d165b --- /dev/null +++ b/NOTICE @@ -0,0 +1,98 @@ +Solomon v1.1.0 +Copyright 2026 Doccy Pty Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this work except in compliance with the License. You may obtain a copy of the +License in the LICENSE file distributed with this work, or at + + http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- +ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c)) +-------------------------------------------------------------------------------- + +This work is a DERIVATIVE WORK of: + + Qwen/Qwen3.8-27B + Copyright 2026 Alibaba Cloud + Licensed under the Apache License, Version 2.0 + Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 + Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes, + the exact bytes served at the pinned revision) + +The base model weights are NOT redistributed in this repository. They are +referenced by the pinned revision above and downloaded by the operator directly +from the upstream repository under the upstream licence. + +Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the +pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials). +Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No +upstream attribution text has been invented or paraphrased. + +"Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model +this adapter was trained against. Apache-2.0 section 6 grants no trademark +rights and none are claimed or implied. Nothing here states or implies any +endorsement, sponsorship or affiliation. + +-------------------------------------------------------------------------------- +STATEMENT OF CHANGES (Apache License 2.0, section 4(b)) +-------------------------------------------------------------------------------- + +No upstream source file is modified, and no upstream file is redistributed +except the unmodified licence text at licenses/Qwen-Apache-2.0.txt. + +The modification this work carries is a trained LoRA adapter and a set of +trained linear answer heads, applied to the base model at inference time: + + * LoRA adapter, rank 64, question-side placement, float32 + adapter/adapter.safetensors + sha256 d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0 + 870363376 bytes + * Trained linear answer heads + adapter/heads.npz + sha256 f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab + 1643162 bytes + * Readout calibration (one positive scalar per task) + serving/readout-temperature-v3.json + file sha256 1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9 + payload sha256 945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b + +The v1.1 adapter and heads were trained on 21 September 2026 (v1.0: 16 to 20 September 2026). The adapter and the heads +are identified by the checksums above. Full change detail is in MODIFICATIONS.md. + +-------------------------------------------------------------------------------- +THIRD-PARTY CONTENT IN THIS REPOSITORY +-------------------------------------------------------------------------------- + +The only third-party content distributed here is the unmodified Apache License +2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a). + +No training document, evaluation panel, dataset, corpus, rendered page, cached +state, score archive or acceptance fixture is distributed. Every file in this +repository was scanned for text originating in third-party source documents +before release; see MODIFICATIONS.md, "Third-party text scan". + +Everything else in this repository -- the serving code under src/, the adapter +and head weights, the calibration artifact, the serving binding and the +documentation -- is original work of Doccy Pty Ltd, licensed under +Apache-2.0. + +-------------------------------------------------------------------------------- +TRAINING-DATA PROVENANCE +-------------------------------------------------------------------------------- + +The v1.1 adapter was trained on real public documents (Australian government +pages under CC BY 4.0, UK Crown copyright under the Open Government Licence v3.0, +and US federal government works) and on synthetic documents, some of which were +produced by editing third-party natural texts. None of those texts, and no +document, panel or dataset built from them, is distributed here. The real-document +counts and licences are summarised in README.md, "Training data (v1.1)"; the +synthetic-document sources are listed with their titles, URLs and recorded +licences in README.md, "Training-data provenance", including the three recorded +as CC BY-SA 4.0. + +That listing is provenance disclosure and attribution as good practice. It is not +a statement that the trained weights are a derivative work or an adaptation of +those texts. Whether share-alike terms reach model weights is unsettled; this +package takes no position on it and the owner accepted the residual risk on +21 September 2026 rather than resolving it. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7a93b1e72517c84ddcbfd722f6c1e213eb96685e --- /dev/null +++ b/README.md @@ -0,0 +1,661 @@ +--- +license: apache-2.0 +base_model: Qwen/Qwen3.8-27B +base_model_relation: adapter +library_name: peft +pipeline_tag: text-classification +tags: + - lora + - document-question-answering + - structured-decisions + - calibration + - synthetic-evaluation +--- + + + +# Solomon + +A LoRA adapter and trained answer heads for **Qwen/Qwen3.8-27B** that turn a document plus a set of +structured questions into one probability per decision, each with a retrieval pointer to where the +support for it plausibly sits in the source. It does not generate text. + +> ### Read this before you trust a number on this page +> +> **v1.1 was measured on real documents, and the headline gain is not statistically established.** On a +> held-out panel of 802 questions over +> 54 real documents it answers +> **706** whole +> questions right against 679 +> for v1.0: +3.4 points, 95% document-bootstrap interval +> [-1.3, +7.4]. +> The interval includes zero. +> +> **The evaluation labels are AI-generated and have not been checked by a human.** See **Evaluation labels**. +> +> Nothing on this page is a certified error rate and nothing is guaranteed. + +## What it is, and what it is for + +Give it a document once and ask structured questions against it. Each answer comes back as a +probability. On request it also returns **ranked pointers** — the three sentences an experimental relevance +head scores highest — as a place to start reading, not as the reason for the answer (**Evidence**, below). +There is no chat, no reasoning trace and no sampling: every answer is read from letter logits at a fixed +position through trained linear heads, so the same document and the same question return the same +numbers every time. + +Four answer types: + +| Type | Question shape | What comes back | +|---|---|---| +| Yes / no | does the document establish X? | one probability | +| Single choice | which of these does it state? | one probability over the listed options | +| Ordered choice | which threshold does it state? | one probability over the ordered options | +| Multi-label | which of these apply? | **one probability per candidate** | + +**v1.1 removed the entity answer type.** "Which of these parties is the X?" is a yes/no question with the +party written in: ask one yes/no question per candidate, or send the parties as multi-label candidates. An +old entity request (`candidate_kind: "entity"`, or a `{candidate}` placeholder) is answered with a 400 that +says exactly this. + +**It is for** turning documents into structured, machine-readable answers where you need a number +attached to each one, and where determinism and a refusal to drift matter more than fluency. + +**It is not for** general knowledge question-answering, chat, generation, or summarisation. It is not +for any setting where a wrong answer is costly and cannot be checked: see **Limitations**. + +## How it works + +| | | +|---|---| +| Base model | `Qwen/Qwen3.8-27B`, Apache-2.0, pinned revision `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`. **Not redistributed here** | +| Adaptation | LoRA, rank 64, **question-side** placement, float32 | +| Answer projection | trained linear heads (`adapter/heads.npz`), not the language-model head | +| Readout | structured letter logits; yes/no-shaped units collapse to a binary log-odds before temperature | +| Calibration | one scalar per answer type; **all 1.0 (unscaled) in v1.1** | +| Runtime identity | a 21-key binding (BF16; one binding per precision) that refuses to load if the engine is not the one measured | + +**The document is prefilled once; the questions branch off it.** The document goes through the model a +single time and becomes a reusable state (`POST /states`). Every question is then answered as an +isolated branch off that prefix. Two consequences are worth stating because they are tested on real +hardware and not merely intended: asking the same questions in a different order returns the same +answers, and answering from the cached document state matches a full forward pass with the same decision and +within 0.05 in probability (the v1.1 acceptance runs: BF16 full, int8 subset; largest difference +0.0108 in BF16, and 0.0097 in an earlier BF16 +run). That limit is looser than the 1e-3 used for fp32, because BF16 differences of about 0.01 are expected; the +fp32 configuration was not re-run through acceptance for v1.1. + +**The adapter is off while the document is read, and on from the question onward.** That is what +question-side placement means. Applying it across the whole sequence gives a different model to +the one that was measured, and the identity binding exists partly to stop that happening by accident. + +**The readout is structured, not generated.** Rather than sampling an answer and parsing it, the model +is asked to commit at a fixed position and the letter logits at that position are read through trained +heads (readout mode `four_collapsed`). Yes/no-shaped units — a yes/no question, and each individual candidate +inside a multi-label answer — are read through **one merged yes/no head** and collapsed to a single binary +log-odds, `p = sigmoid(z / T)` with `z = log P(yes)/P(no)`, before the temperature is applied. The head is +shared, and results are reported per type. Choice questions apply the temperature to the listed slice, +`softmax(logits[:n] / T)`. **v1.1 serves every type at T = 1.0** (boolean 1.0 · multilabel 1.0 · single 1.0 · ordered 1.0): per-type temperatures were fitted on +the real dev panel and did not improve held-out calibration, so none is applied (see **Calibration**). + +A branch whose `head_key` is not in the calibration artifact's map is **refused, not served at an +assumed 1.0**. + +**The temperatures live inside the runtime binding**, covered by its checksum. A calibration fitted on +one model may not be served on another: the loader refuses by name rather than serving scalars that +mean nothing. Serving at temperature 1.0 everywhere is always permitted, on any model. + +### What the returned score means + +`ordering_score` means two different things depending on the question, and the difference matters. + +- **Single-unit** — a yes/no question, a single or ordered choice, and **every per-candidate value** + inside a multi-label answer. Here the score *is* the readout probability (unscaled in v1.1); how well that + magnitude holds on real documents is measured under **Reliability on real documents**, below. +- **Multi-unit** — the rolled-up question-level score for a multi-label question with more + than one candidate. It is the **product** of the per-candidate probabilities, which assumes those + candidates are independent. **That assumption has never been validated as a joint probability.** It + orders such questions well; it is a heuristic ordering, not a calibrated joint. If you need a + magnitude for one of these, read the per-candidate numbers. + +The field is not called `probability` because that would be accurate for the first case and an +overclaim for the second. + +There is **no abstention**. Every question is answered. The service will not emit a field named +`abstain`, `confidence`, `threshold` or `certified_error_rate`; it raises rather than return one. If +you want to decline low-confidence answers, that is your policy, made on your population, and this +release makes no claim about where to put the line. + +## How to run it + +The package is a library, not a daemon: you build the engine, wrap it in the serving layer and start the +HTTP surface in four lines. **The reference CUDA configuration is BF16**: base weights in bfloat16 with the +linear-attention recurrence promoted to float32 (`precision='bf16'`, the configuration the model was trained +in). The v1.1 BF16 and fp32 measurements on this card were run on NVIDIA B200 GPUs. Two alternatives are selectable, each with +its own measured numbers below and its own identity: `precision='fp32'` (float32 weights and attention, +float64 recurrence; roughly twice the memory) and `precision='int8'` (weight-only 8-bit decoder linears via +torchao, compute in bf16; the smallest footprint, not faster: on an NVIDIA RTX A6000 +(48 GB) it held 29.2 GiB after load and peaked at +32.8 GiB on the real test text documents and +40.4 GiB on page images). The binding pins the precision, so a +configuration can only be served against numbers measured on it. **The Apple-silicon MLX package in `mlx/` has +not been updated for v1.1**: it is pinned to the v1.0 revision of this repository and loads the v1.0 adapter, +heads and calibration, not the files described on this card. It is experimental, and none of the numbers on this +card describe it. + +```sh +pip install -r requirements.lock + +huggingface-cli download DoccyHealth/Solomon --local-dir ./solomon +huggingface-cli download Qwen/Qwen3.8-27B --revision 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 --local-dir ./solomon/base + +cd solomon +export PYTHONPATH=$PWD/src +``` + +Run from inside that directory: the engine looks for the base model in `base/`, and the serving layer +reads `serving/selection.json` and the binding it names, both relative to the package root. + +```python +from solomon import api, service +from solomon.serving import ServiceEngine + +engine = ServiceEngine('adapter/adapter.safetensors', 'adapter/heads.npz') # precision='bf16' (default) | 'fp32' | 'int8' +layer = service.service('./store', engine) # './store' holds cached document states +# fp32 / int8: build the engine with that precision AND pass its binding, e.g. +# engine = ServiceEngine(..., precision='int8') +# layer = service.service('./store', engine, binding='serving/serving-binding-int8.json') +server = api.serve(layer) # GET /health, POST /states, POST /v1/decide +print('http://127.0.0.1:%d' % server.server_port) +server.serve_forever() # or skip the server and call layer.decide(...) directly +``` + +`ServiceEngine` also takes `expected_adapter=` and `expected_heads=`, the two sha256 values printed +below; pass them and the engine refuses to start on a file that is not the one this card describes. + +### Asking questions + +One request carries a document (or a saved `state_id`) and any number of questions. The four shapes: + +```jsonc +POST /v1/decide +{ + "state": "…the document text…", + "evidence": "support", // none | support | sufficiency | removal + "questions": { + "certified": "Is Rookwood Ltd certified to supply produce?", // yes / no + "growers": {"type": "noul", "instructions": "Which growers may supply produce?", + "candidates": ["Rookwood Ltd", "Ostler Ltd"]}, // multi-label + "scheme": {"type": "choice", "instructions": "Which certification scheme applies?", + "options": ["Freshcare", "SQF", "GlobalG.A.P."]}, // single choice + "risk": {"type": "choice", "instructions": "What supply risk rating is recorded?", + "options": ["low", "medium", "high"], "ordered": true} // ordered choice + } +} +``` + +```jsonc +{ + "answers": { + "certified": {"type": "noul", "noul": 0.97, "ordering_score": 0.97, "temperature": 1.0, + "evidence_method": "trained_relevance_head_ranked", "evidence": [{"start": 212, "end": 256, "text": "…", "score": 0.83, "rank": 1}, …], "evidence_suppressed": false}, + "growers": {"type": "noul", "candidate_kind": "label", + "candidates": {"Rookwood Ltd": 0.96, "Ostler Ltd": 0.04}, + "candidate_ordering_scores": {"Rookwood Ltd": 0.96, "Ostler Ltd": 0.96}, + "candidate_evidence": {"Rookwood Ltd": [{"start": 212, "end": 256, "text": "…", "score": 0.91, "rank": 1}, …], + "Ostler Ltd": [{"start": 257, "end": 309, "text": "…", "score": 0.77, "rank": 1}, …]}, + "candidate_evidence_suppressed": {"Rookwood Ltd": false, "Ostler Ltd": false}}, + "scheme": {"type": "choice", "answer": "Freshcare", "probabilities": {"Freshcare": 0.91, "SQF": 0.06, "GlobalG.A.P.": 0.03}}, + "risk": {"type": "choice", "ordered": true, "answer": "medium", "probabilities": {"low": 0.12, "medium": 0.81, "high": 0.07}} + } +} +``` + +(Illustrative numbers, not measurements.) The entity form `{"instructions": "Is {candidate} a grower?", +"candidates": [...]}` was removed in v1.1: write `"growers"` above, or one yes/no question per party. + +**Evidence.** At `evidence: "support"` the response carries up to three ranked sentence pointers with scores +from an experimental relevance head, per answer branch. `evidence_method` names the selector: +`trained_relevance_head_ranked`, or `lexical_overlap_fallback` (word overlap, used for page images and whenever +the head is not configured, with `evidence_fallback_reason` saying why). Neither establishes that the +answer was caused by the span (`evidence_faithfulness_established: false`). See **Evidence (experimental)**. + +`GET /health` reports the contract, the readout mode and the runtime identity. If the engine you built +differs from the one the numbers were measured on — a different torch build, a different adapter, a +different arithmetic mode — **the binding refuses to load rather than quietly serving different +numbers**. That is intended behaviour; do not work around it. + +### What is in this repository + +``` +README.md this file +LICENSE Apache-2.0 +NOTICE attribution and the Apache-2.0 4(b)/4(c) notices +MODIFICATIONS.md statement of changes, third-party text scan, maintainer notes +MANIFEST.json every file, its size and its sha256 +requirements.lock the pins of the image this was qualified on, and dependency licences +licenses/Qwen-Apache-2.0.txt the upstream licence, verbatim +adapter/adapter.safetensors the LoRA adapter +adapter/heads.npz the trained answer heads +adapter/config.json base repo, pinned revision, checksums, placement +serving/serving-binding.json runtime identity binding (bf16, default) and the frozen temperatures +serving/serving-binding-fp32.json runtime identity binding for precision=fp32 +serving/serving-binding-int8.json runtime identity binding for precision=int8 +serving/evidence-head.safetensors relevance head weights (float32) +serving/evidence-head.json relevance head config incl. lexical_residual alpha +serving/evidence-policy.json ranked-pointer serving policy (top 3, suppressed when not stated) +serving/readout-temperature-v3.json the calibration artifact, standalone +serving/selection.json readout mode and model identity +serving/service-export.json qualification envelope hashes +src/solomon/__init__.py solomon: a document plus structured questions in, one probability per decision out +src/solomon/api.py HTTP surface for the Solomon layer (solomon/service.py) +src/solomon/binding.py serving identity: which readout is served, and proof that it is the one that was measured +src/solomon/calibration.py the readout calibration: one positive scalar per answer type, and nothing else +src/solomon/engine.py question-only CUDA runtime for immutable semantic-head checkpoints +src/solomon/engine_contract.py the answer contract engine: contract v3 on the reference engine's float32 cached path +src/solomon/engine_cuda.py CUDA contract-v3 engine +src/solomon/engine_numerics.py versioned CUDA repair: bounded FP32 attention, FP64 recurrent accumulation +src/solomon/engine_reasoning.py the confidence layer text reasoning on immutable answer-contract base-prefix states +src/solomon/engine_reference.py the reference engine: task-agnostic document prefix, float32 arithmetic, chunked prefill +src/solomon/evidence.py deterministic source references and explicit evidence interventions +src/solomon/evidence_head.py v1.1 evidence head: the ONE forward shared by the trainer (the training tooling via +src/solomon/evidence_packages.py evidence packages (v3): selected spans plus source-derived governing context, per question unit +src/solomon/evidence_selector.py evidence selection for the Solomon layer (v1.1): the trained relevance head, with word overlap as a labelled fallback +src/solomon/heads.py final normalized feature extraction, preserving the qualified CUDA engine +src/solomon/prompts_two_letter.py two-letter (Noul) prompts and block conversions +src/solomon/readout.py contract v3 readouts: branch jobs for every answer type, predictions from letter logits, and metrics +src/solomon/reliability.py what the readout says about its own answer, with nothing fitted behind it +src/solomon/retrieval.py inference-only source candidates and explicitly labelled retrieval baselines +src/solomon/routing.py real callback-driven escalation +src/solomon/semantics.py answer semantics (design note, not distributed) +src/solomon/service.py decision layer: the Solomon serving contract over the pinned readout chain +src/solomon/service_answers.py confidence-aware five-task service, reusing immutable answer-contract input persistence +src/solomon/service_checked.py backend-neutral contract-v3 service; restart replays immutable inputs, not tensors +src/solomon/service_evidence.py optional source-grounded evidence around the existing confidence service +src/solomon/service_heads.py trained-head fast/views service; unchanged decoder and separate stage confidence +src/solomon/service_packages.py evidence packages (v3) in the answer service: per-unit, source-grounded, page-referenced +src/solomon/service_states.py local contract-v3 prototype: task-neutral text/image states and five answer types +src/solomon/serving.py preserve five-task trained-head confidence/routing with optional evidence +src/solomon/units.py v1.1 shared sentence/list-item splitter +mlx/ the optional Apple-silicon package: its own library, tests and notices +``` + +**What is deliberately not here:** no training data, no evaluation panels, no document corpora, no +datasets, no cached states, no rendered images, no score archives, no test fixtures, no logs and no +base model weights. The package is default-deny: a manifest names every permitted file and the build +fails if anything else is present. + +The files under `serving/` carry identity only: hashes, the readout mode, the served design and the +per-type temperatures. The loader verifies the binding's checksum and every runtime key it carries. The +measured provenance behind those hashes (which panels, which fit, which qualification run) is held in +the maintainer's records and is not distributed. + +## Evidence (experimental) + +**Evidence here is a set of ranked pointers, not an explanation.** At `evidence: "support"` the service returns +the **top 3 sentences** of the document with a relevance score each (`evidence_method: +"trained_relevance_head_ranked"`). The scores come from a small relevance head fitted after training, on the +model's layer-42 states plus a word-overlap term. It reads states the answer already computed; it +makes no extra model call and **cannot change an answer**: with the head on and off, all 3,230 real +test answer branches were identical (maximum logit difference 0.0). + +- `evidence_faithfulness_established` is `false`. Nothing shows the pointed-to sentence caused the answer. +- **No pointers are returned when the answer is "not stated"** (the answer's collapsed state is not-stated, or a + choice answer resolved to the reserved not-stated option). That state is the absence signal; the head itself + has no reliable "no evidence" signal. +- On the development panel the head put a labelled supporting sentence in its top 3 more often than plain word + overlap: hit@3 0.817 against 0.669 + over 753 labelled rows. That comparison was used to choose the head, so it is + optimistic, and **no test-panel measurement exists**. The precision and recall targets for evidence were not established. +- **Word overlap is the labelled fallback** (`lexical_overlap_fallback`, with `evidence_fallback_reason`), used for + page-image documents and whenever the head is not configured. + +Treat pointers as a place to start reading and verify them yourself. + +## What it scores + +Every figure in this section was measured on the adapter this repository ships +(`d122466d430a…`) in the BF16 reference configuration unless stated. The scoring runs +loaded a heads file (`96ea51416bbe…`) that is the shipped `adapter/heads.npz` +(`f766d752d776…`) plus two legacy slots that were never read; every array the two files share is +byte-identical, so the served logits are the measured logits. The packager checks those hashes against the measurement records and refuses to +build if they differ. Figures for v1.0 are that model re-scored on the same panels, for comparison only. + +### Real documents + +Real public documents (government notices, policies, agreements, correspondence, minutes and similar), split by +document into training, dev and test. Test documents were never trained on and never used for any fit. + +| Panel | Questions / documents | This model | v1.0 | External reference¹ | v1.1 − v1.0, points [95% CI] | +|---|---|---|---|---|---| +| Test | 802 / 54 | **706 (88.0%)** | 679 (84.7%) | 690 (86.0%) | +3.4 [-1.3, +7.4] | +| Dev | 624 / 15 | **555 (88.9%)** | 529 (84.8%) | 553 (88.6%) | +4.2 [+1.0, +7.5] | + +¹ a commercial structured-decision API (external reference), scored on the same questions and labels. + +Intervals are document-cluster bootstrap (2,000 resamples). **On test the interval includes zero**: the release +rule asked for a lower bound of −1.0 points and the measured bound is +-1.3. The owner accepted this miss for v1.1; see **Release decisions**. + +Per answer type, real test: + +| Answer type | Questions | This model | v1.0 | External reference | v1.1 − v1.0, points [95% CI] | +|---|---|---|---|---|---| +| Yes / no | 149 | 132 / 149 | 132 / 149 | 135 / 149 | +0.0 [-10.1, +7.2] | +| Party-role questions (formerly entity; now asked as yes/no per party) | 213 | 185 / 213 | 165 / 213 | 180 / 213 | +9.4 [+0.0, +19.2] | +| Multi-label | 226 | 191 / 226 | 190 / 226 | 184 / 226 | +0.4 [-4.8, +5.9] | +| Ordered choice | 109 | 95 / 109 | 90 / 109 | 91 / 109 | +4.6 [-1.9, +10.7] | +| Single choice | 105 | 103 / 105 | 102 / 105 | 100 / 105 | +1.0 [+0.0, +3.0] | +| Multi-label, per candidate (slots) | 1,204 | 96.5% | 95.8% | not tallied | +0.7 [-1.0, +2.3] | +| Party-role, per party (slots) | 1,329 | 97.2% | 94.9% | not tallied | +2.3 [+0.4, +4.1] | + +Counts are whole questions right, with the same question definition applied to all three models. + +### Page images against text + +The same 802 real test questions, asked from rendered page images instead +of extracted text: 713 +whole questions right from images against 706 +from text, with 98.5% of 2,936 +answer branches agreeing between the two. + +### Natural images + +A panel of photographs and pictures with structured questions (no document text). Two populations are reported +and they are different numbers: + +- **Per answer unit** (731 units): this model 97.3%, + v1.0 96.2%, base Qwen 95.5%. + Answers stated at 0.99 or above that were wrong: this model 0.0% + of 293, base Qwen 1.5% of + 401. +- **Whole questions** (415): this model 95.2%, v1.0 + 93.3%, base Qwen 92.0%. + +### General knowledge, with no document (out of domain) + +800 multiple-choice items, 400 from MMLU and 400 from MMLU-Pro, every model on the same items and prompt. + +| Model | Accuracy (800) | Answers stated ≥ 0.99 | Of those, wrong | ECE (top label, 15 bins) | +|---|---|---|---|---| +| **This model (v1.1)** | 72.9% | 24 | 0 (0.0%) | 0.052 | +| v1.0 | 72.8% | 401 | 20 (5.0%) | 0.146 | +| External reference¹ | 87.1% | 314 | 5 (1.6%) | 0.040 | +| Base Qwen, same prompt | 71.8% | 187 | 4 (2.1%) | 0.049 | + +All four rows come from one computation on the same 800 items. Probabilities are unscaled (T = 1) for every row, +which is how v1.1 serves them. + +v1.0 stated half of its answers at 0.99 or above and was wrong on 1 in 20 of them. This model almost never +claims 0.99 on general knowledge. MMLU moved +1.5 points and +MMLU-Pro -1.25 points against v1.0; the release rule allowed at +most 1 point either way, so **MMLU-Pro missed it**. The owner accepted this miss for v1.1. + +### Reliability on real documents + +Real test, text, every answer unit (each option or candidate scored against its label): of the probabilities +stated in each band, the share that were actually right. A calibrated model's column would track the band. + +| Stated P(yes) | this model | external reference | +|---|---|---| +| 0.00–0.01 | 0.0% (n=2,095) | 0.1% (n=2,619) | +| 0.01–0.02 | 0.8% (n=1,038) | 0.7% (n=305) | +| 0.02–0.05 | 3.2% (n=569) | 1.8% (n=325) | +| 0.05–0.10 | 14.4% (n=132) | 3.0% (n=202) | +| 0.10–0.20 | 46.0% (n=87) | 7.5% (n=213) | +| 0.20–0.30 | 51.6% (n=31) | 18.1% (n=116) | +| 0.30–0.40 | 18.2% (n=22) | 28.6% (n=63) | +| 0.40–0.50 | 66.7% (n=18) | 30.4% (n=56) | +| 0.50–0.60 | 47.1% (n=17) | 39.5% (n=43) | +| 0.60–0.70 | 61.5% (n=13) | 55.6% (n=63) | +| 0.70–0.80 | 53.1% (n=32) | 68.6% (n=70) | +| 0.80–0.90 | 83.7% (n=43) | 82.9% (n=111) | +| 0.90–0.95 | 89.3% (n=84) | 91.0% (n=89) | +| 0.95–0.98 | 97.3% (n=295) | 92.5% (n=106) | +| 0.98–0.99 | 99.6% (n=485) | 95.6% (n=90) | +| 0.99–1.00 | 100.0% (n=126) | 99.5% (n=616) | + +The top end is at or above the external reference. **The low end under-calls**: answers stated at 5–20% are yes +more often than stated. (This table is the unscaled readout, which is what v1.1 serves.) + +### Calibration + +Per-type temperatures were fitted on the real dev panel (boolean 0.8175 · multilabel 0.8423 · single 1.1077 · ordered 1.2562) and checked on held-out real test. +**They did not improve held-out calibration**: test ECE got worse in 8 of 10 answer-type × modality +cells, and the question-weighted ECE across all cells was 0.0212 with the fitted temperatures against +0.0199 unscaled. Only multi-label improved. **v1.1 therefore ships unscaled probabilities (T = 1.0 for +every type).** Real test ECE per cell (10 bins): + +| Answer type | Input | Units | ECE, unscaled (served) | ECE, dev-fitted temperature | Meets 0.03 target | +|---|---|---|---|---|---| +| yes/no | image | 149 | **0.055** | 0.068 | **no** | +| yes/no | text | 149 | **0.067** | 0.081 | **no** | +| party-role (per party) | image | 1,329 | **0.009** | 0.020 | yes | +| party-role (per party) | text | 1,329 | **0.011** | 0.013 | yes | +| multi-label (per candidate) | image | 1,204 | **0.020** | 0.012 | yes | +| multi-label (per candidate) | text | 1,204 | **0.024** | 0.013 | yes | +| ordered | image | 109 | **0.073** | 0.086 | **no** | +| ordered | text | 109 | **0.054** | 0.077 | **no** | +| single | image | 105 | **0.022** | 0.053 | yes | +| single | text | 105 | **0.014** | 0.040 | yes | + +The 0.03 target is **missed for yes/no** (0.067 text, +0.055 image) **and ordered choice** (0.054 text, +0.073 image). Those cells have only +105–149 questions each (yes/no, single and ordered). Party-role rows are +the former entity questions, now asked as one yes/no question per party. + +### Precision configurations + +Real test, text, 3,230 answer branches. Accuracy is whole +questions; flips are served decisions that differ from BF16. + +| Configuration | Status | Whole-question accuracy | vs BF16, points [95% CI] | Decisions flipped vs BF16 | ECE (unscaled) | +|---|---|---|---|---|---| +| `bf16` | **default, reference** | 88.03% | — | — | 0.018 | +| `fp32` | comparison | 87.66% | -0.37 [-0.77, +0.00] | 0.10% | 0.016 | +| `int8` | option | 87.53% | -0.50 [-1.02, +0.00] | 0.27% | 0.016 | + +`int8` is weight-only 8-bit (torchao) with bf16 compute. **`int8` page-image accuracy has not been scored against +the labels**; a decision-agreement run against fp32 on page images agreed on +99.71% of served decisions. On page +images `fp32` scored 88.78% against BF16 +88.90%. No speed claim is made for any configuration. + +### Failure modes (synthetic probes) + +6,000 generated questions across 38 targeted failure modes, paired +against v1.0. Most modes are flat. Modes whose interval excludes zero: + +| Mode | Questions | v1.0 | This model | Difference, points [95% CI] | +|---|---|---|---|---| +| indirect reference | 160 | 80.6% | 72.5% | -8.1 [-13.1, -3.8] | +| opposite polarity question | 135 | 94.1% | 97.0% | +3.0 [+0.7, +5.9] | + +- **Paraphrase agreement** on yes/no questions: 0.951 (v1.0 + 0.946); the target was 0.98 and is not met. +- **Adversarial confident flips** (answer changed at ≥ 0.9 by an injected instruction, false summary or + self-classifying text): 2.0% (v1.0 2.1%); + the target was 1% and is not met. +- **Per-type calibration on real test** misses the 0.03 ECE target for yes/no and ordered questions (see + **Calibration**, below; reported, not blocking). + +## Limitations + +- **Evaluation labels are not human-verified.** Every real-document reference label was produced by AI + labellers: two blind passes plus adjudication, with 99% agreement on binary slots between the passes. The owner + decided to release v1.1 without a human label review. Some measured errors may be label errors, and some + measured successes may share a labeller's mistake. +- **The headline improvement is not significant** (interval includes zero) and the real panels are small: + 54 test documents. +- **The low end of the probability scale under-calls** on real documents (see the reliability table). +- **Indirect references regressed** on the synthetic probes (table above). +- **The multi-candidate roll-up is an ordering, not a joint probability.** Read per-candidate values if you + need a magnitude. +- **Nothing here is a certified error rate.** No threshold is enforced anywhere on the serving path. +- **Page images:** measured on the real test panel in BF16 and fp32 only. + +## Release decisions + +Two release rules were missed and **both were accepted by the owner for v1.1**: + +1. Real test, whole questions: lower bound of the 95% interval -1.3 + points against a rule of −1.0. +2. General knowledge: MMLU-Pro -1.25 points against a limit + of 1 point (MMLU +1.5). + +## Evaluation labels + +Reference labels on the real panels are AI-generated (two blind passes plus adjudication) and **have not been +reviewed by a human**. No Claude or GPT output is used anywhere as training input. + +## Training data (v1.1) + +**Real documents.** 200 real public documents: 160 collected for this release plus +40 from an earlier evaluation panel (those 40 are test-only). Split by document: 113 train, +24 dev, 63 test (the evaluation panels above use the labelled subset). Licences of the +160 collected documents, as recorded at collection: 59 Australian government pages under CC BY 4.0, +76 UK Crown copyright under the Open Government Licence v3.0, 25 US federal government works (public domain). +No document is distributed here. + +**Where the training labels came from.** + +- Real-document training labels: Qwen3.8 2.4T (open weights), called through OpenRouter and routed to + third-party hosts serving full-precision weights, not the Alibaba API. +- Anchor targets from the unmodified base Qwen model, so general behaviour does not drift. +- Code generators for the synthetic documents and targeted failure-mode questions. +- Replay of the v1.0 training data (whose third-party sources are listed below). + +**No Claude or GPT output is ever training input or a training label.** The build enforces this with an +allow-list of row producers. + +## Training-data provenance (third-party texts in the synthetic documents) + +**No training document, panel, corpus or source text is distributed in this repository.** +The adapter was trained on synthetic documents, and some of those documents were produced by **editing +third-party natural texts**. Those texts are listed here so that their provenance is on the record, and +so that a reviewer doing lawful-sourcing diligence can see what was used without having to ask. + +The model card discloses the provenance of the third-party texts the training panel was edited from, and attributes them. This is provenance disclosure and attribution as good practice, and it supports documented-lawful-sourcing procurement review. It is NOT a concession that the trained weights are a derivative work or an adaptation of those texts; that question is open and nothing in this package answers it. + +13 of the 42 reviewed sources were used in the training panel: + +| | Source | URL | Licence, as recorded | +|---|---|---|---| +| | Django's security policies | [https://docs.djangoproject.com/en/dev/internals/security/](https://docs.djangoproject.com/en/dev/internals/security/) | BSD 3-Clause (Django project LICENSE, which covers the documentation in the django/django repository) | +| **CC BY-SA** | Wikipedia:Arbitration/Policy (English Wikipedia arbitration policy) | [https://en.wikipedia.org/wiki/Wikipedia:Arbitration/Policy](https://en.wikipedia.org/wiki/Wikipedia:Arbitration/Policy) | CC BY-SA 4.0 (Wikipedia text; attribution: English Wikipedia contributors, 'Wikipedia:Arbitration/Policy') | +| | Common Paper Mutual Non-Disclosure Agreement, Version 1.0 – Standard Terms | [https://commonpaper.com/standards/mutual-nda/1.0/](https://commonpaper.com/standards/mutual-nda/1.0/) | CC BY 4.0 (stated in the agreement footer and in the CommonPaper/Mutual-NDA repository README: 'free to use and modify under CC BY 4.0') | +| | 36 CFR Part 2 (National Park Service) - Resource Protection, Public Use and Recreation: sections 2.10, 2.13, 2.14, 2.15, 2.16, 2.21 and 2.22 | [https://www.ecfr.gov/current/title-36/chapter-I/part-2](https://www.ecfr.gov/current/title-36/chapter-I/part-2) | US Government work (public domain): Code of Federal Regulations text, not subject to copyright (17 U.S.C. 105) | +| | NSF Proposal & Award Policies & Procedures Guide (PAPPG, NSF 24-1), Chapter IV: Non-Award Decisions and Transactions | [https://www.nsf.gov/policies/pappg/24-1/ch-4-non-award-decisions-transactions](https://www.nsf.gov/policies/pappg/24-1/ch-4-non-award-decisions-transactions) | US Government work (public domain): U.S. National Science Foundation policy guide, not subject to copyright (17 U.S.C. 105) | +| | GOV.UK: Make a court claim for money | [https://www.gov.uk/api/content/make-court-claim-for-money](https://www.gov.uk/api/content/make-court-claim-for-money) | Open Government Licence v3.0 | +| | 42 CFR 68: NIH Loan Repayment Programs (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title42-vol1/xml/CFR-2025-title42-vol1-part68.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title42-vol1/xml/CFR-2025-title42-vol1-part68.xml) | US Government work (public domain) | +| **CC BY-SA** | WordPress.com (Automattic) Terms of Service, last updated April 10, 2026 | [https://github.com/Automattic/legalmattic/blob/master/Terms%20of%20Service/WordPress.com/EN-Terms-of-Service.md](https://github.com/Automattic/legalmattic/blob/master/Terms%20of%20Service/WordPress.com/EN-Terms-of-Service.md) | CC BY-SA 4.0 (Automattic/legalmattic LICENSE.txt and README; the Terms themselves state they are available under a Creative Commons Sharealike license) | +| **CC BY-SA** | GitLab Handbook: Global Travel and Expense Policy (sections 1 to 3) | [https://handbook.gitlab.com/handbook/finance/expenses/](https://handbook.gitlab.com/handbook/finance/expenses/) | CC BY-SA 4.0 (GitLab handbook content; licence badge in the footer of handbook.gitlab.com pages) | +| | Sourcegraph Handbook: Spending company money | [https://github.com/sourcegraph/handbook/blob/main/content/benefits-pay-perks/benefits-perks/spending-company-money.md](https://github.com/sourcegraph/handbook/blob/main/content/benefits-pay-perks/benefits-perks/spending-company-money.md) | Apache License 2.0 (LICENSE of the public sourcegraph/handbook repository) | +| | Project Jupyter Governance: Executive Council | [https://raw.githubusercontent.com/jupyter/governance/main/docs/executive_council.md](https://raw.githubusercontent.com/jupyter/governance/main/docs/executive_council.md) | CC0 1.0 Universal (jupyter/governance repository LICENSE.md) | +| | 29 CFR 1904.30–1904.34: establishment records and annual summaries (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title29-vol5/xml/CFR-2025-title29-vol5-part1904.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title29-vol5/xml/CFR-2025-title29-vol5-part1904.xml) | US Government work (public domain) | +| | 31 CFR 1.2, 1.4 and 1.6: Treasury FOIA requests and appeals (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title31-vol1/xml/CFR-2025-title31-vol1-part1.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title31-vol1/xml/CFR-2025-title31-vol1-part1.xml) | US Government work (public domain) | + +**3 of these are recorded as CC BY-SA 4.0** — *Wikipedia:Arbitration/Policy (English Wikipedia arbitration policy)*, *WordPress.com (Automattic) Terms of Service, last updated April 10, 2026*, *GitLab Handbook: Global Travel and Expense Policy (sections 1 to 3)*. Share-alike is the one term +attribution cannot cure. Whether a share-alike obligation can propagate through training into model +weights **is legally unsettled**; there is no authority settling it in either direction, and the +project's own licence review explicitly declines to infer one. The owner of this release +**accepted that residual risk on 2026-09-21 rather than resolving +it**, and kept this package under Apache-2.0. A reader should treat the question as open, not answered. + +**On the strength of this evidence.** The licences above are **as recorded by the person who collected +each source**, from the source's own stated terms at the time of collection. The review records +`evidence_level: "authoring metadata assertion, not archived governing licence text"` and +`upstream_terms_independently_verified: false` for every row. No governing licence text was archived +alongside most of these sources, and this listing should not be read as a licence audit. + +Listing these sources is **provenance disclosure and attribution as good practice**. It is not a +statement that the trained weights are a derivative work, an adaptation, or a copy of any of these +texts. + + +## Third-party dependency licences + +`requirements.lock` names the packages the serving layer needs. **None of them is redistributed in +this repository** — you install them yourself from their own publishers — so Apache-2.0 section 4(a) +imposes no bundled-notice obligation here and no dependency licence text is packaged. This summary +exists because a reviewer will ask for one. + +Each licence in the table is the one declared in that distribution's OWN package metadata -- the `METADATA` file of an installed wheel -- read from a copy on the maintainer's machine. Where no copy existed, the row says NOT VERIFIED instead of guessing. + +| Package | Pinned as | Licence, as declared by the distribution itself | +|---|---|---| +| `torch` | 2.13.0 | BSD-3-Clause — read from version 2.8.0 | +| `torchvision` | 0.28.0 | NOT VERIFIED | +| `transformers` | 5.17.0 | Apache 2.0 License | +| `flash-linear-attention` | 0.5.2 | NOT VERIFIED | +| `safetensors` | unpinned in the qualified image | Apache Software License — read from version 0.8.0 | +| `accelerate` | unpinned in the qualified image | Apache (Apache Software License) — read from version 1.15.0 | +| `numpy` | unpinned in the qualified image | BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0 — read from version 2.5.3 | +| `scipy` | unpinned in the qualified image | BSD License (classifier); the METADATA `License` field carries the Enthought / SciPy Developers copyright line rather than an SPDX identifier — read from version 1.18.1 | +| `pillow` | unpinned in the qualified image | MIT-CMU — read from version 12.3.0 | + +**Two rows say NOT VERIFIED and mean it.** A licence read from one installed version is evidence about that version only. Where the table names a version different from the pin, that is the version whose metadata was actually read, and the pinned version's own metadata could differ. Treat this table as a starting point for +your own review, not as a legal opinion, and re-check the distributions you actually install. + +Full evidence paths for each row are in the release descriptor +(`release/solomon-release.json` → `dependency_licences`), which is not distributed; the same +information is repeated in the comments of `requirements.lock`. + + +## Licence and attribution + +Copyright 2026 Doccy Pty Ltd. + +This repository is licensed **Apache-2.0** — the adapter and head weights, the calibration artifact, +the serving code and the documentation alike. See `LICENSE` and `NOTICE`. + +It is a **derivative work** of `Qwen/Qwen3.8-27B`, Copyright 2026 Alibaba Cloud, licensed under Apache-2.0. +The upstream licence text is reproduced verbatim at `licenses/Qwen-Apache-2.0.txt` (sha256 +`bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`). `NOTICE` carries the attribution required by section 4(c) and +`MODIFICATIONS.md` the statement of changes required by section 4(b). + +No `NOTICE` file exists in the upstream repository at the pinned revision (HTTP 404, checked +2026-09-18), so section 4(d) carries nothing forward and no upstream attribution text has been +invented. + +"Qwen" and "Alibaba Cloud" are used nominatively to identify the base model. Apache-2.0 section 6 +grants no trademark rights and none are claimed. No endorsement or affiliation is implied. + +## Verify what you downloaded + +```sh +shasum -a 256 adapter/adapter.safetensors # d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0 +shasum -a 256 adapter/heads.npz # f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab +shasum -a 256 serving/readout-temperature-v3.json +# -> 1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9 +``` + +`MANIFEST.json` carries the size and sha256 of every file in this repository. + +The calibration artifact has **two legitimate and different hashes**, and confusing them makes a sound +provenance chain look tampered with. `1a2285d8…` is the *file* hash, what `shasum` +returns. `945bad44…` is the artifact's own internal `sha256` field, computed over its +contents with that field removed — a self-referential field cannot hash the file containing it. The +loader verifies the payload hash; use the file hash to check the file you were given. Both are recorded +in the serving binding's `provenance`, under those names. + +## Release record + +Machine-readable identity for citation and pinning. The model identity, the calibration and the runtime +binding move together; pin by revision. + +| | | +|---|---| +| Repository | `DoccyHealth/Solomon` | +| Release | `1.1.0`, 2026-09-21 | +| Serving contract | `solomon-v1` | +| Adapter sha256 | `d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0` | +| Heads sha256 | `f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab` | +| Base model | `Qwen/Qwen3.8-27B` at `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0` | +| Runtime binding sha256 (payload) | `0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8` | +| Calibration sha256 (file / payload) | `1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9` / `945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b` | +| Runtime fingerprint | `7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea` | +| Readout | `four_collapsed` | +| Served temperatures | boolean 1.0 · multilabel 1.0 · single 1.0 · ordered 1.0 | diff --git a/adapter/adapter.safetensors b/adapter/adapter.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..ff06bf0bed288b8c587014a336b806a27fb3219b --- /dev/null +++ b/adapter/adapter.safetensors @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0 +size 870363376 diff --git a/adapter/config.json b/adapter/config.json new file mode 100644 index 0000000000000000000000000000000000000000..e4f7b576cb5558e4e1cb8859c11df91e745a0318 --- /dev/null +++ b/adapter/config.json @@ -0,0 +1,45 @@ +{ + "adapter_bytes": 870363376, + "adapter_file": "adapter/adapter.safetensors", + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "answer_projection": "trained-semantic-head-float32", + "base_model_copyright": "2026 Alibaba Cloud", + "base_model_license": "Apache-2.0", + "base_model_name_or_path": "Qwen/Qwen3.8-27B", + "base_model_redistributed": false, + "base_model_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "calibration_artifact": "serving/readout-temperature-v3.json", + "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "calibration_fitted_on": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "calibration_note": "a temperature is fitted on ONE model's logits. The loader refuses to serve these scalars on any adapter other than the one named in calibration_fitted_on. Serving at T = 1.0 everywhere is always permitted on any model.", + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "contract": "solomon-v1", + "copyright": "2026 Doccy Pty Ltd", + "design": { + "ordered": "S", + "single_choice": "R" + }, + "dtype": "float32", + "heads_bytes": 1643162, + "heads_file": "adapter/heads.npz", + "heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "license": "apache-2.0", + "lora_alpha": 64, + "measured_arithmetic": "fp32", + "measured_backend": "cuda", + "model_name": "Solomon", + "peft_type": "LORA", + "placement": "question", + "placement_note": "question-side: the adapter is OFF while the document prefix is prefilled and ON from the question branch onward. Applying it to the whole sequence gives a different model to the one that was measured.", + "r": 64, + "readout": "four_collapsed", + "runtime_fingerprint": "7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea", + "schema": "solomon-adapter-config-v1", + "serving_binding": "serving/serving-binding.json", + "serving_binding_sha256": "0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8", + "task_type": "FEATURE_EXTRACTION", + "version": "1.1.0" +} diff --git a/adapter/heads.npz b/adapter/heads.npz new file mode 100644 index 0000000000000000000000000000000000000000..40631c01756405e47aae414dc7f9d5764d46719c --- /dev/null +++ b/adapter/heads.npz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab +size 1643162 diff --git a/licenses/Qwen-Apache-2.0.txt b/licenses/Qwen-Apache-2.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..f938136e3adacfd92be087f6e113b5d6d97f678f --- /dev/null +++ b/licenses/Qwen-Apache-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Alibaba Cloud + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/mlx/.gitignore b/mlx/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a6e80d61ea2bc8ef4006ab3a2c1ea9a0f6693cfe --- /dev/null +++ b/mlx/.gitignore @@ -0,0 +1,12 @@ +.venv/ +__pycache__/ +.pytest_cache/ +.ruff_cache/ +*.egg-info/ +snapshots/ +models/ +evaluations/ +*.log + +document-replay.json +dist/ diff --git a/mlx/CONVERTER-SOURCE.json b/mlx/CONVERTER-SOURCE.json new file mode 100644 index 0000000000000000000000000000000000000000..c087e586fb53c3e24f2ae19ebaff96b666839816 --- /dev/null +++ b/mlx/CONVERTER-SOURCE.json @@ -0,0 +1,21 @@ +{ + "converter_code_sha256": "810ec77e6a1e4972a942bc5bcea502183b97e048c9b317a6cc38c2cab45fcaeb", + "files": { + "__init__.py": "714a5439b33780d250c404938cd062c0d6809f83f21b59f89997bd168469925a", + "_vendor/__init__.py": "77defd15cc47661e0e9a31275e7fd59e979f3f9a6429671d8199f6002dca6c2c", + "_vendor/contract.py": "7c6607179a028f30b462349a86f9cf7d0ec2652e0a1c7c8c24649b6ce71df697", + "_vendor/evidence.py": "18a4978a27d6bbd3f7daaa34836d1820303c4ff581b0dfda877aab586392293b", + "_vendor/evidence_v3.py": "6ff3da1e920c082e9b3629ca0230007262f2ce69fd921b89dd4b3e67511b3a64", + "_vendor/prompts.py": "8bfb5a12d625664c8830f5cf243aac04d1e973bfe4f5ecfc8221021f09c318df", + "_vendor/retrieval.py": "11e7bbb1a84ba837eac3768d6e882f44a557be25d62729c8a39dd5cb870c0932", + "_vendor/semantics.py": "b580d3c114536a6faf58bcd92d2c61028f80891a19d78d6a17ed585948abfc15", + "api.py": "bae9fdd3ef6b395142ec5c099635cc9ba90f7f33c7423f99caf6009f51d4fd3c", + "artifacts.py": "0b3d102a2a04d88b2a1f3eb8a22506f626191615d779bfd53a4377824e6c239b", + "budget.py": "f72614686c19e1d923e6bc23d4353e8b1ac150863565939ae4a3831bc4ecd211", + "cli.py": "0a6be4cd03621963ec5f73a8af71224cb8b40ae88232477d8370b7d3b9b75021", + "engine.py": "13c27c3fbca576ae0d70262c2a75c9382df3b4fcfd8a11996d125750883b648a", + "evaluation.py": "bb9f50d184527e21139b9ffb5e9d1613bc30822ccd7cf06b7b7f8b1cc8b50674", + "prepare.py": "dda9879a9ddc1c3f6dcf0df52b3a244b2ca7920946bbd29ddc8e1c0f9944b887" + }, + "schema": "solomon-mlx-converter-source-v1" +} diff --git a/mlx/LICENSE b/mlx/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..cd52324cfae719548ecb79e2b4c42a3fad8bbc30 --- /dev/null +++ b/mlx/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Doccy Pty Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mlx/MODIFICATIONS.md b/mlx/MODIFICATIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..6556beceacaaeced0e887a55477bd1e4a0e27334 --- /dev/null +++ b/mlx/MODIFICATIONS.md @@ -0,0 +1,25 @@ +# Solomon MLX modifications + +This port adapts Doccy Pty Ltd’s Apache-2.0 Solomon source at revision +`5c0a4a82ddaeca6da2e3013f7045a8196c86957d`. Original modification notices are preserved in +`docs/UPSTREAM-MODIFICATIONS.md`. + +Changes: + +- Replaced CUDA execution with MLX-VLM Qwen3.5 execution for the declared Qwen3.8 architecture. +- Added BF16 shard conversion with FP32 normalization parameters, adapter, trained heads and recurrent states. +- Replaced global adapter state with instance-owned state and isolated question cache containers. +- Removed vocabulary projection from decision inference. +- Added a document-state API, replay recipes, artifact checksums and MLX-specific runtime identities. +- Extracted the original prompts, question contract, semantics and evidence utilities into `_vendor`. +- Implemented the documented ordering-score product locally because the release omits `scope9.reliability`. +- Added download, CUDA parity comparison, benchmark and test tools. New temperature fitting is outside the current scope. + +No claim is made that this port inherits CUDA calibration or qualification. See +`docs/VALIDATION-20260921.md` for measured results and outstanding validation. + +Adapter-only distribution update (21 September 2026): added a separate verified Hub +loader, atomic local assembly and cache reuse; retained the exact historical converter +source and FP32 normalization handling. Base weights are downloaded from the pinned +Qwen repository. Duplicate adapter/head files under `mlx/bf16` are replaced by the +root `adapter/` copies. CUDA parity status is reported separately from conversion. diff --git a/mlx/NOTICE b/mlx/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..9f0b076e5bf60021ab0a23f8ae433245adaf6518 --- /dev/null +++ b/mlx/NOTICE @@ -0,0 +1,78 @@ +Solomon v1.1.0 +Copyright 2026 Doccy Pty Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this work except in compliance with the License. You may obtain a copy of the +License in the LICENSE file distributed with this work, or at + + http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- +ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c)) +-------------------------------------------------------------------------------- + +This work is a DERIVATIVE WORK of: + + Qwen/Qwen3.8-27B + Copyright 2026 Alibaba Cloud + Licensed under the Apache License, Version 2.0 + Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 + Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes, + the exact bytes served at the pinned revision) + +The base model weights are NOT redistributed in this repository. They are +referenced by the pinned revision above and downloaded by the operator directly +from the upstream repository under the upstream licence. + +Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the +pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials). +Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No +upstream attribution text has been invented or paraphrased. + +"Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model +this adapter was trained against. Apache-2.0 section 6 grants no trademark +rights and none are claimed or implied. Nothing here states or implies any +endorsement, sponsorship or affiliation. + +-------------------------------------------------------------------------------- +STATEMENT OF CHANGES (Apache License 2.0, section 4(b)) +-------------------------------------------------------------------------------- + +No upstream source file is modified, and no upstream file is redistributed +except the unmodified licence text at licenses/Qwen-Apache-2.0.txt. + +The modification this work carries is a trained LoRA adapter and a set of +trained linear answer heads, applied to the base model at inference time: + + * LoRA adapter, rank 64, question-side placement, float32 + adapter/adapter.safetensors + sha256 2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca + 870363376 bytes + * Trained linear answer heads + adapter/heads.npz + sha256 126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f + 2053938 bytes + * Readout calibration (one positive scalar per task) + serving/scope9-readout-temperature-v2.json + file sha256 baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118 + payload sha256 682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278 + +Training took place between 16 and 20 September 2026. The adapter and the heads +are identified by the checksums above. Full change detail is in MODIFICATIONS.md. + +-------------------------------------------------------------------------------- +THIRD-PARTY CONTENT IN THIS REPOSITORY +-------------------------------------------------------------------------------- + +The only third-party content distributed here is the unmodified Apache License +2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a). + +No training document, evaluation panel, dataset, corpus, rendered page, cached +state, score archive or acceptance fixture is distributed. Every file in this +repository was scanned for text originating in third-party source documents +before release; see MODIFICATIONS.md, "Third-party text scan". + +Everything else in this repository -- the serving code under src/, the adapter +and head weights, the calibration artifact, the serving binding and the +documentation -- is original work of Doccy Pty Ltd, licensed under +Apache-2.0. diff --git a/mlx/README.md b/mlx/README.md new file mode 100644 index 0000000000000000000000000000000000000000..66cd0db5648a94a636d107f9b6380aefc649e876 --- /dev/null +++ b/mlx/README.md @@ -0,0 +1,146 @@ +# Solomon MLX + +Full BF16 Solomon v1.1 inference on Apple Silicon. The repository distributes +adapters and source code; the Qwen backbone is downloaded separately from its +pinned upstream revision and converted locally. No quantization or LoRA merging +is performed. Full-model measurements use an M5 Max with 128 GB memory, with +about 59.9 GB peak Metal allocation on the focused fixtures. Longer documents +and more images require additional memory; 24–32 GB Macs cannot run this profile. + +**Status: experimental.** Focused text and image decisions match CUDA. Complete +held-out parity remains pending. Qualification is CUDA parity only: no new +calibration or temperature fitting. See [validation results](docs/VALIDATION-20260921.md). + +## Install from this repository + +Use Python 3.12 or 3.13 on Apple Silicon. Pin the full repository commit shown on +Hugging Face, including after any repository history rewrite. The historical +Solomon source revision remains provenance; it is not required to be downloadable. + +```sh +# From a source checkout, enter its mlx/ directory first. +uv sync --frozen --extra dev + +# Obtain the current commit once, then retain it for repeatable downloads. +SOLOMON_COMMIT=$(uv run python -c 'from huggingface_hub import HfApi; print(HfApi().model_info("DoccyHealth/Solomon").sha)') +uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" --output models/quality +``` + +Authenticate with `hf auth login` first if the repository requires access. This +package is supplied here as source; it is not claimed to be published on PyPI. +For a checkout without downloading model files through Git LFS: + +```sh +GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/DoccyHealth/Solomon +cd Solomon/mlx +``` + +The setup tool explicitly fetches `adapter/**` and the retained MLX licensing +metadata. It downloads `Qwen/Qwen3.8-27B` at +`1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, verifies every input against the +bundled manifest, and converts one shard at a time. Budget about 112 GB of disk +for original and converted weights, plus temporary space and caches. Existing +original base files can be reused with `--base /path/to/original-qwen`. + +The output contains `backbone/`, the unmerged adapter, trained heads, notices +and a fresh `binding.json`. Existing valid outputs are verified and reused; +corrupt or incompatible outputs fail without being overwritten. Conversion is +atomic and concurrent preparations into the same output are rejected. Interrupted +conversions may leave a hidden temporary directory; the final output is never +marked ready before verification completes. + +For fully offline preparation, provide both downloaded inputs: + +```sh +uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" \ + --snapshot /path/to/solomon-snapshot --base /path/to/original-qwen \ + --output models/quality +uv run solomon-mlx-hub verify models/quality +``` + +CPU conversion is the default. `--device gpu` selects Metal conversion on a Mac. +Linux CPU conversion is also supported by the existing converter and can use +`uv sync --frozen --extra cloud` with MLX's CPU backend. Linux conversion does +not run Apple Metal inference or establish CUDA parity. + +## Converter and provenance + +The exact converter is [src/solomon_mlx/prepare.py](src/solomon_mlx/prepare.py). +The runtime Python sources are unchanged in behaviour. The only edits made for +this release rename the runtime identity's `source_contract` field to `solomon-v1` +and adjust comments, docstrings and one error message, so their combined +`converter_code_sha256` is +`bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec`. +It is computed by `solomon_mlx.artifacts.code_identity()` from the sorted mapping +of relative Python paths to SHA-256 values. The new download/assembly wrapper is +in the separate `solomon_mlx_hub` package. + +The converter retains large weights in BF16 and promotes normalization weights, +`A_log` and `dt_bias` to FP32 **before** applying upstream normalization offsets. +It validates tensor names and shapes with MLX-VLM's Qwen3.5 implementation. The +original FP32 adapter and ten trained heads are copied without changes. The +runtime applies the adapter only to question tokens, with its explicit 2.0 scale. + +`bf16/conversion.json` and `bf16/binding.json` describe the historical cloud +conversion; paths inside them describe the original local model layout. They +are provenance, not a manifest of files currently present on the Hub, and they +record the converter hash of that historical conversion, `648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575`, +which predates the documentation edits described above. The root +adapter files have the same hashes as their removed duplicates. Newly converted +safetensors may serialize differently across CPU and Metal, so new outputs get +actual output checksums and their own runtime binding. Historical CUDA or MLX +qualification identities are never reused for a different artifact binding. + +## Python API + +```python +from solomon_mlx import Solomon + +model = Solomon.load("models/quality", profile="quality") +with model.prefill("Rookwood Ltd holds a current certification.") as state: + result = model.decide( + state=state, + questions={"certified": { + "type": "noul", + "instructions": "Does Rookwood Ltd hold a current certification?", + }}, + evidence="support", + ) + print(result["answers"]["certified"]) +``` + +Or use `solomon_mlx_hub.load("models/quality", revision=COMMIT)` to prepare and +load in one call. `revision` must be a full 40-character commit SHA. Cached valid +outputs are reused without network access and retain their original binding. + +Documents accept text, structured JSON objects, or ordered `{"text": ...}` and +`{"image": local_path}` parts. Image features are computed once per document. +There is no PDF renderer or OCR. Question forms preserve Boolean, entity and +multilabel `noul`, single `choice`, and ordered `score` semantics; candidate order +is retained. Missing/conflicting facts collapse before temperature application. +The API defaults to T=1. No new temperatures are fitted by setup or parity checks. + +Evidence levels are `none`, `support`, `sufficiency`, and `removal`. Text spans use +exact code-point offsets. Sufficiency and removal re-encode the relevant source; +they do not establish causal faithfulness. Image evidence requires `page_selector=`; +otherwise the result reports `unsupported_page_selector`. States belong to one +model instance and support `close()`, `save(path)`, and `model.replay(path)`. +Replay persists a checksummed source recipe and recomputes caches. + +## Tests + +```sh +uv run pytest -q +uv run ruff check src tests scripts +``` + +Tests use synthetic small models to cover conversion precision, CPU/Metal tensor +agreement, cache isolation, text/image processing, answer semantics, evidence, +artifact corruption, and adapter-only setup. These tests do not replace trained +model parity measurements. The included parity checker compares saved CUDA and +MLX scores using the same frozen temperatures exactly once, reports probability +drift separately, and cannot pass a complete-panel gate from partial results. +Evaluation inputs and private infrastructure configuration are not distributed. + +Preserve `LICENSE`, `NOTICE`, `MODIFICATIONS.md`, and upstream model notices with +permitted copies. Model downloads and conversion do not alter repository visibility. diff --git a/mlx/bf16/LICENSE b/mlx/bf16/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..cd52324cfae719548ecb79e2b4c42a3fad8bbc30 --- /dev/null +++ b/mlx/bf16/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Doccy Pty Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mlx/bf16/MODIFICATIONS.md b/mlx/bf16/MODIFICATIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..a4b15ae067dd195f35210d40e315d8ebf372ce70 --- /dev/null +++ b/mlx/bf16/MODIFICATIONS.md @@ -0,0 +1,75 @@ +# Statement of changes + +Apache License 2.0, section 4(b): prominent notice that files carry modifications. + +## What is modified + +**No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited, +renamed, patched or redistributed in this repository, with one exception: the +upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt` +(sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is +unmodified and carries the upstream copyright. + +The modification this work carries is not an edit to a source file. It is a set +of **trained parameters applied to the base model at inference time**, plus an +original serving layer that reads the model's logits. Concretely: + +| Change | Artifact | sha256 | +|---|---|---| +| LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca` | +| Trained linear answer heads | `adapter/heads.npz` | `126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f` | +| Readout calibration, one positive scalar per task | `serving/scope9-readout-temperature-v2.json` | file `baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118` / payload `682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278` | +| Runtime identity binding | `serving/serving-binding.json` | payload `95683c1f87ec3f71b7657669dc311918ff53b7d41a981eaa8d827047e72cb505` | +| Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` | + +Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision +`1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0. + +Training took place between 16 and 20 September 2026. The shipped artifacts are +identified by the checksums in the table above and in `MANIFEST.json`. + +## Which files carry a change notice + +| File | Why | +|---|---| +| `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy | +| `MODIFICATIONS.md` | this file | +| `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body | +| `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums | +| `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one | + +No file under `src/` carries an upstream change notice, because no file under +`src/` contains upstream code. Every file there is original work, written for +this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by +the copyright line in `NOTICE`. + +All of the changes described above — the adapter, the heads, the calibration, the +serving binding and the serving layer — are Copyright 2026 +Doccy Pty Ltd and licensed under Apache-2.0. + +## Third-party text scan + +Before release, **every file staged into this repository was scanned for text +originating in third-party source documents.** The scan compared normalised +6-gram and 8-gram shingles of every staged text file against: + +1. the 42 third-party source records the training and evaluation panels were + built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0, + OGL v3.0 and US-government public-domain assertions); and +2. every generated panel and document corpus on disk. + +**Result: no third-party document text is present in any shipped file.** The +only matches were: + +* the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which + matches an Apache-2.0 licence document held in the evaluation corpus and is + required to be here verbatim; and +* the phrase *"A missing fact is not a negative fact"*, which is **our own + prompt-template wording** appearing in our own evaluation panels, not + third-party text entering our prompts. + +Acceptance fixtures are excluded from this repository entirely. The fixture +documents used in live acceptance are original synthetic text authored for this +project and held in tooling that is not distributed. + +Re-run the scan with `ops/solomon_package.py --scan` in the source project. diff --git a/mlx/bf16/NOTICE b/mlx/bf16/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..9f0b076e5bf60021ab0a23f8ae433245adaf6518 --- /dev/null +++ b/mlx/bf16/NOTICE @@ -0,0 +1,78 @@ +Solomon v1.1.0 +Copyright 2026 Doccy Pty Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this work except in compliance with the License. You may obtain a copy of the +License in the LICENSE file distributed with this work, or at + + http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- +ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c)) +-------------------------------------------------------------------------------- + +This work is a DERIVATIVE WORK of: + + Qwen/Qwen3.8-27B + Copyright 2026 Alibaba Cloud + Licensed under the Apache License, Version 2.0 + Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 + Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes, + the exact bytes served at the pinned revision) + +The base model weights are NOT redistributed in this repository. They are +referenced by the pinned revision above and downloaded by the operator directly +from the upstream repository under the upstream licence. + +Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the +pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials). +Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No +upstream attribution text has been invented or paraphrased. + +"Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model +this adapter was trained against. Apache-2.0 section 6 grants no trademark +rights and none are claimed or implied. Nothing here states or implies any +endorsement, sponsorship or affiliation. + +-------------------------------------------------------------------------------- +STATEMENT OF CHANGES (Apache License 2.0, section 4(b)) +-------------------------------------------------------------------------------- + +No upstream source file is modified, and no upstream file is redistributed +except the unmodified licence text at licenses/Qwen-Apache-2.0.txt. + +The modification this work carries is a trained LoRA adapter and a set of +trained linear answer heads, applied to the base model at inference time: + + * LoRA adapter, rank 64, question-side placement, float32 + adapter/adapter.safetensors + sha256 2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca + 870363376 bytes + * Trained linear answer heads + adapter/heads.npz + sha256 126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f + 2053938 bytes + * Readout calibration (one positive scalar per task) + serving/scope9-readout-temperature-v2.json + file sha256 baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118 + payload sha256 682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278 + +Training took place between 16 and 20 September 2026. The adapter and the heads +are identified by the checksums above. Full change detail is in MODIFICATIONS.md. + +-------------------------------------------------------------------------------- +THIRD-PARTY CONTENT IN THIS REPOSITORY +-------------------------------------------------------------------------------- + +The only third-party content distributed here is the unmodified Apache License +2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a). + +No training document, evaluation panel, dataset, corpus, rendered page, cached +state, score archive or acceptance fixture is distributed. Every file in this +repository was scanned for text originating in third-party source documents +before release; see MODIFICATIONS.md, "Third-party text scan". + +Everything else in this repository -- the serving code under src/, the adapter +and head weights, the calibration artifact, the serving binding and the +documentation -- is original work of Doccy Pty Ltd, licensed under +Apache-2.0. diff --git a/mlx/bf16/README.md b/mlx/bf16/README.md new file mode 100644 index 0000000000000000000000000000000000000000..41c1437ae5e5380596a1e92e6ab97499fd19fb92 --- /dev/null +++ b/mlx/bf16/README.md @@ -0,0 +1,51 @@ +# Solomon BF16 conversion provenance + +This directory retains metadata for the unquantized BF16 conversion. **Backbone +weights are downloaded separately from Qwen; adapter and head weights live at +`adapter/adapter.safetensors` and `adapter/heads.npz` in the repository root.** +This directory is no longer a self-contained loadable model. + +Install the source package in [`../`](../) and follow its README. From that +package directory: + +```sh +uv sync --frozen --extra dev +SOLOMON_COMMIT=$(uv run python -c 'from huggingface_hub import HfApi; print(HfApi().model_info("DoccyHealth/Solomon").sha)') +uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" --output models/quality +``` + +The loader fetches `adapter/**` plus MLX licensing metadata at the pinned Solomon +commit. It obtains the original base from `Qwen/Qwen3.8-27B` at +`1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, verifies every base file, and invokes +the included exact converter. Existing original weights can be supplied with +`--base`. Existing valid converted outputs are verified and reused. + +The original converter source is `../src/solomon_mlx/prepare.py`. The runtime +source hash recorded in `conversion.json` for this historical conversion is +`648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575`. The +current runtime source hash is +`bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec`; +the two differ only by the release documentation edits described in `../README.md`, +which changed no converter behaviour. The new Hub loader is packaged separately so the original +converter remains reproducible. Dependency versions are locked in `../uv.lock`. +Large matrices stay BF16; normalization parameters, `A_log`, and `dt_bias` stay +FP32, with normalization offsets applied after promotion. LoRA is never merged. + +`conversion.json` and `binding.json` retain the historical output checksums and +paths, including removed backbone and duplicate adapter paths. The root adapter +and heads are byte-identical to those historical records. The old binding SHA-256 +is `6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530`. +New local conversions receive a fresh binding with actual output checksums; +safetensors serialization may differ across CPU and Metal. The historical source +revision `2ec506902269e4636285c8811f6c0f52c9300c0c` is provenance, not a required +future download URL. After a history rewrite, pin the new repository commit. + +**Experimental: complete CUDA parity remains pending.** Focused text/image +fixtures and a partial text panel match CUDA decisions. See the dated results in +`../docs/VALIDATION-20260921.md`. No new calibration or temperature fitting is +required or performed. Existing CUDA temperatures are applied identically to both +backends for comparison; CUDA qualification is not inherited. + +The historical conversion produced 55,610,774,146 bytes. Focused inference used +about 59.9 GB peak Metal allocation on an M5 Max / 128 GB Mac; larger inputs need +more memory. This is full BF16, without quantization, and does not fit 24–32 GB Macs. diff --git a/mlx/bf16/base-manifest.json b/mlx/bf16/base-manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..cb70489241c40cb013c06491d28acd16013ba7e8 --- /dev/null +++ b/mlx/bf16/base-manifest.json @@ -0,0 +1,197 @@ +{ + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "files": [ + { + "name": ".gitattributes", + "size": 1570, + "blob_id": "52373fe24473b1aa44333d318f578ae6bf04b49b", + "sha256": null + }, + { + "name": "LICENSE", + "size": 11544, + "blob_id": "f938136e3adacfd92be087f6e113b5d6d97f678f", + "sha256": null + }, + { + "name": "README.md", + "size": 65012, + "blob_id": "bc8aa0e396cd029c21cd773cca21830d0ded28ec", + "sha256": null + }, + { + "name": "chat_template.jinja", + "size": 8952, + "blob_id": "c0c686f9c38d70d179fb7b5f5aa7530bc913dda3", + "sha256": null + }, + { + "name": "config.json", + "size": 4312, + "blob_id": "706cebd746c4b6f2b1d1f892630867acfdfd3df8", + "sha256": null + }, + { + "name": "crc32.txt", + "size": 238, + "blob_id": "6de5ee6a0c6596744baee911af0a5cdcb8d99a1e", + "sha256": null + }, + { + "name": "generation_config.json", + "size": 202, + "blob_id": "023756cfadf88e5bf69eefeee3e172f38c448d64", + "sha256": null + }, + { + "name": "merges.txt", + "size": 3353259, + "blob_id": "a494e019ca1502219fd0128658b979e5f05ae8e8", + "sha256": null + }, + { + "name": "model-00001-of-00018.safetensors", + "size": 3966730552, + "blob_id": "79b73cdd69afeca931d181f5d1e21ad2d73bb7b7", + "sha256": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c" + }, + { + "name": "model-00002-of-00018.safetensors", + "size": 3043080328, + "blob_id": "81cf43545884a227a01351942dfbc72577812316", + "sha256": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb" + }, + { + "name": "model-00003-of-00018.safetensors", + "size": 2542796952, + "blob_id": "a2da5e6cd6307a6cae13d04820ab0d31b47b48bd", + "sha256": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968" + }, + { + "name": "model-00004-of-00018.safetensors", + "size": 3988973152, + "blob_id": "175ddab3f2488ad181f73d789d2d778cf6d4fca6", + "sha256": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a" + }, + { + "name": "model-00005-of-00018.safetensors", + "size": 2099339864, + "blob_id": "64890208fdd563c3aac0c2f55f69948bcaa1a492", + "sha256": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701" + }, + { + "name": "model-00006-of-00018.safetensors", + "size": 3979553696, + "blob_id": "3ae29b86e4c773babfe17124dbd10d54d37819ec", + "sha256": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0" + }, + { + "name": "model-00007-of-00018.safetensors", + "size": 2108759344, + "blob_id": "b7e9c0f038884839f3dad38fef7e75ee3362c24f", + "sha256": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892" + }, + { + "name": "model-00008-of-00018.safetensors", + "size": 3979553696, + "blob_id": "0b58cbc52714e1c683711be87e7c2e33fb10e7a0", + "sha256": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79" + }, + { + "name": "model-00009-of-00018.safetensors", + "size": 2108759344, + "blob_id": "4fc0c6b28004d809225d0e115f30e9e11c2345dc", + "sha256": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8" + }, + { + "name": "model-00010-of-00018.safetensors", + "size": 3979553696, + "blob_id": "9ec693a17c788084116c51b4f34632670df77612", + "sha256": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9" + }, + { + "name": "model-00011-of-00018.safetensors", + "size": 2108759344, + "blob_id": "3c63baaed16d833432a919647af53ac76bb6b007", + "sha256": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8" + }, + { + "name": "model-00012-of-00018.safetensors", + "size": 3979553696, + "blob_id": "8568b440a4666ed1b4cda0b5d21496378304f326", + "sha256": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55" + }, + { + "name": "model-00013-of-00018.safetensors", + "size": 2108759344, + "blob_id": "724083995a2257194321a3d00b36fd7d4bb7348d", + "sha256": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a" + }, + { + "name": "model-00014-of-00018.safetensors", + "size": 3979553696, + "blob_id": "a60b80b6510d7938d951fbddc76aaadd121c6268", + "sha256": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b" + }, + { + "name": "model-00015-of-00018.safetensors", + "size": 2108759344, + "blob_id": "7d4c34f3d716e1a47acff2f576b65b41a3ec4f3f", + "sha256": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22" + }, + { + "name": "model-00016-of-00018.safetensors", + "size": 3979564040, + "blob_id": "c67e3e42c9715298fa7f68c6b378bfb1d74290d0", + "sha256": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe" + }, + { + "name": "model-00017-of-00018.safetensors", + "size": 2108759344, + "blob_id": "23dd899179729a4370c655e2c2241cfdbe4ef4ba", + "sha256": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8" + }, + { + "name": "model-00018-of-00018.safetensors", + "size": 3392197344, + "blob_id": "a92b4073fc6a58ffc9334ac63360fc8d9cc50d33", + "sha256": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06" + }, + { + "name": "model.safetensors.index.json", + "size": 112216, + "blob_id": "da35e3c564457dface7d138f0b6cac284ff8958c", + "sha256": null + }, + { + "name": "preprocessor_config.json", + "size": 390, + "blob_id": "2ea84a437d448ff71b08df68fdd949d5cc4ebb64", + "sha256": null + }, + { + "name": "tokenizer.json", + "size": 12809320, + "blob_id": "9328ce9c41e80f6dd7bc2c66d8ae1fc93bf87440", + "sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3" + }, + { + "name": "tokenizer_config.json", + "size": 17928, + "blob_id": "5de744b3fca2129d7186979ae47c06be33903243", + "sha256": null + }, + { + "name": "video_preprocessor_config.json", + "size": 385, + "blob_id": "3ba673a5ad7d4d13f54155ecd38b2a94a6dac8fe", + "sha256": null + }, + { + "name": "vocab.json", + "size": 6722759, + "blob_id": "0aa0ce0658d60ac4a5d609f4eadb0e8e43514176", + "sha256": null + } + ] +} \ No newline at end of file diff --git a/mlx/bf16/binding.json b/mlx/bf16/binding.json new file mode 100644 index 0000000000000000000000000000000000000000..03bfac46d3cc0bea97692a1f7d45c43f78f78a65 --- /dev/null +++ b/mlx/bf16/binding.json @@ -0,0 +1,141 @@ +{ + "adapter_dtype": "float32", + "adapter_placement": "question", + "adapter_scale": 2.0, + "base_hashes": { + ".gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930", + "LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641", + "chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab", + "crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83", + "generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e", + "merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d", + "model-00001-of-00018.safetensors": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c", + "model-00002-of-00018.safetensors": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb", + "model-00003-of-00018.safetensors": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968", + "model-00004-of-00018.safetensors": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a", + "model-00005-of-00018.safetensors": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701", + "model-00006-of-00018.safetensors": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0", + "model-00007-of-00018.safetensors": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892", + "model-00008-of-00018.safetensors": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79", + "model-00009-of-00018.safetensors": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8", + "model-00010-of-00018.safetensors": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9", + "model-00011-of-00018.safetensors": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8", + "model-00012-of-00018.safetensors": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55", + "model-00013-of-00018.safetensors": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a", + "model-00014-of-00018.safetensors": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b", + "model-00015-of-00018.safetensors": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22", + "model-00016-of-00018.safetensors": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe", + "model-00017-of-00018.safetensors": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8", + "model-00018-of-00018.safetensors": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06", + "model.safetensors.index.json": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df", + "preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516", + "tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27", + "video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13", + "vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003" + }, + "base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset", + "dependencies": { + "mlx": "0.32.2", + "mlx-vlm": "0.7.1", + "numpy": "2.5.3", + "transformers": "5.17.0" + }, + "dtype": "bfloat16", + "files": { + "LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457", + "MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7", + "NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1", + "adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca", + "backbone/.gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930", + "backbone/LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "backbone/README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641", + "backbone/chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "backbone/config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab", + "backbone/crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83", + "backbone/generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e", + "backbone/merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d", + "backbone/model-00001-of-00018.safetensors": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a", + "backbone/model-00002-of-00018.safetensors": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78", + "backbone/model-00003-of-00018.safetensors": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528", + "backbone/model-00004-of-00018.safetensors": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da", + "backbone/model-00005-of-00018.safetensors": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4", + "backbone/model-00006-of-00018.safetensors": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24", + "backbone/model-00007-of-00018.safetensors": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb", + "backbone/model-00008-of-00018.safetensors": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81", + "backbone/model-00009-of-00018.safetensors": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53", + "backbone/model-00010-of-00018.safetensors": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89", + "backbone/model-00011-of-00018.safetensors": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f", + "backbone/model-00012-of-00018.safetensors": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e", + "backbone/model-00013-of-00018.safetensors": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4", + "backbone/model-00014-of-00018.safetensors": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c", + "backbone/model-00015-of-00018.safetensors": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a", + "backbone/model-00016-of-00018.safetensors": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a", + "backbone/model-00017-of-00018.safetensors": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2", + "backbone/model-00018-of-00018.safetensors": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9", + "backbone/model.safetensors.index.json": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f", + "backbone/preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516", + "backbone/tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "backbone/tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27", + "backbone/video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13", + "backbone/vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003", + "heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f" + }, + "head_dtype": "float32", + "profile": "quality", + "quantization": null, + "recurrent_state_dtype": "float32", + "schema": "solomon-mlx-binding-v1", + "sensitive_parameters": "FP32 normalization weights, A_log and dt_bias", + "solomon_revision": "2ec506902269e4636285c8811f6c0f52c9300c0c", + "source_hashes": { + ".gitattributes": "8baff4be52225e26dd13dffb05fed3d0db821911c36d7ffd6f6d00d02ca421c7", + "LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457", + "MANIFEST.json": "a473433af0c2de6a97a0d99e9e47216e91ed67c4269e07ca8d678d8c068c317b", + "MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7", + "NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1", + "PUSH.md": "cc8e0ea0a0dec3db020f8245eb90b04571b77d8ac9f3efcd64d0ab0bef5bac16", + "README.md": "51a2389ef504652f0b0a292cfaf28bb5e7635bde1aede0289f42696ad5818afc", + "adapter/adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca", + "adapter/config.json": "cafc99ce8299eeca3fc2b2e1a2b08dcf659b66467d6675b6f8731a616693dbc4", + "adapter/heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f", + "licenses/Qwen-Apache-2.0.txt": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "requirements.lock": "27675e999e5bcfc175ed768711856a975c2286d2ba807471cc7b0213d6d13f89", + "serving/scope9-readout-temperature-v2.json": "baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118", + "serving/selection.json": "14fa9c88a913d61a60d13b65f0974cff3985f40d50f204a1b83a9426db21b362", + "serving/service-export.json": "ccd7c0ca37cd900912631ab98b72703a4a4aebf2fadd6208675781aabd1e5ac8", + "serving/serving-binding.json": "1849df4bd4897d048a45a2f4cbc63beac4ded6afc0d43ff41e53a8a6dc8c49e4", + "src/scope35/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/scope35/engine.py": "61f91be0dabc680795e5d4b83f9d80f02cbed2502b06e3809fdc09fbeeb99202", + "src/scope4/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/scope4/cuda_engine.py": "f41dd19a248193bbda6aa81705317c702e0ec07976c7520a513b14260edddbf2", + "src/scope4/cuda_engine_v2.py": "d536117abd403811bba62129c2ba30d2e890c9058741baff65f3e7414f829bce", + "src/scope4/cuda_service.py": "c1c2267401b88dc5d4ad3d18b08c547c9b2e5d0a01c922d8ba88a1756075315a", + "src/scope4/engine.py": "b1ef3525011a8ec66c308f461e525d73a0a2b927bbfa605c76bef960d25fb5ff", + "src/scope4/readout.py": "cafcadbfc6ab3e77eac7bcd092d2254049c987913e2f744318ae7e2f36c24587", + "src/scope4/service.py": "2dfbfbadf92bce3f001063f9d94e5bf19a8d0ff9d143dd68b20d5032dd2e5e73", + "src/scope5/__init__.py": "6c079cc4864d55e798e243a6cbdc6e2b3fd963ff31fd28cf9a8122819a8c65cf", + "src/scope5/cuda_engine.py": "2d7d64d7e01cccefdb7afd504b80c54443ca271d0bcd3cf352b6bfd99dc491e3", + "src/scope5/heads_runtime.py": "48c0c29c79a8b781cddf22034b9ffee3df9a8d61b8709b1dd808b8df47024fe7", + "src/scope5/heads_service.py": "5c775e26bcb2de1c32156ce0cc404d06de8fbedff6c67fcb5606ce3713b88342", + "src/scope5/routing.py": "0d88a7f0433dd448b81bd04c48044c98e453038d9ffcd903c9479c20ec6651c1", + "src/scope5/service.py": "d056ae3e4ebda5d89ba4ebe0c93ae225a31e33a8f543632a0976703c25501019", + "src/scope6/__init__.py": "e72ad8e334509eb002496207c4e43598d222ba2953162344e43ee122e3832df0", + "src/scope6/evidence.py": "a0ece458b5c0546c1f80b48cea87ebc1c3732e83f2c0f8e58969645bc8bde2f7", + "src/scope6/evidence_v3.py": "9b2b9e9c319242ca9c4a96a2567d1c1adb3134332c95dd6dfd60114c54d09426", + "src/scope6/retrieval.py": "4e5094aacc2ee3bbb0663de402198cd418933c4682f9d5a9006d4058e15f9b4f", + "src/scope6/runtime.py": "2d154d8ba39e61a10b23bba286e1ce87f57dbdc21d8f6e73430c0929a1990fe7", + "src/scope6/service.py": "5e7b6f9e163be35ab17ed76355ecee9faa541f27ca76e969ff49f2076dfbd9b8", + "src/scope6/service_v3.py": "b92cfdee8ca1c039645a97746e47fc99965f9c5f5ecd98e31aedf71bca29b611", + "src/scope6/serving.py": "aab481eb4b1d34c13caaf8c3628f3702f2956bb413f288c7a30979d5c2281356", + "src/scope9/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "src/scope9/api.py": "db677e569a3f7d15e6398d4bfb9ac0b00f286502fe4d97e9811388ccfb95dcae", + "src/scope9/live.py": "62f40568c8c0bc7ea4d436325eb54d49fe06ea8f51c304b384dcffb605bf3e3f", + "src/scope9/prompts.py": "53e22b99c19f5af588fea2167594c6331877a8ce71d0a0424bce10000bc22b53", + "src/scope9/semantics.py": "9fd9041c50f8925608bf6a2f351abab745648137400475bdf46f66885bef67dc", + "src/scope9/service.py": "9deed4e6acae4b26619a95f6f2ef19ced6bb42d6d6a4e1710c26accff4bc198b" + } +} \ No newline at end of file diff --git a/mlx/bf16/conversion.json b/mlx/bf16/conversion.json new file mode 100644 index 0000000000000000000000000000000000000000..1f68f50c7a3843ef21de97350dc10ad7bfbe2613 --- /dev/null +++ b/mlx/bf16/conversion.json @@ -0,0 +1,203 @@ +{ + "run_id": "bf16-cpu-79517a5cd7aa", + "base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "solomon_revision": "2ec506902269e4636285c8811f6c0f52c9300c0c", + "backend": "mlx-linux-cpu", + "binding_sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530", + "converter_code_sha256": "648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575", + "total_output_bytes": 55610774146, + "seconds": 220.91851106299998, + "apple_silicon_validation": "pending; conversion is not inference qualification", + "files": [ + { + "name": "LICENSE", + "size": 11344, + "sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457" + }, + { + "name": "MODIFICATIONS.md", + "size": 4197, + "sha256": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7" + }, + { + "name": "NOTICE", + "size": 3891, + "sha256": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1" + }, + { + "name": "adapter.safetensors", + "size": 870363376, + "sha256": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca" + }, + { + "name": "backbone/.gitattributes", + "size": 1570, + "sha256": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930" + }, + { + "name": "backbone/LICENSE", + "size": 11544, + "sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a" + }, + { + "name": "backbone/README.md", + "size": 65012, + "sha256": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641" + }, + { + "name": "backbone/chat_template.jinja", + "size": 8952, + "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041" + }, + { + "name": "backbone/config.json", + "size": 4312, + "sha256": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab" + }, + { + "name": "backbone/crc32.txt", + "size": 238, + "sha256": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83" + }, + { + "name": "backbone/generation_config.json", + "size": 202, + "sha256": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e" + }, + { + "name": "backbone/merges.txt", + "size": 3353259, + "sha256": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d" + }, + { + "name": "backbone/model-00001-of-00018.safetensors", + "size": 3967078356, + "sha256": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a" + }, + { + "name": "backbone/model-00002-of-00018.safetensors", + "size": 3043154160, + "sha256": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78" + }, + { + "name": "backbone/model-00003-of-00018.safetensors", + "size": 2542796953, + "sha256": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528" + }, + { + "name": "backbone/model-00004-of-00018.safetensors", + "size": 3989088609, + "sha256": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da" + }, + { + "name": "backbone/model-00005-of-00018.safetensors", + "size": 2099392965, + "sha256": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4" + }, + { + "name": "backbone/model-00006-of-00018.safetensors", + "size": 3979669611, + "sha256": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24" + }, + { + "name": "backbone/model-00007-of-00018.safetensors", + "size": 2108811989, + "sha256": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb" + }, + { + "name": "backbone/model-00008-of-00018.safetensors", + "size": 3979669575, + "sha256": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81" + }, + { + "name": "backbone/model-00009-of-00018.safetensors", + "size": 2108812007, + "sha256": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53" + }, + { + "name": "backbone/model-00010-of-00018.safetensors", + "size": 3979669601, + "sha256": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89" + }, + { + "name": "backbone/model-00011-of-00018.safetensors", + "size": 2108812003, + "sha256": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f" + }, + { + "name": "backbone/model-00012-of-00018.safetensors", + "size": 3979669597, + "sha256": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e" + }, + { + "name": "backbone/model-00013-of-00018.safetensors", + "size": 2108811987, + "sha256": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4" + }, + { + "name": "backbone/model-00014-of-00018.safetensors", + "size": 3979669605, + "sha256": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c" + }, + { + "name": "backbone/model-00015-of-00018.safetensors", + "size": 2108812005, + "sha256": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a" + }, + { + "name": "backbone/model-00016-of-00018.safetensors", + "size": 3979690166, + "sha256": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a" + }, + { + "name": "backbone/model-00017-of-00018.safetensors", + "size": 2108812005, + "sha256": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2" + }, + { + "name": "backbone/model-00018-of-00018.safetensors", + "size": 2542796942, + "sha256": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9" + }, + { + "name": "backbone/model.safetensors.index.json", + "size": 111087, + "sha256": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f" + }, + { + "name": "backbone/preprocessor_config.json", + "size": 390, + "sha256": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516" + }, + { + "name": "backbone/tokenizer.json", + "size": 12809320, + "sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3" + }, + { + "name": "backbone/tokenizer_config.json", + "size": 17928, + "sha256": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27" + }, + { + "name": "backbone/video_preprocessor_config.json", + "size": 385, + "sha256": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13" + }, + { + "name": "backbone/vocab.json", + "size": 6722759, + "sha256": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003" + }, + { + "name": "heads.npz", + "size": 2053938, + "sha256": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f" + }, + { + "name": "binding.json", + "size": 12306, + "sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530" + } + ] +} \ No newline at end of file diff --git a/mlx/docs/UPSTREAM-MODIFICATIONS.md b/mlx/docs/UPSTREAM-MODIFICATIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..a4b15ae067dd195f35210d40e315d8ebf372ce70 --- /dev/null +++ b/mlx/docs/UPSTREAM-MODIFICATIONS.md @@ -0,0 +1,75 @@ +# Statement of changes + +Apache License 2.0, section 4(b): prominent notice that files carry modifications. + +## What is modified + +**No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited, +renamed, patched or redistributed in this repository, with one exception: the +upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt` +(sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is +unmodified and carries the upstream copyright. + +The modification this work carries is not an edit to a source file. It is a set +of **trained parameters applied to the base model at inference time**, plus an +original serving layer that reads the model's logits. Concretely: + +| Change | Artifact | sha256 | +|---|---|---| +| LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca` | +| Trained linear answer heads | `adapter/heads.npz` | `126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f` | +| Readout calibration, one positive scalar per task | `serving/scope9-readout-temperature-v2.json` | file `baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118` / payload `682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278` | +| Runtime identity binding | `serving/serving-binding.json` | payload `95683c1f87ec3f71b7657669dc311918ff53b7d41a981eaa8d827047e72cb505` | +| Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` | + +Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision +`1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0. + +Training took place between 16 and 20 September 2026. The shipped artifacts are +identified by the checksums in the table above and in `MANIFEST.json`. + +## Which files carry a change notice + +| File | Why | +|---|---| +| `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy | +| `MODIFICATIONS.md` | this file | +| `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body | +| `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums | +| `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one | + +No file under `src/` carries an upstream change notice, because no file under +`src/` contains upstream code. Every file there is original work, written for +this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by +the copyright line in `NOTICE`. + +All of the changes described above — the adapter, the heads, the calibration, the +serving binding and the serving layer — are Copyright 2026 +Doccy Pty Ltd and licensed under Apache-2.0. + +## Third-party text scan + +Before release, **every file staged into this repository was scanned for text +originating in third-party source documents.** The scan compared normalised +6-gram and 8-gram shingles of every staged text file against: + +1. the 42 third-party source records the training and evaluation panels were + built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0, + OGL v3.0 and US-government public-domain assertions); and +2. every generated panel and document corpus on disk. + +**Result: no third-party document text is present in any shipped file.** The +only matches were: + +* the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which + matches an Apache-2.0 licence document held in the evaluation corpus and is + required to be here verbatim; and +* the phrase *"A missing fact is not a negative fact"*, which is **our own + prompt-template wording** appearing in our own evaluation panels, not + third-party text entering our prompts. + +Acceptance fixtures are excluded from this repository entirely. The fixture +documents used in live acceptance are original synthetic text authored for this +project and held in tooling that is not distributed. + +Re-run the scan with `ops/solomon_package.py --scan` in the source project. diff --git a/mlx/docs/VALIDATION-20260921.md b/mlx/docs/VALIDATION-20260921.md new file mode 100644 index 0000000000000000000000000000000000000000..74ee0efb341ed3fbda58ac2540b48d2de8f0d39c --- /dev/null +++ b/mlx/docs/VALIDATION-20260921.md @@ -0,0 +1,46 @@ +# Solomon BF16 CUDA parity validation — 21 September 2026 + +Full BF16 inference was exercised on the Apple M5 Max / 128 GB Mac. No weights or temperatures were changed. New temperature fitting is out of scope. + +## Confirmed results + +| Check | Result | +|---|---| +| Fresh text fixtures, all five answer types | 22/22 decisions match CUDA | +| Fresh page-image fixtures | 4/4 decisions match CUDA | +| Saved text subset | 645/645 branch decisions and 327/327 whole-question decisions match CUDA | +| Prefix tokens and captured full token sequences | Exact match with CUDA | +| Replay and fresh process versus previous run | Zero logit drift on this Mac | +| Public API cache isolation, evidence and invalid-input checks | Passed | +| Existing package tests | 21 passed | +| Parity workflow and reserved-answer regression tests | 2 passed | +| Lint | Passed | + +The saved subset covers 196 documents and all five tasks. It comes from previously scored calibration-fit inputs and is a diagnostic subset, not a complete held-out qualification. It was used only for comparison; nothing was fitted. + +## Numerical differences + +With the same frozen CUDA serving temperatures applied once on both backends, maximum probability drift on the saved subset is 2.5871 percentage points; mean drift is 0.0126 points. At identical T=1, maximum drift is 4.1508 points and mean drift is 0.0078 points. All compared decisions still agree. + +CUDA and MLX logits are not bitwise equal. The report preserves the largest probability differences for inspection. No claim is made that the CUDA confidence calibration has been independently validated for MLX. + +The focused runs used 59.90 GB peak Metal allocation. This is not a measurement of the maximum supported context or many-page memory use. + +## Remaining work + +The untouched certification panel is now scored directly: 1,200 documents / 21,718 branches. It skips fit/development scoring and temperature fitting. The complete agreement gate is at least 99.9% for both branches and whole questions, at T=1 and at the frozen CUDA temperatures. Probability drift and task accuracy are reported separately. + +Image fixtures and API evidence checks are limited tests; broad image accuracy and CUDA evidence-selection equivalence are not established by them. + +Runtime fingerprint: `33c9b63f036c5c039a2db4ba944f5b0042ae8b8e485e68c2aeddb57c407f8908`. + +Detailed evaluation inputs and outputs remain private; the source release includes aggregate results only. + +## Adapter-only packaging checks + +The 21 September source update passed 24 portable package tests, including seven +Hub-loader tests. The local development suite passed 30 tests. The new verifier +also checked all 37 files of the existing full BF16 conversion. The built wheel +contains the original converter with its exact recorded source hash and both +pinned-input manifests. These checks cover packaging and integrity, not additional +trained-model parity. diff --git a/mlx/examples/decide.py b/mlx/examples/decide.py new file mode 100644 index 0000000000000000000000000000000000000000..5f89e40c8fe36de238c3c2e56f3bc62e7966c105 --- /dev/null +++ b/mlx/examples/decide.py @@ -0,0 +1,21 @@ +from solomon_mlx import Solomon + +model = Solomon.load("models/quality") +questions = { + "certified": {"type": "noul", "instructions": "Is Rookwood Ltd certified?"}, + "auditor": { + "type": "choice", + "instructions": "Who performs the audit?", + "options": ["The Buyer", "The grower", "An independent auditor"], + }, + "severity": { + "type": "score", + "instructions": "What is the breach severity?", + "levels": ["none recorded", "minor", "material"], + }, +} +with model.prefill( + "Rookwood Ltd is certified. An independent auditor performs the audit. One minor breach is recorded." +) as state: + print(model.decide(state=state, questions=questions, evidence="support")) + state.save("document-replay.json") diff --git a/mlx/pyproject.toml b/mlx/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..7de6badfdcba4b2f7db6df67034ae5bcc63d763a --- /dev/null +++ b/mlx/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["hatchling==1.32.3"] +build-backend = "hatchling.build" + +[project] +name = "solomon-mlx" +version = "0.1.0" +description = "Solomon v1.1 BF16 inference on Apple Silicon" +license = "Apache-2.0" +license-files = ["LICENSE", "NOTICE", "MODIFICATIONS.md", "docs/UPSTREAM-MODIFICATIONS.md"] +requires-python = ">=3.12,<3.14" +dependencies = ["mlx==0.32.2", "mlx-vlm==0.7.1", "transformers==5.17.0", "numpy==2.5.3", "pillow==12.3.0", "safetensors==0.8.0", "huggingface-hub==1.32.0", "scipy==1.18.1"] + +[project.optional-dependencies] +dev = ["pytest==9.1.1", "ruff==0.16.8", "modal==1.5.5"] +cloud = [ + "boto3==1.43.98", + "mlx[cpu]==0.32.2; sys_platform == 'linux'", + "modal==1.5.5", +] + +[project.scripts] +solomon-mlx = "solomon_mlx.cli:main" +solomon-mlx-hub = "solomon_mlx_hub.__main__:main" + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = ["model: requires the full pinned model"] + +[tool.ruff] +line-length = 110 +extend-exclude = ["src/solomon_mlx/_vendor", "snapshots", "models", "evaluations"] + +[tool.hatch.build.targets.wheel] +packages = ["src/solomon_mlx", "src/solomon_mlx_hub"] diff --git a/mlx/requirements.cloud.lock b/mlx/requirements.cloud.lock new file mode 100644 index 0000000000000000000000000000000000000000..16d6a590700666224820c48d79f7492e97c79745 --- /dev/null +++ b/mlx/requirements.cloud.lock @@ -0,0 +1,1072 @@ +# This file was autogenerated by uv via the following command: +# uv export --frozen --no-dev --extra cloud --no-emit-project --format requirements-txt --output-file requirements.cloud.lock +aiohappyeyeballs==2.7.1 \ + --hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \ + --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472 + # via aiohttp +aiohttp==3.14.3 \ + --hash=sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043 \ + --hash=sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b \ + --hash=sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf \ + --hash=sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559 \ + --hash=sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86 \ + --hash=sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a \ + --hash=sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c \ + --hash=sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5 \ + --hash=sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d \ + --hash=sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71 \ + --hash=sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1 \ + --hash=sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427 \ + --hash=sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42 \ + --hash=sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd \ + --hash=sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8 \ + --hash=sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228 \ + --hash=sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0 \ + --hash=sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee \ + --hash=sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f \ + --hash=sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c \ + --hash=sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371 \ + --hash=sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0 \ + --hash=sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529 \ + --hash=sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc \ + --hash=sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b \ + --hash=sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80 \ + --hash=sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e \ + --hash=sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807 \ + --hash=sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d \ + --hash=sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82 \ + --hash=sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19 \ + --hash=sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0 \ + --hash=sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5 \ + --hash=sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4 \ + --hash=sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2 \ + --hash=sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9 \ + --hash=sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883 \ + --hash=sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde \ + --hash=sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787 \ + --hash=sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627 \ + --hash=sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a \ + --hash=sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24 + # via modal +aiosignal==1.4.0 \ + --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \ + --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7 + # via aiohttp +annotated-doc==0.0.5 \ + --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101 \ + --hash=sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb + # via + # fastapi + # typer +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via pydantic +anyio==4.15.1 \ + --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \ + --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94 + # via + # httpx + # starlette + # watchfiles +attrs==26.1.0 \ + --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \ + --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32 + # via aiohttp +boto3==1.43.98 \ + --hash=sha256:1ec732e023fb29c12dc8520f925b5bbbed29eeb36b5764b5a5c26052d7c721f7 \ + --hash=sha256:7454f666a9e852a56db0a7fa23be33a899f64e27828930eac43c3edba7b34e17 + # via solomon-mlx +botocore==1.43.98 \ + --hash=sha256:6135dd639ea6d1b3b49381bc253c8a139d61f7d44cb5f7dae8e7cd1791758572 \ + --hash=sha256:84b35b10402c2fc0c265f634fbf86336eecc6489ee23f55074b329924b2cfd6f + # via + # boto3 + # s3transfer +cbor2==6.1.4 \ + --hash=sha256:01ecc79a28f33d17331943ce508fc1e21f4b06553c73f874f4c77120d72b2ef9 \ + --hash=sha256:1fc15061553e4494dc10883237501e3402c645fe509248dd698e1faf2460d68b \ + --hash=sha256:2310f07db3f9ba26f2a623774ff9f3dc7185af54f732ea119785a6b1bf7e1e7e \ + --hash=sha256:310f3dfb296ba48fe9b63c5cf26e691e3548a1eae6901d2f0c18e941d151f220 \ + --hash=sha256:32a4663425fbca4a4a7aa918eb5789d844c406439e58424cf34511f79f559242 \ + --hash=sha256:36ae16d64b1f7b620c1af748e7b6947e20069ef80eee56871c5fbb84cc635905 \ + --hash=sha256:4bd29f21529e279d50fc14f1a811f7b05b4d8e66a7969163cce98983b6817245 \ + --hash=sha256:553a46bda7d09552631a714e22b91e6ff2c867ecd91511596ce290d8879b8d5b \ + --hash=sha256:598710183daae69cbdeb177a870ec64aa601de8138a61491fd256826d15a860f \ + --hash=sha256:5e6c76004d674ad1c620660cb0bc5a8a0b72a5d8c7b70926d8e09e6d7e87332f \ + --hash=sha256:69978901302ecbc8cda57b520487c5c5240ed217de783eb7728fceb258311d76 \ + --hash=sha256:ad4efa23fee6447e56a269191044e06eb39e809458bcd674e164fe9445feafd0 \ + --hash=sha256:c08b9c7d2ea013e24a0cb819b872b0119dde404f64a1182c0b24095b7bba781f \ + --hash=sha256:c48a7c938fc5fa5300ff82b5df09068dcb4838685ae8556b5ee8279d74f97ab4 \ + --hash=sha256:cc8cd300e236e9797b2e1ce306109dc481fcccf78bfa2682bf36d99e6eab1ec6 \ + --hash=sha256:d2560c2ba6a95904ba2a0ca257af878c4344409d9b46d8e646d8ebb617b1e0dd \ + --hash=sha256:d9ada5a6ccfbb8ea7a3aa2aeb028421b52d8e0cd9323f0a2aeaa9c09d25fbce2 + # via modal +certifi==2026.7.22 \ + --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ + --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 + # via + # httpcore + # httpx + # modal + # requests +cffi==2.1.1 \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via + # miniaudio + # sounddevice +charset-normalizer==3.5.1 \ + --hash=sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac \ + --hash=sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369 \ + --hash=sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031 \ + --hash=sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235 \ + --hash=sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072 \ + --hash=sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c \ + --hash=sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950 \ + --hash=sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2 \ + --hash=sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e \ + --hash=sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2 \ + --hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa \ + --hash=sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2 \ + --hash=sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71 \ + --hash=sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96 \ + --hash=sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8 \ + --hash=sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9 \ + --hash=sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f \ + --hash=sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591 \ + --hash=sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e \ + --hash=sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3 \ + --hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 \ + --hash=sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6 \ + --hash=sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959 \ + --hash=sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf \ + --hash=sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0 \ + --hash=sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e \ + --hash=sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491 \ + --hash=sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a \ + --hash=sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c \ + --hash=sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7 \ + --hash=sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a \ + --hash=sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9 \ + --hash=sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e \ + --hash=sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4 \ + --hash=sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3 \ + --hash=sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99 \ + --hash=sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6 \ + --hash=sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598 \ + --hash=sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6 \ + --hash=sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962 \ + --hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \ + --hash=sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573 \ + --hash=sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6 \ + --hash=sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2 \ + --hash=sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2 \ + --hash=sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe \ + --hash=sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3 \ + --hash=sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc \ + --hash=sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d \ + --hash=sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893 \ + --hash=sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d \ + --hash=sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada \ + --hash=sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876 \ + --hash=sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f \ + --hash=sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0 + # via requests +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 + # via + # huggingface-hub + # modal + # uvicorn +colorama==0.4.6 ; sys_platform == 'win32' \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via + # tqdm + # typer +fastapi==0.141.1 \ + --hash=sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3 \ + --hash=sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1 + # via mlx-vlm +filelock==4.0.1 \ + --hash=sha256:481a321a27bef441e23c53371c6abc8d7d16e26b97090074ba44f7538a3fd55a \ + --hash=sha256:fdefc3f3e87716d855ae2b732c1cfd521dd99799ef2b4d00e8c0d4dcdc7cc94b + # via huggingface-hub +frozenlist==1.8.0 \ + --hash=sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0 \ + --hash=sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121 \ + --hash=sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c \ + --hash=sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d \ + --hash=sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b \ + --hash=sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed \ + --hash=sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b \ + --hash=sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143 \ + --hash=sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e \ + --hash=sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746 \ + --hash=sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8 \ + --hash=sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad \ + --hash=sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82 \ + --hash=sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29 \ + --hash=sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf \ + --hash=sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62 \ + --hash=sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 \ + --hash=sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52 \ + --hash=sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d \ + --hash=sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1 \ + --hash=sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714 \ + --hash=sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65 \ + --hash=sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506 \ + --hash=sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888 \ + --hash=sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41 \ + --hash=sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608 \ + --hash=sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa \ + --hash=sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1 \ + --hash=sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 \ + --hash=sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496 \ + --hash=sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a \ + --hash=sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3 \ + --hash=sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7 \ + --hash=sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4 \ + --hash=sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51 \ + --hash=sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8 \ + --hash=sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042 \ + --hash=sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b \ + --hash=sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e \ + --hash=sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52 \ + --hash=sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1 \ + --hash=sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94 \ + --hash=sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822 \ + --hash=sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11 \ + --hash=sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51 \ + --hash=sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 \ + --hash=sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5 \ + --hash=sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4 \ + --hash=sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 \ + --hash=sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd + # via + # aiohttp + # aiosignal +fsspec==2026.9.0 \ + --hash=sha256:0f08147951c8cb31d844c3547d631053b127863b60be04cf06e121333ee0e2fe \ + --hash=sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f + # via huggingface-hub +grpclib==0.4.9 \ + --hash=sha256:7762ec1c8ed94dfad597475152dd35cbd11aecaaca2f243e29702435ca24cf0e \ + --hash=sha256:cc589c330fa81004c6400a52a566407574498cb5b055fa927013361e21466c46 + # via modal +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # httpcore + # uvicorn +h2==4.4.1 \ + --hash=sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6 \ + --hash=sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516 + # via grpclib +hf-xet==1.6.0 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' \ + --hash=sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7 \ + --hash=sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef \ + --hash=sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3 \ + --hash=sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb \ + --hash=sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d \ + --hash=sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c \ + --hash=sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f \ + --hash=sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a \ + --hash=sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b + # via huggingface-hub +hpack==4.2.0 \ + --hash=sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0 \ + --hash=sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986 + # via h2 +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via httpx +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via huggingface-hub +huggingface-hub==1.32.0 \ + --hash=sha256:b0c7c80561969d9cdacdd55fce67ba9584cca0b9d4ea80957a3a5c1445fac5c8 \ + --hash=sha256:ed70a45498abe86039df7c2f4e5f7575de524be908d3840e8f828d5525eafd6a + # via + # mlx-audio + # solomon-mlx + # tokenizers + # transformers +hyperframe==6.1.0 \ + --hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \ + --hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08 + # via h2 +idna==3.20 \ + --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \ + --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c + # via + # anyio + # httpx + # requests + # yarl +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via mlx-vlm +jmespath==1.1.0 \ + --hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \ + --hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64 + # via + # boto3 + # botocore +llguidance==1.8.0 \ + --hash=sha256:020b4ec2254a20555e69095c7d488907f17d7e65ab5a840b267530f2cb369f70 \ + --hash=sha256:0eb7be70bf822e54cd4021bb200cfe2b86e4f3379d251067dc9f7da327f3ceab \ + --hash=sha256:18d1579eabb040e65c870d50c6df19a7bef140c5260d12ad35b7f0dc446312e0 \ + --hash=sha256:39668c11396896e5f05f59b70c81e4afd060b3408f02c8518b7a6943bfbb8a5d \ + --hash=sha256:6ae4343bd40b88d1dd824a17edcee11b5e5a000b16b6fedb9fcf7f58d019177c \ + --hash=sha256:6bf3953d06e7f5e24bd02fa6a89a5b2b88f7e811c0fa0228487d8267ef7cec54 \ + --hash=sha256:79b0576991b8fc7534456b65c41d43c3183c8ca974a17799359af969c1489c07 \ + --hash=sha256:a8837ac2b3bf4c46e1b6363012a22de043b7f8ef013b04d8689d471eb573b766 \ + --hash=sha256:b5e866d8a896e255f30ec952f5280c61a3d6f391a9dce575ce976dd58f0b7000 \ + --hash=sha256:bb9a89e8cdd7c8b5cf4f84e45b04177e79acdcc4d5116fbc775e511f7314df44 + # via mlx-vlm +markdown-it-py==4.2.0 \ + --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a + # via rich +markupsafe==3.0.3 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 + # via jinja2 +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +miniaudio==1.71 \ + --hash=sha256:12bc33e7e61072b4b541c14e10ef76119d5643e6bbb98e2dec0c0738889438fb \ + --hash=sha256:19be6f0a1e601c2237433e579734cfaf6469191b224c20c9e5f73c32ef9ee2b9 \ + --hash=sha256:1bf93aeede652926f27f430f0fd69ef0cf8a949c07b537d6a2f295602c747037 \ + --hash=sha256:4c849ccb1349f7b3553a77a66fe7e972315185f5c4c44a0bbda7ebcdd224db37 \ + --hash=sha256:61b86f26d653040db32d9d15b05446321dd10e45beba25b44f841e26935213d5 \ + --hash=sha256:62db602651bc20a2698f36a0d356d7217ed6f4f917550c7ffb3705c8e8be90cf \ + --hash=sha256:70fa2ea5353e6919aca59b8c5768144af009d18c3bca251749d66fb497424563 \ + --hash=sha256:8fc1a4f084cc1b4b25c567d22f54d1e46bfa505c17ed777c8b198e5c53d0f785 \ + --hash=sha256:ab100e5240b104b5326e4ec1be07b6ae461f7d3d4d7a694857fd2f0493d210f9 \ + --hash=sha256:d9dc15eff711bcfc62a9d05e0c78e4bc34821a455595e049629f2fea7491a523 \ + --hash=sha256:e6287f15caa808a88aad0700a182bec1ff6d98769717425adf9ebf41259d1936 \ + --hash=sha256:f4a44b70b66628b0c307e40ae0ae857695978cae18462179b806d8edc807d416 \ + --hash=sha256:ff51e2887bb673e2e757752b586b3dc924d59aa5fbcae9bbc45f4a111bd3262b + # via + # mlx-audio + # mlx-vlm +mlx==0.32.2 \ + --hash=sha256:45857fadb381fea3db57d9681b04c987813fa883f3182ec9fb05bc56f468978a \ + --hash=sha256:48e8738b078eeb7bfde74931fc02d8aa9c32f05d3c0dbf97b5c3cbe0470fbddf \ + --hash=sha256:50ced716f4ab860cbda9d65adf74877923fe26036c4e28d8f572ecec57621cfc \ + --hash=sha256:583111ec13fedf63ddbfefda77dabd57168978f06474f5e5d2637180d154fcc4 \ + --hash=sha256:65beb9ce75153072808ef18913ecca3d929b87be5d7c1f57d9df391c04b6f957 \ + --hash=sha256:65d3d29b66045ed8dd2d8e437c8770de325843c364f7b7c38cd8ae90a7eec854 \ + --hash=sha256:68560fd648c5bb900aa6f6765cd74c5a8abaf092d97d73584a57b7545966c227 \ + --hash=sha256:6c615ad1c6877d7d38affe8526446955cd48d6796e871150f59145cf0d1a265d \ + --hash=sha256:77217798a2b036bae9f213b851d4cde4581893787c9964458b7d471f86036bd6 \ + --hash=sha256:8d270ade1e48e006383a6b5f33a3a4ec5c265389299a60a60bf55165af1770d9 \ + --hash=sha256:9d21abe340403b6bf445e8494590a7341b655739088c92f178d7bd4241ba0110 \ + --hash=sha256:bc69bd1062b97028ae6b79522ed0bd635a2b133fe3c398c03b66b060c20d9240 \ + --hash=sha256:c7670ffb854c11e6776349a3797fa076d1a2a00290ebe2e9cfed8b60ca4a5db0 \ + --hash=sha256:c95a384de1a0c0ba18425344cad8ac87180e3c5c1921a42e2621475be5966bcf \ + --hash=sha256:cf63fd5c32258ab07523b06401c20ee2280bf56e26e991d05bf6fd8a4d42d1f6 \ + --hash=sha256:daebf84dfb857d70e87b1b98189a057e691e99a4a2b9f6f64cadebad917f8964 \ + --hash=sha256:dc5eb3cc30d4285f2734c368442f717599e97294663441cb42959e3b856d6898 \ + --hash=sha256:df8c75e509de868fca148dfeb38d92ce956eed386569c87caeb72bd16d2d6962 \ + --hash=sha256:f6071e4973927966c12b3894deb75f4fcfc33300e0705312a80fdbbaa92a9c4f \ + --hash=sha256:f77e47e6c1e176a61ec1bb1f9e74d874482eea6eb86ffb162a1f3e789c806453 \ + --hash=sha256:fe813a4dee2daa6d5ac496ef25117034728c77fe26f713983a3b406eb6f4e8f0 + # via + # mlx-audio + # mlx-vlm + # solomon-mlx +mlx-audio==0.5.4 \ + --hash=sha256:3e1895860d9a636360a9377b5651239f5197e649d56fe25f1382cac6f2ed1e55 \ + --hash=sha256:d350ecc43a65b94b1578381370be64229948f79aef9ff599d20adb65e38351b3 + # via mlx-vlm +mlx-cpu==0.32.2 ; sys_platform == 'linux' \ + --hash=sha256:d0f94625588b51a878786dd51cec5617894ce586cf3c94645ce1b64e24c27c0b \ + --hash=sha256:fc5d31b90fc4f457b2f9614ed45651a567d22a6a11a03d114a90a4e10bdd8878 + # via mlx +mlx-metal==0.32.2 ; sys_platform == 'darwin' \ + --hash=sha256:3825fff379dbc107dd3413e564a06caeaa24819910ec49c0439e454c06a1b9b8 \ + --hash=sha256:55a369250d220b2cf10213a87a2ac1b1a420608c5b35b1df4e7147ac8e32f121 \ + --hash=sha256:e6abeac9ac5265830c9c1541b6f96e9be37a85c2446763a46ad466c63a3837ab + # via mlx +mlx-vlm==0.7.1 \ + --hash=sha256:b8abd3cc7e3513d9915bf9f6903833c697dc4c3084c2acdc880029c33e331a03 \ + --hash=sha256:d9696bc3a2e961f43b5948a101dc0c966b5989d382e6910c37e2f3d555bc3408 + # via solomon-mlx +modal==1.5.5 \ + --hash=sha256:30df363ed1898cc3d91a09ff3f95c38ab043f6b6294011b01085312c6a0ac777 \ + --hash=sha256:8d10d3ee09818aaba1973b73ce2521ab8961b63a29b5b52e3ff0d25e7a74808e + # via solomon-mlx +multidict==6.9.0 \ + --hash=sha256:0db5bf96ec2ce45a8bc7fbbe8a486089969bb2791a66b6789ee3aed0d5dd562e \ + --hash=sha256:1126782e3c3b1a7ccd990be3d3221709348e4b07d6ecf8b50965a93ae2624145 \ + --hash=sha256:11e32ccf23cdbfcf8299a6a825930a858ec9a6aa05d6752d0f90f2bdf19489e1 \ + --hash=sha256:1eb7939025bd9289d9dfe3a399102b1642f4fbb105a6af82f284052ee89e97a9 \ + --hash=sha256:22067e88ff266e6a5dc59114529332692a01cc04b10dbdc2c14ab91217dee819 \ + --hash=sha256:254e53be2ec70518bb82dfa9b0c7166baaf2bd0e918bd17b65caaf489d5a5522 \ + --hash=sha256:2ea72901860ccbe94517421681c60b13533ce03ba2f7bd96293c3a4d16ac4ccb \ + --hash=sha256:3e78870909e9f9e3d672ba99f1eb75130d7e01c42e642e6a70f434df32ca0ee1 \ + --hash=sha256:3ec1e387b1f8a85ae5b94aa8c4e0576912ffa4d31bd0578f24c950d4f05ee476 \ + --hash=sha256:408fac672931f2458be3bc8c89d9facd16dac2aad17c7cee2ca1693eee99f07e \ + --hash=sha256:4bb769ccc72e15d7d441e1a08f169d418376be77cdc387e813129c26b357fe50 \ + --hash=sha256:4be612f23990a261060ccd8f15e89dad1f9b7bb0c1021c5c3987f5fc57959391 \ + --hash=sha256:4bef8cb5edea9c9daeb8396a75eeb97f912c3fb3fa408fe659edfecf91e47424 \ + --hash=sha256:516fa4817cd070088f616a901380db56189f86daee9da79aada3c9b653f49ed7 \ + --hash=sha256:51a08dceed4b42ef25755ee2cbf50df325e90e6b415d00955ffdea2c394ad6c0 \ + --hash=sha256:57c2445049f7d8e66306f712868219da7ff7168ef42263dc032401211bf1205c \ + --hash=sha256:640113258c5925a9eed2c12523410b25565ac5df2fa6735fbae88fb09bcdd212 \ + --hash=sha256:662315f8621b3559268813b11134c1f9633edd4ab5f7b713688a243c13f4026d \ + --hash=sha256:67bcff396d2ad62197c95488a716a88462792b384b5ea6e44bf7c1070eddb8fc \ + --hash=sha256:67bed23e9803945b0760650ec3e903af772c49abe869f3bc03c66b2e7649d5ef \ + --hash=sha256:6a5111a2bd824c821a3dd09da29680391b0caaa18fea7761358f4001e6898d1c \ + --hash=sha256:75f7fc21ffce9a792cb919f0e0b0b52117bd672f2a55d1929d573b6fc437f374 \ + --hash=sha256:7aaa14f0b9ffa2780c5d3b21da0e9b58778ed47af3369df72e5fd6dc10ff8041 \ + --hash=sha256:85ed0f3c3b01174aea5a8a8fcd456f64c2e9719c0f42c612c2694ecd35974a65 \ + --hash=sha256:8bb6be697065cbf31051465f9939d65624573d2219c3da8316db9e7cf5e0f5a7 \ + --hash=sha256:8e991677c4bdc5d9f2e71c74717a4e32cfe98930ca05becdf722b5eae1329d6a \ + --hash=sha256:8f06c4da5315a6f709b13408c3e13f3b475f8c559ec7608c3c67062512871235 \ + --hash=sha256:91092d597fcf0940cd6a59e64ba7156ee22d6899993fd7c5b1897fda71482f8f \ + --hash=sha256:95d339c3b75b4a50c665bdcf8417428cd71c3e5cd48e194cb1d336fcb856beac \ + --hash=sha256:97555ad30ad20a8eb90fa86522088eebbbd68aba03e56d53f4850b3535c65e61 \ + --hash=sha256:9869105ab61db13004f9ed610cd29ce4237b2c609f5f7b8bb8f2339fb9dad17d \ + --hash=sha256:b33e499a7b1f722547d57b4865ed68c03161643b9afe49fe097a0832078d183d \ + --hash=sha256:bb69b724c345420ba49187a17a894f146099f5b2e501df42ddb4452ac8be37fa \ + --hash=sha256:beda95a6bd0a2e2265b941b90ff72543afbeaebb6a450e3a6a010d10d4a2ffca \ + --hash=sha256:c51fd8d59e72e45c64907bf7f81fbd94f4c8dcd1c8b2f23f4c2d7ede788fcfc3 \ + --hash=sha256:c7ab60b91e11b25e7682c5cd8763fdd17929ea83f234ba441091f1492e631ea3 \ + --hash=sha256:cb847cb4002e725f88ffe29883da8290fd0754fa0461ed8988735e20964a9f65 \ + --hash=sha256:cff3cff5a725bdb8359962de8d7429aea592d7693dbd197eeabbdfab9b6300e9 \ + --hash=sha256:d7d32c0543494efbc9394e2b571725071d08e295993486bc9a43f6f89375ee01 \ + --hash=sha256:e231de8ce43d4fd10bec4f67f71238ed88a974de30a9f20be7a4ab16c970a988 \ + --hash=sha256:e4826f6b56456fb1e98111d7bc20cbdc7fa0a41b1f9ad80ff2dd3f2f5b226fe1 \ + --hash=sha256:e71a072c52c78b7f97cd4611df6cef10977e4f2367cd0654a7626192f931adde \ + --hash=sha256:f93c9058a0eceac0df2ce9d4c8823b84786ee598194753c2ca0c100224405e47 \ + --hash=sha256:fca5b74b5909c29041f857c40d51d9273636fb4221cf020e4c452deb1c448a40 \ + --hash=sha256:fdd484b84d3394e805689c56be3ab1f877ae7ff0eb9ff90a3ee7a755cbebab4f + # via + # aiohttp + # grpclib + # yarl +numpy==2.5.3 \ + --hash=sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def \ + --hash=sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0 \ + --hash=sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f \ + --hash=sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff \ + --hash=sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034 \ + --hash=sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec \ + --hash=sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58 \ + --hash=sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204 \ + --hash=sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e \ + --hash=sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321 \ + --hash=sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0 \ + --hash=sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034 \ + --hash=sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3 \ + --hash=sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231 \ + --hash=sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4 \ + --hash=sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b \ + --hash=sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9 \ + --hash=sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034 \ + --hash=sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958 \ + --hash=sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563 \ + --hash=sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c \ + --hash=sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09 \ + --hash=sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e + # via + # mlx-audio + # mlx-vlm + # opencv-python + # scipy + # solomon-mlx + # transformers +opencv-python==5.0.0.93 \ + --hash=sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2 \ + --hash=sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898 \ + --hash=sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157 \ + --hash=sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2 \ + --hash=sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b \ + --hash=sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039 \ + --hash=sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac \ + --hash=sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881 \ + --hash=sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2 + # via mlx-vlm +packaging==26.3 \ + --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c + # via + # huggingface-hub + # transformers +pillow==12.3.0 \ + --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 \ + --hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df \ + --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 \ + --hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 \ + --hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 \ + --hash=sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce \ + --hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace \ + --hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a \ + --hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e \ + --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 \ + --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 \ + --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 \ + --hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f \ + --hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec \ + --hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b \ + --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 \ + --hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 \ + --hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 \ + --hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f \ + --hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c \ + --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 \ + --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7 + # via + # mlx-vlm + # solomon-mlx +propcache==0.5.4 \ + --hash=sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77 \ + --hash=sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b \ + --hash=sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba \ + --hash=sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25 \ + --hash=sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c \ + --hash=sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111 \ + --hash=sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc \ + --hash=sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef \ + --hash=sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468 \ + --hash=sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177 \ + --hash=sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab \ + --hash=sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47 \ + --hash=sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944 \ + --hash=sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21 \ + --hash=sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737 \ + --hash=sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161 \ + --hash=sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9 \ + --hash=sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb \ + --hash=sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526 \ + --hash=sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546 \ + --hash=sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1 \ + --hash=sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f \ + --hash=sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894 \ + --hash=sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429 \ + --hash=sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8 \ + --hash=sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c \ + --hash=sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8 \ + --hash=sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867 \ + --hash=sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937 \ + --hash=sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031 \ + --hash=sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9 \ + --hash=sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c \ + --hash=sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8 \ + --hash=sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0 \ + --hash=sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572 \ + --hash=sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558 + # via + # aiohttp + # yarl +protobuf==6.33.6 \ + --hash=sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326 \ + --hash=sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901 \ + --hash=sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3 \ + --hash=sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a \ + --hash=sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135 \ + --hash=sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3 \ + --hash=sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2 \ + --hash=sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593 + # via modal +pycparser==3.0 ; implementation_name != 'PyPy' \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via cffi +pydantic==2.13.5 \ + --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73 \ + --hash=sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08 + # via fastapi +pydantic-core==2.46.5 \ + --hash=sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942 \ + --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2 \ + --hash=sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc \ + --hash=sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5 \ + --hash=sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0 \ + --hash=sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931 \ + --hash=sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038 \ + --hash=sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b \ + --hash=sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433 \ + --hash=sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29 \ + --hash=sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b \ + --hash=sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8 \ + --hash=sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e \ + --hash=sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1 \ + --hash=sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f \ + --hash=sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761 \ + --hash=sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869 \ + --hash=sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5 \ + --hash=sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c \ + --hash=sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea \ + --hash=sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519 \ + --hash=sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e \ + --hash=sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0 \ + --hash=sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4 \ + --hash=sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d \ + --hash=sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f \ + --hash=sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5 \ + --hash=sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575 \ + --hash=sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62 \ + --hash=sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a \ + --hash=sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed \ + --hash=sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f \ + --hash=sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3 \ + --hash=sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff \ + --hash=sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084 + # via pydantic +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c + # via rich +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via botocore +python-multipart==0.0.32 \ + --hash=sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e \ + --hash=sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23 + # via mlx-vlm +pyyaml==6.0.3 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via + # huggingface-hub + # transformers +regex==2026.9.10 \ + --hash=sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4 \ + --hash=sha256:048a89ee797db10160bd2bd519286577a6b43a100279bd4b7d8456a3d69c80a0 \ + --hash=sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8 \ + --hash=sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0 \ + --hash=sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d \ + --hash=sha256:20e8bfb07ad79a282f8b95b56fe67f9750b1b7f775724e4ba1f23cb296115ce4 \ + --hash=sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348 \ + --hash=sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd \ + --hash=sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1 \ + --hash=sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0 \ + --hash=sha256:3bdeed3318a8eb2bbadc9c56347e0ff651639e934a47e168d05a3b12929fd0e7 \ + --hash=sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1 \ + --hash=sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86 \ + --hash=sha256:5847e22bbf959764d776937d791d034cc2d19b787e361c88d97e859e8dc68502 \ + --hash=sha256:6aebdd9a946de328b3f6f61dbf48dd064a36eb6dddf96e34ae6651d37f6e9383 \ + --hash=sha256:6b34a778c695d24e77c140e3b4c95da69282e34f2f6b02b55656aa4a0379f643 \ + --hash=sha256:79e9432995e14c749d34209413de5e621ec8e67789bf4f46dbfabea9d06a2406 \ + --hash=sha256:7abb38b8c40f3a235235a44da452c64b7b5c1d650ec6351027db0e090804f2e5 \ + --hash=sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb \ + --hash=sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db \ + --hash=sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a \ + --hash=sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041 \ + --hash=sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3 \ + --hash=sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944 \ + --hash=sha256:c103b3b14e011774af4fb7e4617ad4d72b9171905cd3b231a70a4efd76e477d7 \ + --hash=sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae \ + --hash=sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23 \ + --hash=sha256:d8c668af8f7bdb1d18739c27d30cd9f4b371495a883f75a002fb7a39d740fecd \ + --hash=sha256:dce932f8e3ba936475ea3d0d8b59f7b050a9e206e994f53f8fd80299871e87da \ + --hash=sha256:e0dc78251154b66dc60211563fc115345da332eaa881e4e2523fb1edae3772f4 \ + --hash=sha256:ebb2ba68e4641a994061f70bf44ed448fba0b9b1d18c94ffb9efc1cca805b39b \ + --hash=sha256:ef5a059ea1c6ee5d1c7e99a2484e628608d010921efe876c6f0e2029d2f35eca \ + --hash=sha256:f2374c27deb189b282ec7e16106752c22ad39b056bbd8018960b1e4cc95d67a1 + # via transformers +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed + # via mlx-vlm +rich==15.0.0 \ + --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \ + --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36 + # via + # modal + # typer +s3transfer==0.19.2 \ + --hash=sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993 \ + --hash=sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25 + # via boto3 +safetensors==0.8.0 \ + --hash=sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358 \ + --hash=sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f \ + --hash=sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d \ + --hash=sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d \ + --hash=sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0 \ + --hash=sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc \ + --hash=sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235 \ + --hash=sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98 \ + --hash=sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4 \ + --hash=sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846 \ + --hash=sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca \ + --hash=sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0 \ + --hash=sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25 \ + --hash=sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452 \ + --hash=sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d \ + --hash=sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78 \ + --hash=sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774 + # via + # solomon-mlx + # transformers +scipy==1.18.1 \ + --hash=sha256:3ab3523da44749156e1f68b464dc56af11ae4cbc5c739a49d05f32b982eca9f3 \ + --hash=sha256:3c085faa2cfa879c5141df483f836f4d691045a078224a670fa570fa01612d89 \ + --hash=sha256:457fd7a2a8edeb044ab6ffbc0aa03ff6cd18491356e5e0c834d76ce621b916d1 \ + --hash=sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307 \ + --hash=sha256:559ed65f60c1af5a03f3912605a1b5114f522c7c32fb23c3376ae8f03219fe28 \ + --hash=sha256:5e4d44984abc0020154ea81b247adeddcc3ac5527b975ff798bd1ba0adc513c2 \ + --hash=sha256:75b00eb8fb802090aa903f4ea1c7f5a584779f967361e68b7e98e531cc2d7174 \ + --hash=sha256:78c0665edead396b1abb4897c41a5c1d9bf090c8a637a4c20a61678e0a264e66 \ + --hash=sha256:7bbf207c4453ce1ad2e00b17313852b33310b83090c2311bdaf97f93c0380d12 \ + --hash=sha256:c35d74ce0e193ff740c2f2be2ac913ddc232fe6c1ff40b26cfecb9c670c63314 \ + --hash=sha256:c825cef2f49e46753726a7181a8e199804a912b29519ada542c6ebc654951899 \ + --hash=sha256:cd479fc04dd9401e3b4f49e76518768ef99c4f517a98c284eb091fd725719adf \ + --hash=sha256:d2924a03db38dc2e848bca2fe9f077dafb891480b91a00a0963a8cf86dfc31c1 \ + --hash=sha256:d416b16cccfd70fbf62400e84d0bb2f4e6af519a45557f1692c749b37f14b315 \ + --hash=sha256:d65d448389b8436493abcf629cc94ad0cf32aecaf06e1acca1de53cc795f2f12 \ + --hash=sha256:e3b417bf8c2c7c16e8f58ad91db17783ec911ac16e7b50eb6eab6e809b4f5b07 \ + --hash=sha256:e6fb6a55cc0ba97b59a1f288fb86dc6fce8bdfc0fffcbfd015e3a954bf2a2d93 \ + --hash=sha256:e708533e8b2ae2497d65346538a7dcc92814410b25b81432eac66de0f2af8265 \ + --hash=sha256:ea324d9dd34c38bfb9bec8ca4d1b407db97dbb74029f566b8e322b1b6fe56fe6 \ + --hash=sha256:f55fa87b6c612ecd6b058f167c53231b1d14e412efe361d3d6e38b3631c73218 \ + --hash=sha256:fdaf5ea890a6183d0565f51a61799d67081bd5b1cf03c5f4b3fd3732108625c9 + # via + # mlx-audio + # solomon-mlx +sentencepiece==0.2.2 \ + --hash=sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b \ + --hash=sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a \ + --hash=sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838 \ + --hash=sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5 \ + --hash=sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6 \ + --hash=sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d \ + --hash=sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd \ + --hash=sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b \ + --hash=sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb \ + --hash=sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78 \ + --hash=sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9 \ + --hash=sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d \ + --hash=sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8 \ + --hash=sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383 \ + --hash=sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53 \ + --hash=sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0 \ + --hash=sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91 \ + --hash=sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da \ + --hash=sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a \ + --hash=sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563 \ + --hash=sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820 \ + --hash=sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c + # via mlx-vlm +shellingham==1.5.4 \ + --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ + --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de + # via typer +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil +sounddevice==0.5.6 \ + --hash=sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4 \ + --hash=sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410 \ + --hash=sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c \ + --hash=sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45 \ + --hash=sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0 \ + --hash=sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c + # via mlx-audio +starlette==1.6.0 \ + --hash=sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c \ + --hash=sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b + # via + # fastapi + # mlx-vlm +synchronicity==0.12.5 \ + --hash=sha256:94d96b1d85698e3056b96a793b8c0949af6584e4a7d877fabdeb5385efe230aa \ + --hash=sha256:fdbbb10d437bc08a6b0f814fc66fddd1b58ffed314533d42f1ab555801e781af + # via modal +tokenizers==0.23.2 \ + --hash=sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78 \ + --hash=sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde \ + --hash=sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7 \ + --hash=sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305 \ + --hash=sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb \ + --hash=sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718 \ + --hash=sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5 \ + --hash=sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac \ + --hash=sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90 \ + --hash=sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703 \ + --hash=sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf \ + --hash=sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2 \ + --hash=sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef \ + --hash=sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a \ + --hash=sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa \ + --hash=sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40 \ + --hash=sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835 + # via transformers +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via modal +tqdm==4.70.1 \ + --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73 \ + --hash=sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4 + # via + # huggingface-hub + # mlx-audio + # mlx-vlm + # transformers +transformers==5.17.0 \ + --hash=sha256:78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801 \ + --hash=sha256:a153be279169b55b92d8000bf4af294aed684503d091cca7804da2dd8a9de000 + # via + # mlx-audio + # mlx-vlm + # solomon-mlx +typer==0.27.2 \ + --hash=sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945 \ + --hash=sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d + # via transformers +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via modal +types-toml==0.10.8.20260518 \ + --hash=sha256:0e564ab05f6fde62a315b3b5a9b6624fda569399795d30a37e64705a70459303 \ + --hash=sha256:80e10facd24fdeda9d5c672187d72be3ac284843788d67f5aae59e3e016db6fe + # via modal +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # aiohttp + # aiosignal + # anyio + # fastapi + # huggingface-hub + # modal + # pydantic + # pydantic-core + # starlette + # synchronicity + # typing-inspection +typing-inspection==0.4.4 \ + --hash=sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47 \ + --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147 + # via + # fastapi + # pydantic +urllib3==2.8.0 \ + --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \ + --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63 + # via + # botocore + # requests +uvicorn==0.53.0 \ + --hash=sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e \ + --hash=sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e + # via mlx-vlm +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 + # via modal +websockets==17.1 \ + --hash=sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3 \ + --hash=sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f \ + --hash=sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d \ + --hash=sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6 \ + --hash=sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391 \ + --hash=sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c \ + --hash=sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906 \ + --hash=sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e \ + --hash=sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608 \ + --hash=sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c \ + --hash=sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad \ + --hash=sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780 \ + --hash=sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98 \ + --hash=sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11 \ + --hash=sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641 \ + --hash=sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec \ + --hash=sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090 \ + --hash=sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8 \ + --hash=sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3 \ + --hash=sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a \ + --hash=sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2 \ + --hash=sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb \ + --hash=sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc \ + --hash=sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab \ + --hash=sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74 \ + --hash=sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729 \ + --hash=sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce \ + --hash=sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c \ + --hash=sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a \ + --hash=sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51 \ + --hash=sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60 \ + --hash=sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435 \ + --hash=sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08 \ + --hash=sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0 \ + --hash=sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1 \ + --hash=sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067 \ + --hash=sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c \ + --hash=sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837 \ + --hash=sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23 \ + --hash=sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a \ + --hash=sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4 \ + --hash=sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80 + # via mlx-vlm +yarl==1.25.1 \ + --hash=sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7 \ + --hash=sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa \ + --hash=sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec \ + --hash=sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26 \ + --hash=sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651 \ + --hash=sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc \ + --hash=sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9 \ + --hash=sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5 \ + --hash=sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a \ + --hash=sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b \ + --hash=sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c \ + --hash=sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184 \ + --hash=sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58 \ + --hash=sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3 \ + --hash=sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707 \ + --hash=sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03 \ + --hash=sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40 \ + --hash=sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54 \ + --hash=sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d \ + --hash=sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4 \ + --hash=sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a \ + --hash=sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df \ + --hash=sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68 \ + --hash=sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe \ + --hash=sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85 \ + --hash=sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab \ + --hash=sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d \ + --hash=sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1 \ + --hash=sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6 \ + --hash=sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3 \ + --hash=sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad \ + --hash=sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d \ + --hash=sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f \ + --hash=sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a \ + --hash=sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98 \ + --hash=sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc + # via aiohttp diff --git a/mlx/requirements.lock b/mlx/requirements.lock new file mode 100644 index 0000000000000000000000000000000000000000..4a13dab02931d5771745f19709d677113f1deb5a --- /dev/null +++ b/mlx/requirements.lock @@ -0,0 +1,79 @@ +aiohappyeyeballs==2.7.1 +aiohttp==3.14.3 +aiosignal==1.4.0 +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.15.1 +attrs==26.1.0 +cbor2==6.1.4 +certifi==2026.7.22 +cffi==2.1.1 +charset-normalizer==3.5.1 +click==8.5.0 +fastapi==0.141.1 +filelock==4.0.1 +frozenlist==1.8.0 +fsspec==2026.9.0 +grpclib==0.4.9 +h11==0.16.0 +h2==4.4.1 +hf-xet==1.6.0 +hpack==4.2.0 +httpcore==1.0.9 +httpx==0.28.1 +huggingface-hub==1.32.0 +hyperframe==6.1.0 +idna==3.20 +iniconfig==2.3.0 +jinja2==3.1.6 +llguidance==1.8.0 +markdown-it-py==4.2.0 +markupsafe==3.0.3 +mdurl==0.1.2 +miniaudio==1.71 +mlx==0.32.2 +mlx-audio==0.5.4 +mlx-metal==0.32.2 +mlx-vlm==0.7.1 +modal==1.5.5 +multidict==6.9.0 +numpy==2.5.3 +opencv-python==5.0.0.93 +packaging==26.3 +pillow==12.3.0 +pluggy==1.6.0 +propcache==0.5.4 +protobuf==6.33.6 +pycparser==3.0 +pydantic==2.13.5 +pydantic-core==2.46.5 +pygments==2.21.0 +pytest==9.1.1 +python-multipart==0.0.32 +pyyaml==6.0.3 +regex==2026.9.10 +requests==2.34.2 +rich==15.0.0 +ruff==0.16.8 +safetensors==0.8.0 +scipy==1.18.1 +sentencepiece==0.2.2 +shellingham==1.5.4 +-e . +sounddevice==0.5.6 +starlette==1.6.0 +synchronicity==0.12.5 +tokenizers==0.23.2 +toml==0.10.2 +tqdm==4.70.1 +transformers==5.17.0 +typer==0.27.2 +types-certifi==2021.10.8.3 +types-toml==0.10.8.20260518 +typing-extensions==4.16.0 +typing-inspection==0.4.4 +urllib3==2.8.0 +uvicorn==0.53.0 +watchfiles==1.2.0 +websockets==17.1 +yarl==1.25.1 diff --git a/mlx/scripts/benchmark.py b/mlx/scripts/benchmark.py new file mode 100644 index 0000000000000000000000000000000000000000..ab5de9c01aeb03a0da8a81f585f4c0fe7a62e710 --- /dev/null +++ b/mlx/scripts/benchmark.py @@ -0,0 +1,122 @@ +"""Full-model parity and measurements on this Mac; emits no simulated hardware results.""" + +import argparse +import json +import time +from pathlib import Path + +import mlx.core as mx +import numpy as np + +from solomon_mlx import Solomon +from solomon_mlx._vendor.semantics import listed_probs, p_yes +from solomon_mlx.artifacts import digest, sha256 + + +def probabilities(job, row): + if job["task"] in ("boolean", "multilabel", "entity"): + p = p_yes(row["letter_logits"]) + return np.array([p, 1 - p]) + n = job["n"] - 2 if job["head_key"].endswith("choiceR") else job["n"] + return listed_probs(row["letter_logits"], n) + + +def run(model_dir, jobs_path, reference_path, output): + output = Path(output) + if output.exists(): + raise FileExistsError("Benchmark outputs are immutable") + jobs = json.loads(Path(jobs_path).read_text()) + reference = json.loads(Path(reference_path).read_text()) + ref = {r["id"]: r for r in reference["rows"]} + if set(ref) != {j["id"] for j in jobs}: + raise ValueError("Benchmark and reference jobs differ") + mx.reset_peak_memory() + started = time.perf_counter() + model = Solomon.load(model_dir) + load_seconds = time.perf_counter() - started + states = {} + rows = [] + prefills = [] + try: + for job in jobs: + key = digest(job["parts"]) + if key not in states: + state = model.prefill(job["parts"]) + states[key] = state + prefills.append( + { + "document": key, + "tokens": state.prefix_tokens, + "seconds": state._data["prefill_seconds"], + "vision_seconds": state._data["vision_seconds"], + "cache_bytes": sum(c.nbytes for c in state._data["cache"]), + } + ) + state = states[key] + row = model.engine.ask( + state._data, + job["block"], + job["n"], + job["head_key"], + execution=job.get("execution", "cached"), + taps=job.get("taps", []), + ) + p, q = probabilities(job, row), probabilities(job, ref[job["id"]]) + row.update( + id=job["id"], + decision_agrees=bool(p.argmax() == q.argmax()), + max_probability_drift=float(np.max(np.abs(p - q))), + max_logit_drift=float( + np.max(np.abs(np.array(row["letter_logits"]) - ref[job["id"]]["letter_logits"])) + ), + prefix_ids_exact=state._data["prefix_ids"] == ref[job["id"]]["prefix_ids"], + ) + if "token_ids" in row: + row["token_ids_exact"] = row["token_ids"] == ref[job["id"]]["token_ids"] + if row.get("taps"): + row["layer_max_hidden_drift"] = { + k: float(np.max(np.abs(np.array(v) - ref[job["id"]]["taps"][k]))) + for k, v in row["taps"].items() + } + rows.append(row) + print(job["id"], row["seconds"], row["decision_agrees"], flush=True) + # Replay checks real prefill and repeated question semantics on the same binding. + first = states[digest(jobs[0]["parts"])] + recipe = output.with_suffix(".replay.json") + first.save(recipe) + with model.replay(recipe) as restored: + b, n, h = jobs[0]["block"], jobs[0]["n"], jobs[0]["head_key"] + replay = model.engine.ask(restored._data, b, n, h) + replay_drift = float(np.max(np.abs(np.array(replay["letter_logits"]) - rows[0]["letter_logits"]))) + finally: + for state in states.values(): + state.close() + warm = [r["seconds"] for r in rows if r["reused_prefix_tokens"]] + report = { + "runtime": model.identity, + "device": mx.device_info(), + "jobs_sha256": sha256(jobs_path), + "reference_sha256": sha256(reference_path), + "load_seconds": load_seconds, + "prefills": prefills, + "rows": rows, + "warm_question_latency_median_seconds": float(np.median(warm)), + "questions_per_second": len(warm) / sum(warm), + "peak_metal_bytes": mx.get_peak_memory(), + "replay_max_logit_drift": replay_drift, + "decision_agreement": float(np.mean([r["decision_agrees"] for r in rows])), + "scope": "development parity fixtures; not held-out task accuracy or release qualification", + "calibration_status": "uncalibrated", + } + output.write_text(json.dumps(report, indent=2)) + return report + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--model", default="models/quality") + p.add_argument("--jobs", default="evaluations/golden-jobs.json") + p.add_argument("--reference", default="evaluations/bf16-reference-1789901869/report.json") + p.add_argument("--output", default="evaluations/bf16-text-benchmark.json") + a = p.parse_args() + run(a.model, a.jobs, a.reference, a.output) diff --git a/mlx/scripts/benchmark_chunks.py b/mlx/scripts/benchmark_chunks.py new file mode 100644 index 0000000000000000000000000000000000000000..8db1141581c2af51a03cca7e0606795f9752a00c --- /dev/null +++ b/mlx/scripts/benchmark_chunks.py @@ -0,0 +1,50 @@ +"""Measure 512/1024/2048-token prefill on frozen original acceptance documents.""" + +import argparse +import json +import time +from pathlib import Path + +import mlx.core as mx + +from solomon_mlx import Solomon + +p = argparse.ArgumentParser() +p.add_argument("--model", default="models/quality") +p.add_argument("--output", default="evaluations/chunk-benchmark.json") +a = p.parse_args() +out = Path(a.output) +if out.exists(): + raise FileExistsError("Use a new immutable benchmark output") +model = Solomon.load(a.model) +fixtures = json.loads(Path("evaluations/cuda-acceptance/input/documents.json").read_text()) +results = [] +for document, parts in fixtures["documents"].items(): + for target in (1242, 2048): + text = "".join(p["text"] for p in parts) + while True: + rendered = model.engine.render([{"text": text}], "X") + end = rendered.rfind("\n\nX") + length = len(model.engine.t.encode(rendered[:end], add_special_tokens=False)) - 1 + if length >= target: + break + text += fixtures["filler"] + for chunk in (512, 1024, 2048): + model.engine.chunk_size = chunk + mx.reset_peak_memory() + started = time.perf_counter() + with model.prefill(text) as state: + results.append( + { + "document": document, + "chunk": chunk, + "tokens": state.prefix_tokens, + "seconds": time.perf_counter() - started, + "peak_metal_bytes": mx.get_peak_memory(), + "cache_bytes": sum(c.nbytes for c in state._data["cache"]), + } + ) + print(results[-1], flush=True) +out.write_text( + json.dumps({"runtime": model.identity, "measurements": results, "hardware_simulation": False}, indent=2) +) diff --git a/mlx/scripts/check_cuda_parity.py b/mlx/scripts/check_cuda_parity.py new file mode 100644 index 0000000000000000000000000000000000000000..fedc88acfb8fd2fe2792450f2291ac9b4775f965 --- /dev/null +++ b/mlx/scripts/check_cuda_parity.py @@ -0,0 +1,172 @@ +"""Compare saved MLX scores with CUDA using identical, frozen temperatures. + +No fitting, parameter selection, or changes to inference weights take place. +Partial panels are diagnostic only and can never qualify a release. +""" + +import argparse +import json +from collections import defaultdict +from pathlib import Path + +import numpy as np + +from solomon_mlx._vendor.semantics import listed_probs, p_yes +from solomon_mlx.api import TASKS +from solomon_mlx.artifacts import digest, runtime_identity, sha256 +from solomon_mlx.evaluation import compare_rows, load_panel, read_cuda_scores + + +def decision_probabilities(row, temperature): + """Parity includes every branch, even when its gold label is not a listed option.""" + if row["task"] in ("boolean", "entity", "multilabel"): + p = p_yes(row["letter_logits"], temperature) + return np.array([1 - p, p]) + width = row["n"] - 2 if row["head_key"].endswith("choiceR") else row["n"] + return listed_probs(row["letter_logits"], width, temperature) + + +def compare(panel, scores, cuda_directory, reference, output, *, allow_partial=False): + panel, scores, output = Path(panel), Path(scores), Path(output) + if output.exists(): + raise FileExistsError("Parity reports are immutable") + jobs, manifest = load_panel(panel) + identity = json.loads((scores / "identity.json").read_text()) + model_binding = json.loads(Path("models/quality/binding.json").read_text()) + if identity["runtime"] != runtime_identity(model_binding): + raise ValueError("Scores belong to another MLX runtime") + if identity["panel_sha256"] != manifest["jobs_sha256"]: + raise ValueError("Scores belong to another panel") + groups = defaultdict(list) + for job in jobs: + groups[job["document_key"]].append(job) + rows, files = [], {} + for key, group in groups.items(): + path = scores / (key + ".json") + if not path.exists() and allow_partial: + continue + record = json.loads(path.read_text()) + body = {k: v for k, v in record.items() if k != "sha256"} + if ( + record["sha256"] != digest(body) + or record["identity"] != digest(identity) + or [r["id"] for r in record["rows"]] != [r["id"] for r in group] + ): + raise ValueError("Corrupt or mismatched score document") + rows.extend(record["rows"]) + files[path.name] = sha256(path) + complete = len(files) == len(groups) + if not allow_partial: + marker = json.loads((scores / "complete.json").read_text()) + if marker != { + "identity": digest(identity), + "documents": len(groups), + "branches": len(jobs), + "files": files, + }: + raise ValueError("Incomplete or mismatched completion manifest") + ref = json.loads(Path(reference).read_text()) + cuda = read_cuda_scores(cuda_directory, panel, ref["identity"]) + selected = {r["id"] for r in rows} + cuda = [r for r in cuda if r["id"] in selected] + binding_path = Path("evaluations/cuda-acceptance/input/serving-binding.json") + source_manifest = json.loads((binding_path.parent / "manifest.json").read_text()) + if sha256(binding_path) != source_manifest["files"][binding_path.name]: + raise ValueError("CUDA acceptance binding checksum mismatch") + binding = json.loads(binding_path.read_text()) + for key in ( + "adapter_sha256", + "trained_heads_sha256", + "model_sha256", + "numerics", + "placement", + "arithmetic", + ): + if binding["runtime"][key] != ref["identity"][key]: + raise ValueError("CUDA temperatures belong to another reference") + temperatures = {task: binding["temperatures"]["models"][task]["temperature"] for task in TASKS} + comparisons = {} + cuda_by_id = {r["id"]: r for r in cuda} + for name, temps in (("temperature_one", dict.fromkeys(TASKS, 1.0)), ("cuda_serving", temperatures)): + result = compare_rows(rows, cuda, temperatures=temps, reference_temperatures=temps) + result.pop("quality_gate_passed") + result["accuracy_units"] = result["units"] + result["accuracy_questions"] = result["questions"] + worst, questions = [], defaultdict(list) + for row in rows: + other = {**row, "letter_logits": cuda_by_id[row["id"]]["letter_logits"]} + p = decision_probabilities(row, temps[row["task"]]) + q = decision_probabilities(other, temps[row["task"]]) + if not np.isfinite(p).all() or not np.isfinite(q).all(): + raise ValueError("Nonfinite parity probability") + agrees = int(np.argmax(p)) == int(np.argmax(q)) + questions[row["question_id"]].append(agrees) + worst.append( + { + "id": row["id"], + "task": row["task"], + "max_probability_drift": float(np.max(np.abs(p - q))), + "decision_agrees": agrees, + } + ) + result.update( + units=len(rows), + questions=len(questions), + unit_decision_agreement=float(np.mean([r["decision_agrees"] for r in worst])), + question_decision_agreement=float(np.mean([all(v) for v in questions.values()])), + max_probability_drift=max(r["max_probability_drift"] for r in worst), + mean_probability_drift=float(np.mean([r["max_probability_drift"] for r in worst])), + ) + result["agreement_gate_passed"] = ( + result["unit_decision_agreement"] >= 0.999 and result["question_decision_agreement"] >= 0.999 + ) + result["largest_probability_differences"] = sorted( + worst, key=lambda r: r["max_probability_drift"], reverse=True + )[:10] + comparisons[name] = result + report = { + "scope": "complete text parity panel" if complete else "partial text parity diagnostic", + "complete": complete, + "documents": len(files), + "total_documents": len(groups), + "branches": len(rows), + "total_branches": len(jobs), + "tasks": sorted({r["task"] for r in rows}), + "runtime": identity["runtime"], + "cuda_runtime": ref["identity"], + "panel_sha256": identity["panel_sha256"], + "score_files_sha256": digest(files), + "cuda_serving_binding_sha256": sha256(binding_path), + "temperature_fitting_performed": False, + "temperature_policy": "identical settings on both backends; not MLX calibration", + "comparisons": comparisons, + "parity_gate_passed": complete and all(r["agreement_gate_passed"] for r in comparisons.values()), + "bitwise_equality_claimed": False, + "image_qualification": False, + } + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(report, indent=2)) + print( + json.dumps( + {k: report[k] for k in ("scope", "documents", "branches", "tasks", "parity_gate_passed")}, + indent=2, + ) + ) + return report + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + for field in ("panel", "scores", "cuda-directory", "output"): + parser.add_argument("--" + field, required=True) + parser.add_argument("--reference", default="evaluations/bf16-reference-1789901869/report.json") + parser.add_argument("--allow-partial", action="store_true") + args = parser.parse_args() + compare( + args.panel, + args.scores, + args.cuda_directory, + args.reference, + args.output, + allow_partial=args.allow_partial, + ) diff --git a/mlx/scripts/score_parity.py b/mlx/scripts/score_parity.py new file mode 100644 index 0000000000000000000000000000000000000000..8d11bf20e032584f8c59e05945c3b8e223f518c3 --- /dev/null +++ b/mlx/scripts/score_parity.py @@ -0,0 +1,19 @@ +"""Score a frozen panel for CUDA parity without fitting temperatures.""" + +import argparse + +from solomon_mlx import Solomon +from solomon_mlx.evaluation import score_panel + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--model", default="models/quality") + parser.add_argument("--panel", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + score_panel(Solomon.load(args.model), args.panel, args.output) + + +if __name__ == "__main__": + main() diff --git a/mlx/scripts/validate_api.py b/mlx/scripts/validate_api.py new file mode 100644 index 0000000000000000000000000000000000000000..c13b8cdc021a10e4bd4ea1b719b2f2fb97967dc6 --- /dev/null +++ b/mlx/scripts/validate_api.py @@ -0,0 +1,128 @@ +"""Focused public API checks using the real BF16 model, without calibration.""" + +import argparse +import json +from pathlib import Path + +import numpy as np + +from solomon_mlx import Solomon + + +def validate(output): + output = Path(output) + if output.exists(): + raise FileExistsError("Validation outputs are immutable") + model = Solomon.load("models/quality") + document = "Alice is certified. Bob is not certified. The current priority is high." + questions = { + "boolean": "Is Alice certified?", + "single": {"type": "choice", "instructions": "Who is certified?", "options": ["Alice", "Bob"]}, + "ordered": {"type": "score", "instructions": "What is the priority?", "levels": ["low", "high"]}, + "entity": {"instructions": "Is {candidate} certified?", "candidates": ["Alice", "Bob"]}, + "multilabel": { + "instructions": "Which facts apply?", + "candidates": ["Alice is certified", "Bob is certified"], + }, + } + checks = {} + with model.prefill(document) as state: + first = model.decide(state=state, questions=questions, evidence="none", diagnostics=True) + assert set(first["answers"]) == set(questions) + checks["all_five_answer_types"] = True + repeated = model.decide( + state=state, questions={"boolean": questions["boolean"]}, evidence="none", diagnostics=True + ) + a = first["answers"]["boolean"]["branches"][0]["letter_logits"] + b = repeated["answers"]["boolean"]["branches"][0]["letter_logits"] + np.testing.assert_array_equal(a, b) + checks["repeated_question_logits_exact"] = True + full = model.decide( + state=state, + questions={"boolean": questions["boolean"]}, + evidence="none", + execution="full", + diagnostics=True, + ) + c = full["answers"]["boolean"]["branches"][0]["letter_logits"] + checks["cached_full_max_logit_drift"] = float(np.max(np.abs(np.array(a) - c))) + assert (first["answers"]["boolean"]["noul"] >= 0.5) == (full["answers"]["boolean"]["noul"] >= 0.5) + reverse = model.decide( + state=state, + questions={"entity": {**questions["entity"], "candidates": ["Bob", "Alice"]}}, + evidence="none", + ) + assert reverse["answers"]["entity"]["candidates"] == first["answers"]["entity"]["candidates"] + assert list(reverse["answers"]["entity"]["candidates"]) == ["Bob", "Alice"] + checks["candidate_order_and_cache_isolation"] = True + evidence = model.decide(state=state, questions={"boolean": questions["boolean"]}, evidence="removal") + answer = evidence["answers"]["boolean"] + assert answer["evidence"] + for span in answer["evidence"]: + assert document[span["start"] : span["end"]] == span["text"] + assert answer["evidence_detail"]["verification"] == "fresh_source_reencoding" + assert answer["evidence_detail"]["calls"] == 2 + checks["evidence_spans_and_fresh_verification"] = True + exhausted = model.decide( + state=state, questions={"boolean": questions["boolean"]}, evidence="removal", evidence_max_calls=0 + ) + assert exhausted["answers"]["boolean"]["evidence_status"] == "budget_exhausted" + assert exhausted["usage"]["evidence_calls"] == 0 + checks["evidence_budget_enforced"] = True + replay = output.with_suffix(".replay.json") + state.save(replay) + try: + model.decide(state=state, questions={"q": "Fact?"}) + except ValueError: + checks["closed_state_rejected"] = True + else: + raise AssertionError("Closed state accepted") + with model.replay(replay) as restored: + result = model.decide( + state=restored, questions={"boolean": questions["boolean"]}, evidence="none", diagnostics=True + ) + np.testing.assert_array_equal(a, result["answers"]["boolean"]["branches"][0]["letter_logits"]) + checks["public_api_replay_exact"] = True + corrupt = json.loads(replay.read_text()) + corrupt["parts"][0]["text"] += " changed" + bad_path = output.with_suffix(".corrupt-replay.json") + bad_path.write_text(json.dumps(corrupt)) + try: + model.replay(bad_path) + except ValueError: + checks["corrupt_replay_rejected"] = True + else: + raise AssertionError("Corrupt replay accepted") + image_parts = json.loads(Path("evaluations/image-jobs.json").read_text())[0]["parts"] + with model.prefill(image_parts) as images: + result = model.decide(state=images, questions={"q": "Is Alice certified?"}, evidence="support") + assert result["answers"]["q"]["evidence_status"] == "unsupported_page_selector" + checks["missing_page_selector_reported"] = True + with model.prefill({"subject": "Alice", "certified": True}) as structured: + assert structured.prefix_tokens > 0 + checks["structured_document_accepted"] = True + try: + model.engine.admit(40961) + except ValueError: + checks["context_ceiling_enforced"] = True + else: + raise AssertionError("Context limit not enforced") + assert model.engine.context["start"] is None + assert len(model.engine.heads) == 10 + checks["adapter_state_reset_and_ten_heads_loaded"] = True + report = { + "runtime": model.identity, + "checks": checks, + "passed": True, + "scope": "real-weight API behavior; these checks do not establish held-out CUDA parity", + "answers": first["answers"], + "evidence": answer, + } + output.write_text(json.dumps(report, indent=2)) + print(json.dumps({"passed": True, "checks": checks}, indent=2)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--output", required=True) + validate(parser.parse_args().output) diff --git a/mlx/src/solomon_mlx/__init__.py b/mlx/src/solomon_mlx/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c44088bfab06b17808eec1b2e08e6e53cb38dd8b --- /dev/null +++ b/mlx/src/solomon_mlx/__init__.py @@ -0,0 +1,5 @@ +"""Solomon semantic decisions on Apple Silicon.""" + +from .api import DocumentState, Solomon + +__all__ = ["DocumentState", "Solomon"] diff --git a/mlx/src/solomon_mlx/_vendor/__init__.py b/mlx/src/solomon_mlx/_vendor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6cf13cb28dd601ba6a7016c6e04698cc434cfb3d --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. diff --git a/mlx/src/solomon_mlx/_vendor/contract.py b/mlx/src/solomon_mlx/_vendor/contract.py new file mode 100644 index 0000000000000000000000000000000000000000..e6f99dadef7e11c59e0897eb82cbc78ac5da618e --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/contract.py @@ -0,0 +1,111 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +import json +import numpy as np +MAX_CANDIDATES = 64 + +def _text(value, name): + if not isinstance(value, str) or not value.strip(): + raise ValueError(name + ' must be a nonempty string') + return value + +def parse_questions(questions): + """Questions {id: spec} -> ordered list of normalised specs. + + noul: {"type": "noul", "instructions": str} -> task boolean + {"type": "noul", "instructions": "Is {candidate} ...?", "candidates": [...]} -> task entity (one Noul per candidate) + {"type": "noul", "instructions": str, "candidates": [...], "candidate_kind": "label"} -> task multilabel + (candidate_kind defaults to 'entity' when instructions contain {candidate}, else 'label') + choice: {"type": "choice", "instructions": str, "options": [str, ...] | {key: text}, "ordered": bool} + ('criteria' is accepted as a compatibility alias of 'options') + score: {"type": "score", "instructions": str, "levels": [str, ...]} (alias 'criteria'); an ordered choice keyed + "0".."K-1" with a legend and score = sum_i i * p_i. + """ + if not isinstance(questions, dict) or not questions: + raise ValueError('questions must be a nonempty mapping of id -> question') + out = [] + for qid, spec in questions.items(): + if not isinstance(qid, str) or not qid: + raise ValueError('question ids must be nonempty strings') + if isinstance(spec, str): + spec = {'type': 'noul', 'instructions': spec} + if not isinstance(spec, dict): + raise ValueError(f'question {qid}: spec must be an object') + kind = str(spec.get('type', 'noul')).lower() + instructions = _text(spec.get('instructions', spec.get('question')), f'question {qid}: instructions') + if kind == 'noul': + if 'candidates' not in spec: + out.append({'id': qid, 'type': 'noul', 'task': 'boolean', 'request': {'question': instructions}}) + continue + candidates = spec['candidates'] + if not isinstance(candidates, list) or not 1 <= len(candidates) <= MAX_CANDIDATES or len(set(candidates)) != len(candidates) or any((not isinstance(c, str) or not c.strip() for c in candidates)): + raise ValueError(f'question {qid}: candidates must be 1 to {MAX_CANDIDATES} distinct nonempty strings') + ckind = spec.get('candidate_kind', 'entity' if '{candidate}' in instructions else 'label') + if ckind == 'entity': + if instructions.count('{candidate}') != 1 or '{entity}' in instructions: + raise ValueError(f'question {qid}: entity instructions need exactly one {{candidate}} placeholder') + request = {'template': instructions.replace('{candidate}', '{entity}'), 'entities': list(candidates)} + task = 'entity' + elif ckind == 'label': + if '{candidate}' in instructions: + raise ValueError(f'question {qid}: label candidates take no {{candidate}} placeholder') + request = {'question': instructions, 'labels': list(candidates)} + task = 'multilabel' + else: + raise ValueError(f'question {qid}: candidate_kind must be entity or label') + out.append({'id': qid, 'type': 'noul', 'task': task, 'candidates': list(candidates), 'request': request}) + elif kind in ('choice', 'score'): + raw = spec.get('levels', spec.get('options', spec.get('criteria'))) if kind == 'score' else spec.get('options', spec.get('criteria')) + if isinstance(raw, dict): + keys, texts = ([str(k) for k in raw], [raw[k] if isinstance(raw[k], str) and raw[k].strip() else str(k) for k in raw]) + elif isinstance(raw, list): + texts = list(raw) + keys = [str(i) for i in range(len(raw))] if kind == 'score' else list(raw) + else: + raise ValueError(f'question {qid}: options must be a list or a mapping') + if not 2 <= len(texts) <= 8 or any((not isinstance(t, str) or not t.strip() for t in texts)) or len(set(texts)) != len(texts): + raise ValueError(f'question {qid}: provide 2 to 8 distinct nonempty options') + ordered = kind == 'score' or bool(spec.get('ordered', False)) + if not isinstance(spec.get('ordered', False), bool): + raise ValueError(f'question {qid}: ordered must be Boolean') + out.append({'id': qid, 'type': kind, 'task': 'ordered' if ordered else 'single', 'keys': keys, 'texts': texts, 'request': {'question': instructions, 'options': texts}}) + else: + raise ValueError(f'question {qid}: type must be noul, choice or score') + return out + +def _state_parts(state): + if isinstance(state, str): + return [{'text': state}] + if isinstance(state, list): + return state + if isinstance(state, dict): + return [{'text': json.dumps(state, ensure_ascii=False, indent=2, sort_keys=False)}] + raise ValueError('state must be text, an object, or a list of document parts') +def empty(spec): + if spec['type'] == 'noul': + return {'candidates': None} if 'candidates' in spec else {'noul': None} + return {'probabilities': None, 'answer': None} + +def present(spec, dists): + if spec['type'] == 'noul': + if 'candidates' in spec: + return {'candidate_kind': 'entity' if spec['task'] == 'entity' else 'label', 'candidates': {c: float(d[0]) for c, d in zip(spec['candidates'], dists)}, 'candidate_ordering_scores': {c: float(max(d[0], 1 - d[0])) for c, d in zip(spec['candidates'], dists)}} + return {'noul': float(dists[0][0])} + p = dists[0] + keys = spec['keys'] + out = {'probabilities': {k: float(v) for k, v in zip(keys, p)}, 'answer': keys[int(np.argmax(p))]} + out['choice'] = out['answer'] + if spec['type'] == 'score': + out['score'] = float(np.dot(np.arange(len(p)), p)) + out['legend'] = dict(zip(keys, spec['texts'])) + elif spec['task'] == 'ordered': + out['ordered'] = True + return out + +def decision(spec, shown): + if spec['type'] == 'noul': + if 'candidates' in spec: + return {c: p >= 0.5 for c, p in shown['candidates'].items()} + return shown['noul'] >= 0.5 + return shown['answer'] + diff --git a/mlx/src/solomon_mlx/_vendor/evidence.py b/mlx/src/solomon_mlx/_vendor/evidence.py new file mode 100644 index 0000000000000000000000000000000000000000..c06132b648d9c0763e83520af88e2c99f45edf3a --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/evidence.py @@ -0,0 +1,156 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +"""Deterministic source references and explicit evidence interventions. + +Offsets count Python Unicode code points, never bytes. Candidate ranking is lexical +and is labelled as such; only an independent model callback supplies support scores. +Interventions re-encode edited source; an existing KV state cannot prove removal. +""" +import hashlib +import re +from pathlib import Path + + +def digest(text): + return hashlib.sha256(text.encode()).hexdigest() + + +def passages(text, max_chars=1200): + if not isinstance(text,str) or max_chars < 1: + raise ValueError('text and positive passage size required') + result=[] + # Cover every character, including whitespace; splitting does not normalize text. + start=0 + while start < len(text): + limit=min(len(text),start+max_chars) + end=limit + if limit < len(text): + candidates=[m.end() for m in re.finditer(r'\n\s*\n|(?<=[.!?])\s+',text[start:limit])] + if candidates and candidates[-1] >= max_chars//2:end=start+candidates[-1] + result.append({'id':f'text:{start}:{end}','kind':'text','start':start,'end':end, + 'text':text[start:end],'source_sha256':digest(text)}) + start=end + return result + + +def image_pages(paths): + return [{'id':f'page:{i+1}','kind':'image','page':i+1,'path':str(p), + 'source_sha256':hashlib.sha256(Path(p).read_bytes()).hexdigest()} for i,p in enumerate(paths)] + + +def validate_spans(text, spans): + ordered=sorted(spans,key=lambda s:(s['start'],s['end'])) + last=0 + for span in ordered: + start,end=span['start'],span['end'] + if type(start) is not int or type(end) is not int or not 0<=start=minimum_support] + return {'evidence':chosen,'candidates':results,'verification':'model_support_only', + 'faithfulness_established':False,'no_support_found':not chosen} + + +def intervene(text, spans, question, decide): + """decide(document,question) must prefill each supplied document afresh. + + All three actual calls are returned; agreement/disagreement is evidence, not a + guarantee that a passage is the unique cause of an answer. + """ + spans=validate_spans(text,spans) + evidence='\n\n'.join(text[s['start']:s['end']] for s in spans) + pieces=[];start=0 + for span in spans: + pieces.append(text[start:span['start']]);start=span['end'] + pieces.append(text[start:]);removed=''.join(pieces) + full=decide(text,question) + only=decide(evidence,question) + removal=decide(removed,question) + return {'full':full,'evidence_only':only,'evidence_removed':removal, + 'evidence_only_agrees':only==full,'removal_changes_answer':removal!=full, + 'verification':'fresh_source_reencoding','source_sha256':digest(text), + 'evidence_sha256':digest(evidence),'removed_sha256':digest(removed), + 'calls':3,'input_characters':len(text)+len(evidence)+len(removed)} + + +def validate_pages(pages, selected): + """Validate ordered page manifests and selected immutable references.""" + indexed = {} + for expected, page in enumerate(pages, 1): + if page.get('kind') != 'image' or type(page.get('page')) is not int or page['page'] != expected: + raise ValueError('image manifest must use consecutive source page IDs') + if page.get('id') != f'page:{expected}': + raise ValueError('image page ID does not match source position') + actual = hashlib.sha256(Path(page['path']).read_bytes()).hexdigest() + if page.get('source_sha256') != actual: + raise ValueError('image no longer matches source') + indexed[expected] = page + ids = [] + for reference in selected: + number = reference.get('page') + if type(number) is not int or number not in indexed or number in ids: + raise ValueError('invalid or duplicate evidence page') + source = indexed[number] + if any(reference.get(key) != source[key] for key in ('id', 'kind', 'path', 'source_sha256')): + raise ValueError('evidence page no longer matches source manifest') + ids.append(number) + return [indexed[number] for number in sorted(ids)] + + +def intervene_pages(pages, selected, question, decide, text=''): + """Re-encode full, evidence-only and page-removed multimodal documents. + + decide(text, image_paths, question) must create a fresh state each time and + accept an empty image list. Evidence-only has no accompanying source text; + removal retains all source text and unselected pages. This isolates page + evidence and makes text-only sufficiency a visible competing explanation. + Original page IDs are recorded because subset images are renumbered on input. + """ + if not isinstance(text, str): + raise ValueError('source text must be a string') + selected = validate_pages(pages, selected) + selected_ids = {p['page'] for p in selected} + removed = [p for p in pages if p['page'] not in selected_ids] + calls = [(text, pages), ('', selected), (text, removed)] + answers = [] + for source_text, source_pages in calls: + # Recheck all originals before each call: never silently mix revisions. + validate_pages(pages, selected) + answers.append(decide(source_text, [p['path'] for p in source_pages], question)) + validate_pages(pages, selected) + full, only, removal = answers + return {'full': full, 'evidence_only': only, 'evidence_removed': removal, + 'evidence_only_agrees': only == full, 'removal_changes_answer': removal != full, + 'verification': 'fresh_source_reencoding', 'faithfulness_established': False, + 'source_text_sha256': digest(text), + 'source_pages': [{k: p[k] for k in ('id', 'page', 'source_sha256')} for p in pages], + 'evidence_page_ids': [p['page'] for p in selected], + 'removed_input_page_ids': [p['page'] for p in removed], + 'calls': 3, 'input_images': sum(len(p) for _, p in calls), + 'input_image_bytes': sum(Path(p['path']).stat().st_size for _, ps in calls for p in ps), + 'input_characters': 2 * len(text)} diff --git a/mlx/src/solomon_mlx/_vendor/evidence_v3.py b/mlx/src/solomon_mlx/_vendor/evidence_v3.py new file mode 100644 index 0000000000000000000000000000000000000000..70580faee131fe6efd4157314a0203ba48b2fb3a --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/evidence_v3.py @@ -0,0 +1,425 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +"""Evidence packages (v3): selected spans plus source-derived governing context, per question unit. + +A package is a genuine subset of one source document: every range is an exact, validated slice +of the source (Python code-point offsets), rendered in source order with whitespace-only +separators. Nothing is paraphrased, summarised or generated. Context is attached by +question-independent document structure plus the unit's own selected spans and question text; +gold/authoring data is never an input. + +Roles + evidence spans chosen by the selector (or, in oracle diagnostics, gold anchors). Span + precision/recall gates are computed on these only, exactly as before. + context ranges attached automatically, each with one or more reasons: + header document title block (identity of the source, author/compiler, date) + interpretation generic reading rules (scope6.retrieval.governing_context, unchanged regex) + locator container of an included span: message header line (date, author -> + recipient), minute number + heading, schedule/section heading, entry number + correction a withdrawal/rescission/deletion elsewhere that refers to the container of an + included statement and names the same subject and topic (or the exact entry) + withdrawn the statement(s) that an included withdrawal refers to, so the chain is + readable (the withdrawn text is marked by the withdrawal, not by us) + rule a general rule / band scale whose operative topic matches the unit question + (attached only when the unit has evidence) + exception the exception clause governing an included rule, or the rule an included + exception limits + definition the sentence defining a capitalised class term used by an included rule, + when it names the unit's subject + condition a statement about the unit's subject that bears on an included rule's + conditions or exception (>=2 shared content words beyond the question topic, + or one document-rare shared word) + convention the document's own reading conventions (sentences of the paragraphs that hold + generic reading rules) that the package needs: silence always; conflict and vocabulary when + there is evidence; removal when a withdrawal/deletion chain is present; + condition when a rule is present; exception when an exception is present + +Size is measured as covered source characters / source characters and reported per package. A +package above the registered cap (default 0.30) sheds context in TRIM_ORDER (never evidence, rule, +exception, condition, correction or withdrawn context) and records what was trimmed. +""" +import re +from .evidence import digest, validate_spans +from .retrieval import candidates, governing_context + +SCHEMA = 'scope6-evidence-package-v3.1' +SIZE_CAP = .30 +# Context dropped first when a package exceeds the size cap (evidence is never dropped). +TRIM_ORDER = ('interpretation', 'convention/silence', 'convention/vocabulary', 'convention/conflict', 'header', 'definition', + 'convention/condition', 'convention/exception', 'convention/removal', 'locator') +MONTHS = 'January|February|March|April|May|June|July|August|September|October|November|December' +_WORD = re.compile(r'\w+') +_CAP = re.compile(r"\b[A-Z][\w&'-]*") +_WITHDRAWAL = re.compile(r"withdr[ae]w|withdrawn|take back|disregard|rescind|retract|should not be relied|" + r"substituted|\bis deleted|\bdelete[sd]?\b|struck out|replaced by|no further effect", re.I) +_REF_DATE = re.compile(r'(?:message|wrote|written|letter|note)\D{0,14}?(\d{1,2} (?:' + MONTHS + r'))') +_REF_MINUTE = re.compile(r'\bminute (\d+)\b', re.I) +_REF_ENTRY = re.compile(r'\b[Ee]ntry (\d+) of [Ss]chedule (\d+)') +_MSG_HEAD = re.compile(r'Message \d+\. (\d{1,2} (?:' + MONTHS + r'))\.') +_MINUTE_HEAD = re.compile(r'(\d+)\.\s+[^.\n]{1,80}\.') +_SCHEDULE_HEAD = re.compile(r'SCHEDULE (\d+)\b') +_ENTRY_PREFIX = re.compile(r'[A-Z]?\d+(?:\.\d+)*\.?\s*') +_PERMISSION = re.compile(r'\bmay\b|permitted|authorised|entitled|leave to|allowed|cleared|confers?', re.I) +_RULE = re.compile(r"(?i:\b(?:any|each|every)\s+[a-z]+(?:\s+[a-z]+)?\s+(?:who|that)\b|\ban?\s+[a-z]+\s+who\b|general rule|standing rule|" + r"office rule|rule governing|\bif that\b|where one and the same|\b[a-z]+s who\b|\bwhere (?:an?|any|one)\b)|\b[A-Z][a-z]+ [A-Z][a-z]+s? (?:that|who)\b") +_SCALE = re.compile(r'\bbands?\b|\bscale\b|from lowest to highest|order of the levels|worked out from|fixed by that number|\bfewer than \d+', re.I) +_EXCEPTION = re.compile(r'does not apply|confers nothing on|is outside (?:clause|the rule)|not engaged|does not reach|switched off|' + r'disapplied|subject to (?:one|the) exception|nothing in the rule|save that|except (?:where|that|for)\b', re.I) +CONVENTIONS = { + 'removal': re.compile(r"withdr[ae]w|rescind|delet|substitut|struck out|take back|displace|disregard|\bspent\b|express(?:ly)? (?:withdrawal|rescission)", re.I), + 'condition': re.compile(r"\bconditions?\b|\blimbs?\b|requirements?\b|only part|part of the way|whole of it|each of its|all of its", re.I), + 'exception': re.compile(r"\bexcept", re.I), + 'conflict': re.compile(r"both (?:stand|remain|hold|are in force|left standing)|opposite (?:ways|things)|inconsistent|contradict|" + r"point opposite|not say which|not chosen between|this office does not say", re.I), + 'vocabulary': re.compile(r"interchangeabl|one and the same (?:permission|refusal)|are one (?:grant|refusal)|mean the same|same thing|words to like effect", re.I), + 'silence': re.compile(r"silen|absence of|unminuted|nothing has been decided|has not been said|undecided|left open", re.I)} +_DEFINES = re.compile(r'\bare\b|\bmeans\b|\bidentified\b|\binclude', re.I) +_STOP = {'this', 'that', 'these', 'those', 'with', 'under', 'which', 'what', 'does', 'file', 'correspondence', 'record', 'records', + 'recorded', 'minutes', 'agreement', 'bundle', 'messages', 'message', 'stand', 'stands', 'taking', 'reading', 'whole', + 'strength', 'position', 'open', 'given', 'have', 'been', 'applies', 'apply', 'entitled', 'allowed', 'liberty', 'free', + 'from', 'there', 'their', 'they', 'decisions', 'here', 'schedules', 'schedule', 'papers', 'office', 'shown', 'show', + 'shows', 'case', 'matters', 'terms', 'place', 'placed', 'about', 'question', 'whether', 'should', 'relied', 'either', + 'wrote', 'withdraw', 'withdrawn', 'passage', 'treat', 'nothing', 'follows', 'please', 'disregard', 'concerns', 'deals', + 'resolved', 'rescinded', 'much', 'decision', 'minute', 'further', 'effect', 'permission', 'permitted', 'refused', + 'barred', 'prohibited', 'authorised', 'cleared', 'leave', 'allows', 'allow', 'bars', 'refuses', 'grant', 'refusal', + 'entry', 'deleted', 'substituted', 'following', 'replaced', 'struck', 'committee', 'secretary', 'chair', 'principal'} +ROLES = ('evidence', 'context') +# Capitalised words that are never subject names (sentence openers, document furniture). +_NOT_NAMES = {'the', 'this', 'that', 'these', 'those', 'it', 'its', 'i', 'we', 'our', 'my', 'on', 'for', 'so', 'as', 'at', 'in', 'of', + 'please', 'treat', 'where', 'when', 'what', 'which', 'who', 'whether', 'any', 'each', 'every', 'no', 'nothing', 'there', + 'message', 'messages', 'entry', 'schedule', 'clause', 'minute', 'minutes', 'agreement', 'principal', 'resolved', 'note', + 'committee', 'secretary', 'chair', 'treasurer', 'register', 'records', 'record', 'permission', 'an', 'a', 'if', 'all', + 'both', 'neither', 'either', 'one', 'two', 'words', 'dates', 'decisions', 'rules', 'only', 'once', 'part', 'to', 'by', + 'read', 'do', 'what', 'should', 'with', 'from', 'under', 'after', 'before', 'reading', 'taking', 'is', 'has', 'have'} + + +def _words(s): + return set(_WORD.findall(s.casefold())) + + +def _topic(s, names=()): + low = {w for n in names for w in _words(n)} + return {w for w in _words(s) if len(w) >= 4 and w not in _STOP and w not in low and not w.isdigit() + and not re.fullmatch(MONTHS.casefold(), w)} + + +class Structure: + """Question-independent source structure: sentences, paragraphs, lines and containers.""" + + def __init__(self, text): + self.text = text + self.sentences = candidates(text) + self.paragraphs = [] + start = 0 + for m in list(re.finditer(r'\n\s*\n', text)) + [None]: + end = m.start() if m else len(text) + if text[start:end].strip(): + self.paragraphs.append((start, end)) + start = m.end() if m else len(text) + self.lines = [(m.start(), m.end()) for m in re.finditer(r'[^\n]+', text)] + df = {} + for s in self.sentences: + for w in _words(s['text']): + df[w] = df.get(w, 0) + 1 + self.df = df + self.interpretation = [(s['start'], s['end']) for s in governing_context(text, self.sentences)] + # containers: message date -> paragraph; minute number -> paragraph; (schedule, entry) -> line + self.messages, self.minutes, self.entries = {}, {}, {} + for a, b in self.paragraphs: + first = text[a:b].split('\n', 1)[0] + m = _MSG_HEAD.match(first) + if m: + self.messages.setdefault(m.group(1), (a, b)) + m = re.match(r'(\d+)\. ', text[a:b]) + if m and '\n' not in text[a:b].strip(): + self.minutes.setdefault(m.group(1), (a, b)) + m = _SCHEDULE_HEAD.match(first) + if m: + for la, lb in self.lines: + if a <= la and lb <= b: + e = re.match(r'(\d+)\. ', text[la:lb]) + if e: + self.entries[(m.group(1), e.group(1))] = (la, lb) + self.rules = [s for s in self.sentences if (_RULE.search(s['text']) and _PERMISSION.search(s['text'])) or _SCALE.search(s['text'])] + self.exceptions = [s for s in self.sentences if _EXCEPTION.search(s['text'])] + self.withdrawals = [s for s in self.sentences if _WITHDRAWAL.search(s['text'])] + # Reading conventions: sentences of the interpretation paragraphs (those holding a generic reading rule). + blocks = {self.paragraph_of(a) for a, _ in self.interpretation} - {None} + general = [s for s in self.sentences if self.paragraph_of(s['start']) in blocks and s not in self.rules] + self.conventions = {k: [(s['start'], s['end']) for s in general if rx.search(s['text'])] for k, rx in CONVENTIONS.items()} + + def governed_rule(self, exception): + """The rule an exception limits: nearest preceding rule in the same paragraph (or the same sentence).""" + para = self.paragraph_of(exception['start']) + prior = [r for r in self.rules if r['start'] <= exception['start'] and para and para[0] <= r['start'] < para[1] + and not _SCALE.search(r['text'])] + return prior[-1] if prior else None + + def names(self, s, limit=16): + """Capitalised tokens that are rare in this source (subject names); months/number words excluded.""" + out = set() + for tok in _CAP.findall(s): + tok = re.sub(r"'s$", '', tok) + w = tok.casefold() + if re.fullmatch(MONTHS, tok) or len(w) < 3 or w in _NOT_NAMES or (tok.isupper() and len(tok) > 1): + continue + if self.df.get(w, 0) <= limit: + out.add(tok) + return out + + def paragraph_of(self, pos): + for a, b in self.paragraphs: + if a <= pos < b: + return a, b + return None + + def line_of(self, pos): + for a, b in self.lines: + if a <= pos < b: + return a, b + return None + + def container(self, span): + """(kind, key) of the message/minute/entry holding a span, else None.""" + for key, (a, b) in self.entries.items(): + if a <= span['start'] < b: + return ('entry', key) + para = self.paragraph_of(span['start']) + if para is None: + return None + for key, rng in self.messages.items(): + if rng == para: + return ('message', key) + for key, rng in self.minutes.items(): + if rng == para: + return ('minute', key) + return None + + def references(self, sentence): + """Containers a withdrawal-type sentence refers to.""" + out = [] + for m in _REF_ENTRY.finditer(sentence): + out.append(('entry', (m.group(2), m.group(1)))) + for m in _REF_DATE.finditer(sentence): + out.append(('message', m.group(1))) + for m in _REF_MINUTE.finditer(sentence): + out.append(('minute', m.group(1))) + return out + + def container_range(self, ref): + kind, key = ref + return {'entry': self.entries, 'message': self.messages, 'minute': self.minutes}[kind].get(key) + + def locators(self, span): + """Header ranges that place a span in its container (never the span's own text).""" + text, out = self.text, [] + para = self.paragraph_of(span['start']) + if para is None: + return out + a, b = para + nl = text.find('\n', a, b) + if nl >= 0 and span['start'] > nl: + out.append((a, nl)) # first line of a multi-line block: message/section/schedule heading + else: + m = _MINUTE_HEAD.match(text, a) + if m and m.end() <= span['start']: + out.append((a, m.end())) # numbered minute and its heading + line = self.line_of(span['start']) + if line and line[0] < span['start']: + prefix = text[line[0]:span['start']] + if len(prefix) <= 16 and _ENTRY_PREFIX.fullmatch(prefix): + out.append((line[0], span['start'])) # entry / clause number + return out + + def sentence_ranges_in(self, rng): + a, b = rng + return [s for s in self.sentences if a <= s['start'] < b] + + +def _strip(text, a, b): + while a < b and text[a].isspace(): + a += 1 + while b > a and text[b - 1].isspace(): + b -= 1 + return a, b + + +def _merge(text, items): + """items: (start, end, role, reason) -> merged validated ranges; evidence role wins on overlap.""" + cleaned = [] + for a, b, role, reason in items: + a, b = _strip(text, a, b) + if a < b: + cleaned.append((a, b, role, reason)) + cleaned.sort() + merged = [] + for a, b, role, reason in cleaned: + if merged and a <= merged[-1]['end']: + m = merged[-1] + m['end'] = max(m['end'], b) + m['roles'].add(role) + m['reasons'].add(reason) + else: + merged.append({'start': a, 'end': b, 'roles': {role}, 'reasons': {reason}}) + h = digest(text) + out = [{'id': f"text:{m['start']}:{m['end']}", 'kind': 'text', 'start': m['start'], 'end': m['end'], + 'text': text[m['start']:m['end']], 'source_sha256': h, + 'role': 'evidence' if 'evidence' in m['roles'] else 'context', 'reasons': sorted(m['reasons'])} for m in merged] + validate_spans(text, out) + return out + + +def render(text, spans): + """Source-order rendering; separators are whitespace only and mirror the source layout.""" + parts, last = [], None + for s in sorted(spans, key=lambda s: s['start']): + if last is not None: + gap = text[last:s['start']] + parts.append('\n\n' if '\n\n' in gap or re.search(r'\n\s*\n', gap) else ('\n' if '\n' in gap else ' ')) + parts.append(text[s['start']:s['end']]) + last = s['end'] + return ''.join(parts) + + +def build(text, question, evidence, *, subject=None, structure=None, options=None, cap=SIZE_CAP): + """One unit's evidence package. + + evidence: selected (or gold, for oracle diagnostics) source spans for this unit. + subject: optional explicit subject string (entity name); otherwise rare capitalised question tokens. + """ + opts = {'header': True, 'interpretation': True, 'locator': True, 'correction': True, 'withdrawn': True, + 'rule': True, 'exception': True, 'definition': True, 'condition': True, 'convention': True, **(options or {})} + st = structure or Structure(text) + evidence = validate_spans(text, [{k: s[k] for k in ('start', 'end')} for s in evidence]) if evidence else [] + items = [(s['start'], s['end'], 'evidence', 'selected') for s in evidence] + subj = st.names(subject if subject else question, limit=10 ** 9 if subject else 16) + if subject: + subj |= {subject} + qtopic = _topic(question, subj) + rare = st.names(question) + + def mentions(t): + return (subject in t) if subject else bool(st.names(t) & rare) + if opts['header'] and st.paragraphs: + a, b = st.paragraphs[0] + items.append((a, min(b, a + 400), 'context', 'header')) + if opts['interpretation']: + items += [(a, b, 'context', 'interpretation') for a, b in st.interpretation] + if evidence: + included = [dict(s) for s in evidence] + if opts['rule']: + for r in st.rules: + if len(_topic(r['text'], subj) & qtopic) >= (3 if 'Attribute:' in question else 2): + items.append((r['start'], r['end'], 'context', 'rule')) + included.append(r) + # Iterate twice: attached statements can themselves need locators/corrections. + for _ in range(3): + current = [{'start': a, 'end': b} for a, b, role, reason in items + if role == 'evidence' or reason in ('rule', 'exception', 'withdrawn', 'correction', 'definition', 'condition')] + spans = [s for s in st.sentences if any(max(s['start'], c['start']) < min(s['end'], c['end']) for c in current)] + for s in spans: + s_text = s['text'] + if opts['exception'] and s in st.rules: + for e in st.exceptions: + g = st.governed_rule(e) + if g is not None and g['start'] == s['start']: + items.append((e['start'], e['end'], 'context', 'exception')) + if opts['exception'] and s in st.exceptions: + g = st.governed_rule(s) + if g is not None: + items.append((g['start'], g['end'], 'context', 'exception')) + if opts['definition'] and s in st.rules: + for term in set(re.findall(r'(?<=[a-z,;] )([A-Z][a-z]+ [A-Z][a-z]+?)s?\b', s_text)): + for d in st.sentences: + if term in d['text'] and _DEFINES.search(d['text']) and d['start'] < s['start'] and mentions(d['text']): + items.append((d['start'], d['end'], 'context', 'definition')) + if opts['condition'] and s in st.rules: + # Facts about this unit's subject that bear on the rule's conditions or its exception. + governing = [s] + [e for e in st.exceptions if (st.governed_rule(e) or {}).get('start') == s['start']] + words = set().union(*(_topic(g['text'], subj) for g in governing)) - qtopic + for t in st.sentences: + shared = _topic(t['text'], subj) & words + if t['start'] != s['start'] and mentions(t['text']) and (len(shared) >= 2 or any(st.df.get(w, 0) <= 3 for w in shared)): + items.append((t['start'], t['end'], 'context', 'condition')) + if opts['locator']: + items += [(a, b, 'context', 'locator') for a, b in st.locators(s)] + is_withdrawal = bool(_WITHDRAWAL.search(s_text)) + if opts['withdrawn'] and is_withdrawal: + wnames = st.names(s_text) + for ref in st.references(s_text): + rng = st.container_range(ref) + if rng is None: + continue + if ref[0] == 'entry': + items.append((rng[0], rng[1], 'context', 'withdrawn')) + continue + for t in st.sentence_ranges_in(rng): + if t['start'] == s['start']: + continue + if st.names(t['text']) & wnames and _topic(t['text'], wnames) & _topic(s_text, wnames): + items.append((t['start'], t['end'], 'context', 'withdrawn')) + if opts['correction'] and not is_withdrawal: + where = st.container(s) + if where is None: + continue + snames = st.names(s_text) + for w in st.withdrawals: + if w['start'] == s['start'] or where not in st.references(w['text']): + continue + if where[0] == 'entry' or (st.names(w['text']) & snames and _topic(w['text'], snames) & _topic(s_text, snames)): + items.append((w['start'], w['end'], 'context', 'correction')) + if opts['convention']: + reasons = {r for _, _, _, r in items} + ev_text = ' '.join(text[a:b] for a, b, role, _ in items if role == 'evidence') + wanted = {'silence'} + if evidence: + wanted |= {'conflict', 'vocabulary'} + if reasons & {'correction', 'withdrawn'} or _WITHDRAWAL.search(ev_text): + wanted.add('removal') + if 'rule' in reasons or any(r['start'] < b and a < r['end'] for r in st.rules if not _SCALE.search(r['text']) + for a, b, role, _ in items if role == 'evidence'): + wanted.add('condition') + if 'exception' in reasons: + wanted.add('exception') + for kind in sorted(wanted): + items += [(a, b, 'context', 'convention/'+kind) for a, b in st.conventions[kind]] + spans = _merge(text, items);trimmed = [] + def size(sp):return sum(x['end'] - x['start'] for x in sp) / len(text) if text else 0. + for reason in (TRIM_ORDER if cap is not None else ()): + if size(spans) <= cap: + break + keep = [it for it in items if it[3] != reason] + if len(keep) != len(items): + items = keep;trimmed.append(reason);spans = _merge(text, items) + rendered = render(text, spans) + covered = sum(s['end'] - s['start'] for s in spans) + return {'schema': SCHEMA, 'question': question, 'source_sha256': digest(text), 'text': rendered, 'spans': spans, + 'size_cap': cap, 'within_size_cap': cap is None or covered <= cap * len(text), 'trimmed_for_cap': trimmed, + 'evidence_ranges': [{'start': s['start'], 'end': s['end']} for s in evidence], + 'no_support_found': not evidence, 'source_characters': len(text), 'covered_characters': covered, + 'rendered_characters': len(rendered), 'source_fraction': covered / len(text) if text else 0., + 'context_reasons': sorted({r for s in spans for r in s['reasons'] if r != 'selected'}), + 'faithfulness_established': False} + + +def union(text, packages): + """One document-level package: union of unit packages (used for whole-question rendering).""" + items = [(s['start'], s['end'], s['role'], r) for p in packages for s in p['spans'] for r in s['reasons']] + if not items: + return {'schema': SCHEMA, 'text': '', 'spans': [], 'source_fraction': 0., 'covered_characters': 0, + 'source_characters': len(text), 'rendered_characters': 0, 'source_sha256': digest(text)} + spans = _merge(text, items) + rendered = render(text, spans) + covered = sum(s['end'] - s['start'] for s in spans) + return {'schema': SCHEMA, 'text': rendered, 'spans': spans, 'source_fraction': covered / len(text), + 'covered_characters': covered, 'source_characters': len(text), 'rendered_characters': len(rendered), + 'source_sha256': digest(text)} + + +def with_pages(package, text, page_map): + """Attach renderer page IDs to every package span (scope6.sources.evidence_pages).""" + from scope6.sources import evidence_pages + out = dict(package) + out['spans'] = [{**s, 'pages': evidence_pages(text, [s], page_map)} for s in package['spans']] + out['pages'] = sorted({p for s in out['spans'] for p in s['pages']}) + return out diff --git a/mlx/src/solomon_mlx/_vendor/prompts.py b/mlx/src/solomon_mlx/_vendor/prompts.py new file mode 100644 index 0000000000000000000000000000000000000000..50aff41f8b25250e9a3e30516f31882c5f86976f --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/prompts.py @@ -0,0 +1,43 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +SYSTEM = 'You answer questions about the supplied document. Use only the document. Task instructions follow the document; follow them exactly.' + +BOOLEAN_TASK = 'Task: classify the evidence for the question using only the document and its explicit rules. A = Yes only. B = No only. C = neither Yes nor No is established. D = both Yes and No are established. A missing fact is not a negative fact. Evidence about another person or subject does not contradict the queried one. Apply explicit time and replacement rules before deciding. Respond with exactly one letter: A, B, C, or D. Do not explain.' + +CHOICE_TASK = 'Task: choose the single option that the document best supports. Respond with exactly one letter. Do not explain.' + +def boolean_block(question): + return BOOLEAN_TASK + '\nQuestion: ' + question + '\nAnswer (one letter):' +LETTERS = 'ABCDEFGHIJ' + +PAGE = '<|vision_start|><|image_pad|><|vision_end|>' + +RESERVED = ['The document does not state this', 'The document gives conflicting answers'] + +SINGLE_R = 'Task: choose the single option that the document establishes as the answer. If the document does not establish any of the other listed answers, choose the option that says it does not state this. If the document establishes two different listed answers and does not say which prevails, choose the option that says it gives conflicting answers. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter. Do not explain.' + +ORDERED_R = 'Task: the options form an ordered scale, lowest first, followed by two special options. Choose the single level that the document establishes. If the document does not establish any level, choose the option that says it does not state this. If it establishes two different levels and does not say which prevails, choose the option that says it gives conflicting answers. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter. Do not explain.' + +SINGLE_S = CHOICE_TASK + +ORDERED_S = 'Task: the options form an ordered scale, lowest first. Choose the single level that the document best supports. Respond with exactly one letter. Do not explain.' + +SUFFICIENCY = 'Task: classify what the document establishes about the answer to the question, using only the document. A = it establishes exactly one of the listed answers. B = it does not establish any of the listed answers. C = it establishes two or more different listed answers and does not say which prevails. A missing fact is not a negative fact. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, or C. Do not explain.' + +LABEL = 'Task: decide whether the label applies, using only the document. A = the document establishes that it applies, and nothing in it establishes that it does not. B = the document establishes that it does not apply, and nothing in it establishes that it does. C = the document establishes neither. D = the document establishes both. A missing fact is not a negative fact. Evidence about another person or subject does not count. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, C, or D. Do not explain.' + +OPTION = 'Task: decide whether the proposed answer is correct, using only the document. A = the document establishes this answer, and nothing in it establishes a different one. B = the document establishes a different answer, or establishes that this one is wrong, and nothing in it establishes this one. C = the document establishes neither. D = the document establishes both this answer and a different one. A missing fact is not a negative fact. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, C, or D. Do not explain.' + +def _lettered(options): + return '\n'.join((f'{LETTERS[i]}. {text}' for i, text in enumerate(options))) + +def listwise_block(question, options, ordered=False, reserved=True): + """Caller options in caller order; with `reserved` the service appends the two reserved outcomes.""" + if not 2 <= len(options) <= 8 or len(set(options)) != len(options): + raise ValueError('needs 2 to 8 distinct options') + shown = list(options) + (RESERVED if reserved else []) + task = (ORDERED_R if ordered else SINGLE_R) if reserved else ORDERED_S if ordered else SINGLE_S + return (task + '\nQuestion: ' + question + '\nOptions:\n' + _lettered(shown) + '\nAnswer (one letter):', len(shown)) + +def label_block(question, label): + return (LABEL + '\nQuestion: ' + question + '\nLabel: ' + label + '\nAnswer (one letter):', 4) diff --git a/mlx/src/solomon_mlx/_vendor/retrieval.py b/mlx/src/solomon_mlx/_vendor/retrieval.py new file mode 100644 index 0000000000000000000000000000000000000000..0d4660f56c94d836d73c0fb1c329fc18de1d4e80 --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/retrieval.py @@ -0,0 +1,152 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +"""Inference-only source candidates and explicitly labelled retrieval baselines.""" +import re +from .evidence import digest,rank_candidates,validate_spans + +def candidates(text): + """Source sentences retaining exact character ranges; no authoring labels.""" + ends=[m.end() for m in re.finditer(r'(?<=[.!?])(?:[ \t]+|\n+)|\n\s*\n',text)]+[len(text)] + result=[];start=0 + for end in ends: + if end>start and text[start:end].strip(): + result.append({'id':f'text:{start}:{end}','kind':'text','start':start,'end':end, + 'text':text[start:end],'source_sha256':digest(text)}) + start=end + return result + +def governing_context(text,spans=None): + """Generic rule/retraction interpretation context, independent of question gold.""" + spans=candidates(text) if spans is None else spans + pattern=re.compile(r'general rule|both limbs|same (?:person|courier|member|officer)|neither limb|missing requirement|silence|withdrawn passage|dates and seniority|a withdrawn|outside these|bands are|order of the levels|no.*precedence|each passage|a grant in force|an exception to a rule',re.I) + return [s for s in spans if pattern.search(s['text'])] + +def merge(text,spans): + intervals=[] + for span in sorted(spans,key=lambda s:(s['start'],s['end'])): + a,b=span['start'],span['end'] + if intervals and a<=intervals[-1][1]:intervals[-1]=(intervals[-1][0],max(b,intervals[-1][1])) + else:intervals.append((a,b)) + result=[{'id':f'text:{a}:{b}','kind':'text','start':a,'end':b,'text':text[a:b],'source_sha256':digest(text)} for a,b in intervals] + return validate_spans(text,result) + +def render_subset(text,spans,*,include_context=True): + selected=merge(text,list(spans)+(governing_context(text) if include_context else [])) + return '\n\n'.join(s['text'] for s in selected),selected + +_WORD=re.compile(r'\w+') +_SCALE=re.compile(r'\bbands?\b|\bscale\b|from lowest to highest|order of the levels',re.I) +POOL_V2={'name':'idf-rare-rule-v2','idf_limit':24,'rare_df':16,'rules':True} + +def _words(s):return set(_WORD.findall(s.casefold())) + +def rule_candidates(text,spans=None): + """Question-independent rule, exception, withdrawal-convention and scale passages.""" + spans=candidates(text) if spans is None else spans + rules={s['id'] for s in governing_context(text,spans)} + return [s for s in spans if s['id'] in rules or _SCALE.search(s['text'])] + +def candidate_pool(question,spans,text=None,*,idf_limit=24,rare_df=16,rules=True,**_): + """Bounded source-only pool: document-IDF top-k, every passage sharing a rare + question term (typically the subject's name), plus question-independent rules. + + Candidate order is source order. candidate_score stays the plain lexical-overlap + fraction used by the frozen relevance-head feature, so heads remain comparable. + Gold is never an input. + """ + import math + if idf_limit<1 or rare_df<0:raise ValueError('invalid candidate pool policy') + ranked=rank_candidates(question,spans,len(spans)) if spans else [] + words=[_words(c['text']) for c in ranked];q=_words(question);n=len(ranked) + df={} + for ws in words: + for w in ws:df[w]=df.get(w,0)+1 + idf=[sum(math.log((n+1)/(df[w]+.5)) for w in ws&q) for ws in words] + order=sorted(range(n),key=lambda i:(-idf[i],ranked[i]['start'])) + reason={} + for i in order[:idf_limit]:reason.setdefault(ranked[i]['id'],'idf') + rare={w for w in q if df.get(w,0)<=rare_df} + for c,ws in zip(ranked,words): + if ws&rare:reason.setdefault(c['id'],'rare_term') + if rules: + for c in rule_candidates(text or '',ranked):reason.setdefault(c['id'],'rule') + return sorted(({**c,'pool_reason':reason[c['id']]} for c in ranked if c['id'] in reason),key=lambda c:c['start']) + +_WITHDRAW=re.compile(r'withdr[ae]w|withdrawn|take back|disregard|rescind|retract|should not be relied|there is substituted|is deleted|expressly delete',re.I) +_REFERENCE=re.compile(r'(?:message of|wrote on|message dated) (\d{1,2} [A-Z][a-z]+)|minute (\d+)|[Ee]ntry (\d+) of [Ss]chedule (\d+)|clause ([\d.]+)|passage in my message') +_EXCEPTION=re.compile(r'does not apply to|subject to the exception|is an exception|except (?:where|that|for)\b',re.I) +_STOP={'this','that','these','those','with','under','which','what','does','file','correspondence','record','records','recorded', + 'minutes','agreement','bundle','messages','message','stand','stands','taking','reading','whole','strength','position', + 'open','given','have','been','applies','apply','entitled','allowed','liberty','free','from','there','their','they', + 'decisions','here','schedules','schedule','papers','office','shown','show','shows','case','matters','terms','place','placed'} +DEPENDENCIES_V1={'name':'withdrawal-exception-v1','withdrawals':True,'exceptions':True,'prune_withdrawn':False,'rare_df':16} + +def _topic(words,exclude): + return {w for w in words if len(w)>=4 and w not in _STOP and w not in exclude and not w.isdigit()} + +def _withdrawn_block(text,passage): + """Source range that a withdrawal refers to (dated message or numbered minute); None if not resolvable.""" + m=_REFERENCE.search(passage) + if not m:return None + if m.group(1):head=re.search(r'Message \d+\. '+re.escape(m.group(1))+r'\.',text) + elif m.group(2):head=re.search(r'(?:^|\n)'+m.group(2)+r'\. ',text) + else:return None + if not head:return None + end=text.find('\n\n',head.end());return head.start(),(len(text) if end<0 else end) + +def dependency_expand(question,spans,selected_ids,text,*,withdrawals=True,exceptions=True,prune_withdrawn=False,rare_df=16,**_): + """Complete a unit's selected evidence with its withdrawal/exception dependencies. + + Source text and the already-selected passages only; never gold. Units with no + selection are unchanged, so no-positive-support decisions are preserved. + - withdrawal: a passage with a withdrawal verb AND an explicit reference (message/minute/ + entry/clause) that names the question's subject (a rare capitalised name, also present in a + selected passage) and its most specific in-source topic word; + - exception: an exception clause immediately following a selected passage; + - prune_withdrawn (optional): drop selected passages inside the dated message / numbered + minute a kept withdrawal refers to, when they share its subject and a topic word. + Returns {'keep','added','pruned'} as candidate id lists in source order. + """ + selected=[s for s in spans if s['id'] in set(selected_ids)] + if not selected:return {'keep':[],'added':[],'pruned':[]} + words=[_words(s['text']) for s in spans];df={} + for ws in words: + for w in ws:df[w]=df.get(w,0)+1 + # Subject: capitalised non-initial question tokens (names) that are rare in the source. + names=re.findall(r'(?=3 and df.get(w,0)<=rare_df} + q=_words(question);present=[w for w in _topic(q,subject) if df.get(w,0)] + # The withdrawal must name the question's most specific in-source topic word (e.g. the attribute). + topic={min(present,key=lambda w:(df[w],w))} if present else set() + chosen={s['id'] for s in selected};sel_words=set().union(*(_words(s['text']) for s in selected));added=[] + for i,(s,ws) in enumerate(zip(spans,words)): + if s['id'] in chosen:continue + if (withdrawals and _WITHDRAW.search(s['text']) and _REFERENCE.search(s['text']) + and ws&subject&sel_words and ws&topic):added.append(s['id']);continue + if exceptions and i>0 and spans[i-1]['id'] in chosen and _EXCEPTION.search(s['text']):added.append(s['id']) + keep=chosen|set(added);pruned=[] + if prune_withdrawn: + for s,ws in zip(spans,words): + if s['id'] not in keep or not _WITHDRAW.search(s['text']):continue + block=_withdrawn_block(text,s['text']) + if block is None:continue + key=ws&subject;about=_topic(ws,subject) + for t,tw in zip(spans,words): + if (t['id'] in keep and t['id']!=s['id'] and block[0]<=t['start'] and t['end']<=block[1]+2 + and tw&key and tw&about and not _WITHDRAW.search(t['text'])):pruned.append(t['id']) + order=[s['id'] for s in spans];pruned=set(pruned) + return {'keep':[i for i in order if i in keep and i not in pruned],'added':[i for i in order if i in set(added)], + 'pruned':[i for i in order if i in pruned]} + +def lexical_select(text,questions,limit=4): + source=candidates(text);chosen={} + for q in questions: + for candidate in rank_candidates(q,source,limit): + if candidate['candidate_score']>0:chosen[candidate['id']]=candidate + return {'evidence':sorted(chosen.values(),key=lambda c:c['start']), + 'method':'lexical_overlap','verification':'retrieval_only','faithfulness_established':False} + +def remove(text,spans): + spans=merge(text,spans);cursor=0;parts=[] + for s in spans:parts.append(text[cursor:s['start']]);cursor=s['end'] + parts.append(text[cursor:]);return ''.join(parts) diff --git a/mlx/src/solomon_mlx/_vendor/semantics.py b/mlx/src/solomon_mlx/_vendor/semantics.py new file mode 100644 index 0000000000000000000000000000000000000000..6edfe8afb91dd9eed89edb7bf59a65a2f52ec61a --- /dev/null +++ b/mlx/src/solomon_mlx/_vendor/semantics.py @@ -0,0 +1,71 @@ +# Copyright 2026 Doccy Pty Ltd. Apache-2.0. +# Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md. +"""Scope 9 answer semantics (docs/plans/2026-09-20-scope9-noul-final-refinement.md §1). + +Yes/no, entity and multi-label candidates are Nouls: one probability P(yes). Gold yes only when the +document clearly establishes it; not stated and conflicting are no. Choice (single, ordered) is a +distribution over the listed options only; reserved-gold items have no Scope 9 target. + +Works for both readouts: four_collapsed (four-state letter logits A/B/C/D, collapsed) and two_letter (A/B). +""" +import numpy as np + +YES, NO, NOT_STATED, CONFLICTING = 0, 1, 2, 3 + + +def softmax(x, t=1.0): + z = np.asarray(x, np.float64) / t + z = z - z.max() + e = np.exp(z) + return e / e.sum() + + +def noul_gold(gold4): + """Four-state (or two-state) gold -> 1 for yes, 0 for no.""" + return int(int(gold4) == YES) + + +def noul_logit(letter_logits): + """Binary log-odds z = log P(yes)/P(no) of a Noul branch: letter A against everything else.""" + logits = np.asarray(letter_logits, np.float64) + if len(logits) not in (2, 4): + raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}') + rest = logits[1:] - logits[1:].max() + return float(logits[YES] - (logits[1:].max() + np.log(np.exp(rest).sum()))) + + +def p_yes(letter_logits, t=1.0): + """P(yes) from a Noul branch: letter A of a 2-letter (two_letter) or 4-state (four_collapsed) readout. + + Temperature applies to the COLLAPSED binary logit, not to the letters: a Noul is a binary unit whose + 'no' mass may be spread over several reserved letters, so p_yes(t) = sigmoid(z/t) with z = noul_logit. + At t = 1 this is exactly softmax over the letters at A (the two forms only differ once t != 1, where the + letterwise form would decay toward 1/len(letters) instead of toward 1/2). scope9.qualification.p_yes and + abstention_refit/readout.py fit and evaluate the collapsed form, so the serving path must match it. + """ + logits = np.asarray(letter_logits, np.float64) + if len(logits) not in (2, 4): + raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}') + if t == 1.0: + return float(softmax(logits)[YES]) + z = noul_logit(logits) / float(t) + return float(1.0 / (1.0 + np.exp(-z))) if z > -700 else 0.0 + + +def noul_confidence(p): + return max(p, 1.0 - p) + + +def listed_gold(gold, n_options): + """Listed option index, or None when the old gold was a reserved slot (not stated / none-of-listed / conflicting).""" + return int(gold) if isinstance(gold, (int, np.integer)) and 0 <= int(gold) < n_options else None + + +def listed_probs(letter_logits, n_options, t=1.0): + """Choice distribution over the listed options only (reserved slots, if present, are discarded).""" + return softmax(np.asarray(letter_logits, np.float64)[:n_options], t) + + +def complement_deviation(p, p_negated): + """G3a under Scope 9: a statement and its negation should sum to 1.""" + return abs(p - (1.0 - p_negated)) diff --git a/mlx/src/solomon_mlx/api.py b/mlx/src/solomon_mlx/api.py new file mode 100644 index 0000000000000000000000000000000000000000..5441a213e8185da93d2e0c901c61710ced6538df --- /dev/null +++ b/mlx/src/solomon_mlx/api.py @@ -0,0 +1,321 @@ +"""Public document-state API and four-state answer semantics.""" + +import copy +import json +import math +from pathlib import Path + +from ._vendor.contract import _state_parts, decision, parse_questions, present +from ._vendor.prompts import boolean_block, label_block, listwise_block +from ._vendor.semantics import listed_probs, p_yes +from .artifacts import digest, sha256 + +TASKS = ("boolean", "single", "ordered", "multilabel", "entity") + + +def branches(spec): + task, req = spec["task"], spec["request"] + if task == "boolean": + return [(boolean_block(req["question"]), 4, "boolean/state4")] + if task in ("single", "ordered"): + block, width = listwise_block( + req["question"], req["options"], ordered=task == "ordered", reserved=task == "single" + ) + return [(block, width, task + ("/choiceR" if task == "single" else "/choiceS"))] + if task == "entity": + return [ + (boolean_block(req["template"].replace("{entity}", c)), 4, "entity/state4") + for c in req["entities"] + ] + return [(*label_block(req["question"], c), "multilabel/state4") for c in req["labels"]] + + +def distributions(spec, rows, temperature): + if spec["task"] in ("boolean", "entity", "multilabel"): + values = [p_yes(r["letter_logits"], temperature) for r in rows] + return [[p, 1 - p] for p in values] + return [listed_probs(rows[0]["letter_logits"], len(spec["texts"]), temperature).tolist()] + + +def ordering_score(values): + """Product of per-unit top probabilities; not a calibrated joint probability.""" + if not values: + raise ValueError("At least one answer unit is required") + for p in values: + if len(p) < 2 or not all(math.isfinite(v) and v >= 0 for v in p) or abs(sum(p) - 1) > 1e-6: + raise ValueError("Invalid answer distribution") + return math.prod(max(p) for p in values) + + +class DocumentState: + def __init__(self, owner, data): + self._owner, self._data, self.closed = owner, data, False + self.image_hashes = {p["image"]: sha256(p["image"]) for p in data["parts"] if "image" in p} + + @property + def prefix_tokens(self): + self._check() + return len(self._data["prefix_ids"]) + + def _check(self): + if self.closed: + raise ValueError("Document state is closed") + if any(sha256(path) != value for path, value in self.image_hashes.items()): + raise ValueError("Document image changed after prefill") + + def save(self, path): + """Save a source-bound replay recipe, never pickle executable cache objects.""" + self._check() + body = { + "format": "solomon-mlx-replay-v1", + "runtime": self._owner.identity["fingerprint"], + "parts": self._data["parts"], + "image_hashes": self.image_hashes, + "prefix_ids_sha256": digest(self._data["prefix_ids"]), + } + Path(path).write_text(json.dumps({**body, "sha256": digest(body)}, indent=2)) + + def close(self): + with self._owner.engine.lock: + self._data.clear() + self.closed = True + + def __enter__(self): + self._check() + return self + + def __exit__(self, *args): + self.close() + + +class Solomon: + @classmethod + def load( + cls, + model_dir, + profile="quality", + *, + chunk_size=2048, + max_tokens=40960, + page_selector=None, + calibration=None, + ): + if profile != "quality": + raise ValueError("Only full BF16 quality is implemented; quantization is secondary") + from .engine import Engine + + return cls( + Engine(model_dir, chunk_size=chunk_size, max_tokens=max_tokens), + page_selector=page_selector, + calibration=calibration, + ) + + def __init__(self, engine, *, page_selector=None, calibration=None): + self.engine, self.identity, self.page_selector = engine, engine.identity, page_selector + self.temperatures = dict.fromkeys(TASKS, 1.0) + self.calibration_status = "uncalibrated" + if calibration is not None: + artifact = json.loads(Path(calibration).read_text()) + payload = {k: v for k, v in artifact.items() if k != "sha256"} + if ( + artifact.get("sha256") != digest(payload) + or artifact["runtime"] != self.identity["fingerprint"] + ): + raise ValueError("Calibration checksum or MLX runtime identity mismatch") + temps = artifact["temperatures"] + if set(temps) != set(TASKS) or any( + isinstance(v, bool) + or not isinstance(v, (int, float)) + or not math.isfinite(v) + or not 0 < v <= 20 + for v in temps.values() + ): + raise ValueError("Invalid temperatures") + self.temperatures, self.calibration_status = temps, "profile_fitted" + + def prefill(self, document): + parts = copy.deepcopy(_state_parts(document)) + if not parts: + parts = [{"text": ""}] + for p in parts: + if not isinstance(p, dict) or set(p) not in ({"text"}, {"image"}): + raise ValueError("Each document part must contain only text or image") + if "text" in p and not isinstance(p["text"], str): + raise ValueError("Text parts must be strings") + if "image" in p: + p["image"] = str(Path(p["image"]).resolve(strict=True)) + hashes = {p["image"]: sha256(p["image"]) for p in parts if "image" in p} + state = DocumentState(self, self.engine.prefill(parts)) + if state.image_hashes != hashes: + state.close() + raise ValueError("Image changed while document was being prefilled") + return state + + def replay(self, path): + body = json.loads(Path(path).read_text()) + expected = body.pop("sha256") + if ( + digest(body) != expected + or body["format"] != "solomon-mlx-replay-v1" + or body["runtime"] != self.identity["fingerprint"] + ): + raise ValueError("Replay checksum or runtime mismatch") + if any(sha256(p) != h for p, h in body["image_hashes"].items()): + raise ValueError("Replay image changed") + state = self.prefill(body["parts"]) + if digest(state._data["prefix_ids"]) != body["prefix_ids_sha256"]: + state.close() + raise ValueError("Replay tokenization differs") + return state + + def _answer(self, state, spec, execution="cached"): + rows = [self.engine.ask(state._data, b, n, h, execution=execution) for b, n, h in branches(spec)] + dists = distributions(spec, rows, self.temperatures[spec["task"]]) + return { + **present(spec, dists), + "ordering_score": ordering_score(dists), + "temperature": self.temperatures[spec["task"]], + }, rows + + def decide( + self, + *, + state, + questions, + evidence="support", + evidence_max_calls=64, + execution="cached", + diagnostics=False, + ): + if not isinstance(state, DocumentState) or state._owner is not self: + raise ValueError("State belongs to a different model instance") + if evidence not in ("none", "support", "sufficiency", "removal"): + raise ValueError("Invalid evidence level") + if type(evidence_max_calls) is not int or not 0 <= evidence_max_calls <= 512: + raise ValueError("Invalid evidence call budget") + specs = parse_questions(questions) + with self.engine.lock: + state._check() + answers, usage = {}, {"branches": 0, "input_tokens": 0, "evidence_calls": 0} + for spec in specs: + answer, rows = self._answer(state, spec, execution) + body = self._evidence(state, spec, answer, evidence, evidence_max_calls) + answer.update( + evidence=body["references"], evidence_status=body["status"], evidence_detail=body + ) + if diagnostics: + answer["branches"] = rows + answers[spec["id"]] = answer + usage["branches"] += len(rows) + usage["input_tokens"] += sum( + r["branch_tokens"] if execution == "cached" else r["prompt_tokens"] for r in rows + ) + usage["evidence_calls"] += body.get("calls", 0) + return { + "answers": answers, + "usage": usage, + "runtime": self.identity, + "calibration_status": self.calibration_status, + "answer_policy": "always_answers", + } + + def _fresh(self, document, spec): + with self.prefill(document) as state: + answer, _ = self._answer(state, spec) + return decision(spec, answer) + + def _evidence(self, state, spec, answer, level, budget): + from ._vendor import evidence_v3 as v3 + from ._vendor.evidence import image_pages, validate_pages, validate_spans + from ._vendor.retrieval import lexical_select, remove + + body = { + "references": [], + "status": "not_requested", + "calls": 0, + "verification": "none", + "faithfulness_established": False, + } + if level == "none": + return body + parts, req = state._data["parts"], spec["request"] + task = spec["task"] + if task == "entity": + questions = [req["template"].replace("{entity}", c) for c in req["entities"]] + elif task == "multilabel": + questions = [req["question"] + " Label: " + c for c in req["labels"]] + else: + questions = [req["question"] + (" " + " ".join(req["options"]) if "options" in req else "")] + images = [p["image"] for p in parts if "image" in p] + needed = (1 if images else len(questions)) if level in ("sufficiency", "removal") else 0 + needed += int(level == "removal") + if needed > budget: + return {**body, "status": "budget_exhausted", "required_calls": needed} + baseline = decision(spec, answer) + if images: + if self.page_selector is None: + return {**body, "status": "unsupported_page_selector", "pages_available": len(images)} + pages = image_pages(images) + selector = self.page_selector + plan = None + if hasattr(selector, "plan"): + plan = selector.plan( + pages, questions, **({"task": task} if getattr(selector, "task_aware", False) else {}) + ) + if type(plan.get("calls")) is not int or plan["calls"] < 0: + raise ValueError("Invalid page selector call estimate") + if needed + plan["calls"] > budget: + return {**body, "status": "budget_exhausted", "required_calls": needed + plan["calls"]} + selection = ( + selector.execute(plan) if plan is not None else selector(copy.deepcopy(pages), questions) + ) + calls = selection.get("cost", {}).get("calls", 0) + if calls != (plan["calls"] if plan is not None else 0): + raise ValueError("Page selector exceeded its declared call budget") + refs = validate_pages(pages, selection["evidence"]) + body["calls"] = calls + selected = {r["page"] for r in refs} + page, remainder = 0, [] + for part in parts: + if "image" in part: + page += 1 + if page in selected: + continue + remainder.append(part) + subsets = [([{"image": r["path"]} for r in refs], spec)] + else: + text = "".join(p["text"] for p in parts) + selection = lexical_select(text, questions) + refs = validate_spans(text, selection["evidence"]) + structure = v3.Structure(text) + packages, subsets = [], [] + for i, q in enumerate(questions): + subject = req["entities"][i] if task == "entity" else None + package = v3.build(text, q, refs, subject=subject, structure=structure) + unit = copy.deepcopy(spec) + if "candidates" in spec: + candidate = spec["candidates"][i] + unit["candidates"] = [candidate] + unit["request"]["entities" if task == "entity" else "labels"] = [candidate] + subsets.append((package["text"], unit)) + packages.append({k: v for k, v in package.items() if k != "text"}) + body["packages"] = packages + remainder = remove(text, refs) + body.update( + references=refs, status="found" if refs else "no_support_found", verification="retrieval_only" + ) + if level in ("sufficiency", "removal"): + predictions = [self._fresh(doc, unit) for doc, unit in subsets] + assembled = ( + {k: v for d in predictions for k, v in d.items()} + if "candidates" in spec and not images + else predictions[0] + ) + body["evidence_only"] = {"prediction": assembled, "agrees_with_full": assembled == baseline} + body["calls"] += len(subsets) + body["verification"] = "fresh_source_reencoding" + if level == "removal": + removed = self._fresh(remainder, spec) + body["evidence_removed"] = {"prediction": removed, "agrees_with_full": removed == baseline} + body["calls"] += 1 + return body diff --git a/mlx/src/solomon_mlx/artifacts.py b/mlx/src/solomon_mlx/artifacts.py new file mode 100644 index 0000000000000000000000000000000000000000..e820c787f721ee9ef12ae2b497590b1e1759eb62 --- /dev/null +++ b/mlx/src/solomon_mlx/artifacts.py @@ -0,0 +1,77 @@ +"""Artifact verification and distinct MLX runtime identities.""" + +import hashlib +import json +from importlib.metadata import version +from pathlib import Path + +SOLOMON_REVISION = "5c0a4a82ddaeca6da2e3013f7045a8196c86957d" +BASE_REVISION = "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0" +ADAPTER_SHA = "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca" +HEADS_SHA = "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f" + + +def sha256(path): + h = hashlib.sha256() + with Path(path).open("rb") as f: + for block in iter(lambda: f.read(8 << 20), b""): + h.update(block) + return h.hexdigest() + + +def digest(value): + return hashlib.sha256( + json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode() + ).hexdigest() + + +def verify_release(root): + root = Path(root) + manifest = json.loads((root / "MANIFEST.json").read_text()) + checked = {} + for row in manifest["files"]: + rel = row["destination"] + path = (root / rel).resolve() + if not path.is_relative_to(root.resolve()): + raise ValueError("Manifest path escapes source directory") + expected = row.get("staged_sha256") + size = row.get("staged_bytes") + actual = sha256(path) + if expected and actual != expected: + raise ValueError(f"Source checksum mismatch: {rel}") + if size is not None and path.stat().st_size != size: + raise ValueError(f"Source size mismatch: {rel}") + checked[rel] = actual + return checked + + +def code_identity(): + root = Path(__file__).parent + return digest({str(p.relative_to(root)): sha256(p) for p in sorted(root.rglob("*.py"))}) + + +def runtime_identity(binding, *, chunk_size=2048, max_tokens=40960): + import platform + + import mlx.core as mx + + value = { + "backend": "mlx-metal", + "contract": "solomon-mlx-v1", + "source_contract": "solomon-v1", + "profile": binding["profile"], + "chunk_size": chunk_size, + "max_tokens": max_tokens, + "metal_device": mx.device_info(), + "macos_version": platform.mac_ver()[0], + "model_binding": digest(binding), + "code_sha256": code_identity(), + "versions": {p: version(p) for p in ("mlx", "mlx-vlm", "transformers", "numpy", "pillow")}, + "placement": "question", + "lora_scale": 2.0, + "answer_projection": "trained-semantic-head-float32", + "recurrence": "mlx-float32", + "solomon_revision": SOLOMON_REVISION, + "base_revision": BASE_REVISION, + } + return {**value, "fingerprint": digest(value)} diff --git a/mlx/src/solomon_mlx/budget.py b/mlx/src/solomon_mlx/budget.py new file mode 100644 index 0000000000000000000000000000000000000000..92e6f935f985060c357f5d066839371cd7b9bc0a --- /dev/null +++ b/mlx/src/solomon_mlx/budget.py @@ -0,0 +1,71 @@ +"""Fail-closed local cost reservations for serialized Modal reference jobs.""" + +import fcntl +import json +import math +import os +import time +import uuid +from contextlib import contextmanager +from pathlib import Path + + +class Budget: + def __init__(self, path, cap=250.0): + self.path, self.cap = Path(path), cap + if not math.isfinite(cap) or not 0 < cap <= 250: + raise ValueError("The authorized cap is at most US$250") + + @contextmanager + def reserve(self, amount, label): + if not math.isfinite(amount) or amount <= 0: + raise ValueError("Positive finite reservation required") + self.path.parent.mkdir(parents=True, exist_ok=True) + with self.path.with_suffix(".lock").open("a") as lock: + # Held until the synchronous remote call finishes. Refuse concurrent jobs. + try: + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise RuntimeError("Another reference job already owns the GPU budget") from None + ledger = ( + json.loads(self.path.read_text()) if self.path.exists() else {"cap": self.cap, "entries": []} + ) + if ledger["cap"] != self.cap: + raise ValueError("Budget cap changed") + if any(e["status"] == "reserved" for e in ledger["entries"]): + raise RuntimeError( + "Unresolved prior dispatch: reconcile its remote status before another GPU job" + ) + if any( + type(e.get("reserved_usd")) not in (int, float) + or not math.isfinite(e["reserved_usd"]) + or e["reserved_usd"] <= 0 + or e.get("status") not in ("reserved", "completed_conservative_charge") + for e in ledger["entries"] + ): + raise ValueError("Corrupt budget ledger") + used = sum(e["reserved_usd"] for e in ledger["entries"]) + if used + amount > self.cap: + raise RuntimeError("Modal budget exhausted before dispatch") + entry = { + "id": uuid.uuid4().hex, + "label": label, + "reserved_usd": amount, + "status": "reserved", + "time": time.time(), + } + ledger["entries"].append(entry) + self._write(ledger) + # If interrupted, leave the reservation unresolved. + yield entry + entry["status"] = "completed_conservative_charge" + entry["completed"] = time.time() + self._write(ledger) + + def _write(self, value): + tmp = self.path.with_suffix(".tmp") + with tmp.open("w") as file: + file.write(json.dumps(value, indent=2)) + file.flush() + os.fsync(file.fileno()) + tmp.replace(self.path) diff --git a/mlx/src/solomon_mlx/cli.py b/mlx/src/solomon_mlx/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..954fcfb66e59d0599852c79eb083b8edb33db316 --- /dev/null +++ b/mlx/src/solomon_mlx/cli.py @@ -0,0 +1,76 @@ +import argparse +import json +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description="Private Solomon BF16 MLX tooling") + commands = parser.add_subparsers(dest="command", required=True) + verify = commands.add_parser("verify-source") + verify.add_argument("directory") + download = commands.add_parser("download-base") + download.add_argument("--output", default="snapshots/base") + convert = commands.add_parser("prepare") + convert.add_argument("--base", default="snapshots/base") + convert.add_argument("--solomon", default="snapshots/solomon") + convert.add_argument("--manifest", default="snapshots/base-manifest.json") + convert.add_argument("--output", default="models/quality") + decide = commands.add_parser("decide") + decide.add_argument("--model", default="models/quality") + decide.add_argument("--document", required=True) + decide.add_argument("--questions", required=True) + decide.add_argument( + "--evidence", choices=["none", "support", "sufficiency", "removal"], default="support" + ) + args = parser.parse_args() + if args.command == "verify-source": + from .artifacts import verify_release + + print(json.dumps(verify_release(args.directory), indent=2)) + elif args.command == "download-base": + from huggingface_hub import HfApi, snapshot_download + + from .artifacts import BASE_REVISION + from .prepare import verify_base + + model = HfApi().model_info("Qwen/Qwen3.8-27B", revision=BASE_REVISION, files_metadata=True) + manifest = { + "revision": model.sha, + "files": [ + { + "name": f.rfilename, + "size": f.size, + "blob_id": f.blob_id, + "sha256": f.lfs.sha256 if f.lfs else None, + } + for f in model.siblings + ], + } + Path(args.output).parent.mkdir(parents=True, exist_ok=True) + Path(args.output + "-manifest.json").write_text(json.dumps(manifest, indent=2)) + snapshot_download("Qwen/Qwen3.8-27B", revision=BASE_REVISION, local_dir=args.output, max_workers=18) + verified = verify_base(args.output, manifest) + Path(args.output + "-verified.json").write_text(json.dumps(verified, indent=2)) + elif args.command == "prepare": + from .prepare import prepare + + prepare(args.base, args.solomon, args.output, args.manifest) + else: + from .api import Solomon + + model = Solomon.load(args.model) + with model.prefill(Path(args.document).read_text()) as state: + print( + json.dumps( + model.decide( + state=state, + questions=json.loads(Path(args.questions).read_text()), + evidence=args.evidence, + ), + indent=2, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/mlx/src/solomon_mlx/engine.py b/mlx/src/solomon_mlx/engine.py new file mode 100644 index 0000000000000000000000000000000000000000..d3f25fd7455f627b22101bb5b1bb1db7d771646f --- /dev/null +++ b/mlx/src/solomon_mlx/engine.py @@ -0,0 +1,333 @@ +"""BF16 Metal execution with instance-owned adaptation and isolated question caches.""" + +import copy +import json +import threading +import time +from pathlib import Path + +import mlx.core as mx +import numpy as np +from mlx import nn +from PIL import Image + +from ._vendor.prompts import PAGE, SYSTEM +from .artifacts import ADAPTER_SHA, BASE_REVISION, HEADS_SHA, SOLOMON_REVISION, runtime_identity, sha256 + + +class SwitchLoRA(nn.Module): + def __init__(self, linear, a, b, context): + super().__init__() + self.linear, self.lora_a, self.lora_b = linear, a, b + self._context = context + + def __call__(self, x): + y = self.linear(x) + start = self._context["start"] + if start is None or start >= x.shape[1]: + return y + delta = (2.0 * ((x[:, start:].astype(mx.float32) @ self.lora_a) @ self.lora_b)).astype(y.dtype) + return y + delta if start == 0 else mx.concatenate([y[:, :start], y[:, start:] + delta], axis=1) + + +def fork_cache(caches): + """New cache containers and array handles; MLX owns copy-on-write storage. + + mx.array creates a distinct handle, so slice updates cannot change a prefix's + Python array. Recurrent/window updates replace the branch's private slots. + """ + from mlx_vlm.models.cache import ArraysCache, KVCache + + result = [] + for original in caches: + if isinstance(original, ArraysCache): + branch = ArraysCache(len(original.cache)) + branch.cache = [None if x is None else mx.array(x) for x in original.cache] + elif isinstance(original, KVCache): + branch = KVCache() + branch.state = tuple(None if x is None else mx.array(x) for x in original.state) + else: + raise TypeError(f"Unsupported prefix cache: {type(original).__name__}") + result.append(branch) + return result + + +class Engine: + def __init__(self, directory, *, chunk_size=2048, max_tokens=40960): + from mlx_vlm.models.qwen3_vl.processing_qwen3_vl import Qwen3VLProcessor + from mlx_vlm.utils import load_model + + self.directory = Path(directory).resolve() + self.binding = json.loads((self.directory / "binding.json").read_text()) + if ( + self.binding.get("schema") != "solomon-mlx-binding-v1" + or self.binding.get("base_revision") != BASE_REVISION + or self.binding.get("solomon_revision") != SOLOMON_REVISION + ): + raise ValueError("Unrecognized or unpinned Solomon MLX binding") + if self.binding["profile"] != "quality" or self.binding["dtype"] != "bfloat16": + raise ValueError("This runtime currently accepts only the BF16 quality profile") + for name, expected in self.binding["files"].items(): + path = (self.directory / name).resolve() + if not path.is_relative_to(self.directory) or sha256(path) != expected: + raise ValueError(f"Model artifact checksum mismatch: {name}") + adapter, heads = self.directory / "adapter.safetensors", self.directory / "heads.npz" + if sha256(adapter) != ADAPTER_SHA or sha256(heads) != HEADS_SHA: + raise ValueError("Solomon checkpoint identity mismatch") + if not 1 <= chunk_size <= 2048 or not 1 <= max_tokens <= 40960: + raise ValueError("Invalid chunk size or context ceiling") + weight_bytes = sum( + (self.directory / name).stat().st_size + for name in self.binding["files"] + if name.endswith((".safetensors", ".npz")) + ) + if weight_bytes + 4 * 2**30 > mx.device_info()["max_recommended_working_set_size"]: + raise MemoryError( + "Full BF16 weights and minimum workspace exceed this Mac’s recommended Metal working set" + ) + self.chunk_size, self.max_tokens = chunk_size, max_tokens + self.lock = threading.RLock() + self.context = {"start": None} + self.model = load_model(self.directory / "backbone", lazy=True, strict=True) + self.processor = Qwen3VLProcessor.from_pretrained( + str(self.directory / "backbone"), trust_remote_code=False + ) + self.lm, self.t = self.model.language_model, self.processor.tokenizer + self.pad = self.t.convert_tokens_to_ids("<|image_pad|>") + weights = mx.load(str(adapter)) + for name in sorted({key.rsplit(".", 1)[0] for key in weights}): + parts = name.split(".") + if parts[:2] != ["model", "layers"]: + raise ValueError(f"Unexpected adapter target: {name}") + owner = self.lm.model.layers[int(parts[2])] + for part in parts[3:-1]: + owner = getattr(owner, part) + linear = getattr(owner, parts[-1]) + a, b = weights[name + ".lora_a"].astype(mx.float32), weights[name + ".lora_b"].astype(mx.float32) + if a.shape != (linear.weight.shape[1], 64) or b.shape != (64, linear.weight.shape[0]): + raise ValueError(f"Adapter orientation/shape mismatch: {name}") + setattr(owner, parts[-1], SwitchLoRA(linear, a, b, self.context)) + with np.load(heads, allow_pickle=False) as archive: + keys = {k[:-7] for k in archive.files if k.endswith("/weight")} + required_heads = { + "boolean/state4", + "entity/state4", + "multilabel/state4", + "ordered/threshold4", + "single/choiceR", + "single/choiceS", + "single/sufficiency3", + "ordered/choiceR", + "ordered/choiceS", + "ordered/sufficiency3", + } + if keys != required_heads: + raise ValueError("All ten semantic heads are required") + self.heads = {} + for key in keys: + w, b = archive[key + "/weight"], archive[key + "/bias"] + if ( + w.shape != (10, 5120) + or b.shape != (10,) + or not np.isfinite(w).all() + or not np.isfinite(b).all() + ): + raise ValueError("Invalid semantic head") + self.heads[key] = (mx.array(w, mx.float32), mx.array(b, mx.float32)) + self.model.freeze() + self.model.eval() + mx.eval(self.model.parameters(), self.heads) + self.identity = runtime_identity(self.binding, chunk_size=self.chunk_size, max_tokens=self.max_tokens) + + def render(self, parts, block): + content = "" + for i, p in enumerate(parts): + if "text" in p: + content += ("\n" if i and "image" in parts[i - 1] else "") + p["text"] + else: + content += ("\n" if i and "text" in parts[i - 1] else "") + PAGE + return self.t.apply_chat_template( + [ + {"role": "system", "content": SYSTEM}, + {"role": "user", "content": "Document:\n" + content + "\n\n" + block}, + ], + tokenize=False, + add_generation_prompt=True, + enable_thinking=False, + ) + + def expand(self, ids, counts): + out, index = [], 0 + for token in ids: + if token == self.pad: + if index >= len(counts): + raise ValueError("Unexpected image placeholder in document text") + out.extend([token] * counts[index]) + index += 1 + else: + out.append(token) + if index != len(counts): + raise ValueError("Image placeholder count mismatch") + return out + + def positions(self, start, count): + return mx.broadcast_to(mx.arange(start, start + count)[None, None, :], (3, 1, count)) + + def admit(self, count): + if count < 1 or count > self.max_tokens: + raise ValueError(f"{count} tokens exceeds the {self.max_tokens}-token scope ceiling") + # Conservative allowance: BF16 attention KV + FP32 recurrent states and + # chunk intermediates. This supplements the token ceiling, not a promise + # of availability in the presence of other processes. + temporary = 4 * 2**30 + count * 16 * 2 * 4 * 256 * 2 + limit = mx.device_info()["max_recommended_working_set_size"] + if mx.get_active_memory() + temporary > limit: + raise MemoryError("Insufficient recommended Metal working set for this request") + + def forward(self, ids, positions, cache, *, embeds=None, adapter_from=None, taps=()): + hidden, captured = None, {} + try: + for start in range(0, len(ids), self.chunk_size): + end = min(start + self.chunk_size, len(ids)) + self.context["start"] = None if adapter_from is None else max(0, adapter_from - start) + last = end == len(ids) + out = self.lm( + mx.array([ids[start:end]]), + cache=cache, + position_ids=positions[:, :, start:end], + inputs_embeds=None if embeds is None else embeds[:, start:end], + skip_logits=True, + return_hidden=last, + capture_layer_ids=list(taps) if last else None, + ) + if last: + hidden = out.hidden_states[-1][0, -1].astype(mx.float32) + captured = { + str(i): self.lm.model.norm(h[:, -1:])[0, -1].astype(mx.float32) + for i, h in zip(sorted(set(taps)), out.hidden_states[:-1]) + } + mx.eval(hidden, captured) + mx.eval([c.state for c in cache]) + return hidden, captured + finally: + self.context["start"] = None + + def prefill(self, parts): + with self.lock: + prefill_started = time.perf_counter() + text = self.render(parts, "X") + boundary = text.rfind("\n\nX") + if boundary < 0: + raise ValueError("Missing document boundary") + raw = self.t.encode(text[:boundary], add_special_tokens=False) + if "text" in parts[-1]: + raw = raw[:-1] + vision_started = time.perf_counter() + counts, grids, features = [], [], [] + for part in parts: + if "image" not in part: + continue + with Image.open(part["image"]) as image: + processed = self.processor.image_processor(images=[image.convert("RGB")]) + grid_np = np.asarray(processed["image_grid_thw"]) + count = int(grid_np.prod()) // self.model.config.vision_config.spatial_merge_size**2 + self.admit(len(raw) + sum(counts) + count - len(counts) - 1) + grid = mx.array(grid_np) + pixels = mx.array(np.asarray(processed["pixel_values"])).astype( + self.model.vision_tower.patch_embed.proj.weight.dtype + ) + feature, _ = self.model.vision_tower(pixels, grid) + mx.eval(feature) + counts.append(count) + grids.append(grid) + features.append(feature) + vision_seconds = time.perf_counter() - vision_started if counts else 0.0 + ids = self.expand(raw, counts) + self.admit(len(ids)) + embeds, delta, feats, grid = None, 0, None, None + if counts: + feats, grid = mx.concatenate(features), mx.concatenate(grids) + f = self.model.get_input_embeddings( + mx.array([ids]), mx.zeros((1,)), image_grid_thw=grid, cached_image_features=feats + ) + embeds, positions = f.inputs_embeds, f.position_ids + delta = int(np.asarray(f.rope_deltas).reshape(-1)[0]) + if delta != int(mx.max(positions).item()) + 1 - len(ids): + raise ValueError("Multimodal RoPE offset mismatch") + else: + positions = self.positions(0, len(ids)) + cache = self.lm.make_cache() + started = time.perf_counter() + self.forward(ids, positions, cache, embeds=embeds) + return { + "parts": copy.deepcopy(parts), + "prefix_ids": ids, + "cache": cache, + "counts": counts, + "rope_delta": delta, + "features": feats, + "grid": grid, + "positions": positions, + "prefill_seconds": time.perf_counter() - prefill_started, + "language_prefill_seconds": time.perf_counter() - started, + "vision_seconds": vision_seconds, + } + + def ask(self, state, block, width, head, *, execution="cached", taps=()): + with self.lock: + if head not in self.heads or not 2 <= width <= 10: + raise ValueError("Unknown semantic head or invalid width") + ids = self.expand( + self.t.encode(self.render(state["parts"], block), add_special_tokens=False), state["counts"] + ) + self.admit(len(ids)) + p = len(state["prefix_ids"]) + if ids[:p] != state["prefix_ids"]: + raise ValueError("Question token prefix differs from cached document") + started = time.perf_counter() + if execution == "cached": + hidden, captured = self.forward( + ids[p:], + self.positions(p + state["rope_delta"], len(ids) - p), + fork_cache(state["cache"]), + adapter_from=0, + taps=taps, + ) + elif execution == "full": + embeds = None + if state["counts"]: + f = self.model.get_input_embeddings( + mx.array([ids]), + mx.zeros((1,)), + image_grid_thw=state["grid"], + cached_image_features=state["features"], + ) + embeds, positions = f.inputs_embeds, f.position_ids + else: + positions = self.positions(0, len(ids)) + hidden, captured = self.forward( + ids, positions, self.lm.make_cache(), embeds=embeds, adapter_from=p, taps=taps + ) + else: + raise ValueError("Execution must be cached or full") + w, b = self.heads[head] + logits = (w @ hidden + b)[:width] + mx.eval(logits) + values = np.asarray(logits) + if not np.isfinite(values).all(): + raise ValueError("Nonfinite trained-head output") + result = { + "letter_logits": values.tolist(), + "head_key": head, + "prompt_tokens": len(ids), + "branch_tokens": len(ids) - p, + "reused_prefix_tokens": p if execution == "cached" else 0, + "seconds": time.perf_counter() - started, + } + if taps: + result.update( + hidden=np.asarray(hidden).tolist(), + taps={k: np.asarray(v).tolist() for k, v in captured.items()}, + token_ids=ids, + ) + return result diff --git a/mlx/src/solomon_mlx/evaluation.py b/mlx/src/solomon_mlx/evaluation.py new file mode 100644 index 0000000000000000000000000000000000000000..a4f1cd0a58835608a08003bcd0d14c6c55347fad --- /dev/null +++ b/mlx/src/solomon_mlx/evaluation.py @@ -0,0 +1,324 @@ +"""Resumable panel scoring, fit-only calibration and one-shot held-out reports.""" + +import gzip +import hashlib +import json +from collections import defaultdict +from pathlib import Path + +import numpy as np +from scipy.optimize import minimize_scalar + +from ._vendor.semantics import listed_probs, p_yes +from .api import TASKS +from .artifacts import ADAPTER_SHA, HEADS_SHA, digest, sha256 + + +def load_panel(directory): + directory = Path(directory) + manifest = json.loads((directory / "manifest.json").read_text()) + if ( + manifest["adapter_sha256"] != ADAPTER_SHA + or manifest["heads_sha256"] != HEADS_SHA + or manifest["readout_mode"] != "four_collapsed" + ): + raise ValueError("Panel belongs to a different checkpoint or answer semantics") + raw = gzip.decompress((directory / "jobs.json.gz").read_bytes()) + if hashlib.sha256(raw).hexdigest() != manifest["jobs_sha256"]: + raise ValueError("Panel jobs checksum mismatch") + jobs = json.loads(raw) + if len({r["id"] for r in jobs}) != len(jobs): + raise ValueError("Duplicate panel branch IDs") + return jobs, manifest + + +def score_panel(model, panel, output): + """Atomically persist each document so interruption never requires rescoring it.""" + jobs, manifest = load_panel(panel) + output = Path(output) + output.mkdir(parents=True, exist_ok=True) + identity = { + "runtime": model.identity, + "panel_sha256": manifest["jobs_sha256"], + "panel_role": Path(panel).name.split("-")[0], + } + meta = output / "identity.json" + if meta.exists() and json.loads(meta.read_text()) != identity: + raise ValueError("Cannot resume with different model code, weights or panel") + meta.write_text(json.dumps(identity, indent=2)) + documents = defaultdict(list) + for row in jobs: + documents[row["document_key"]].append(row) + for key, group in documents.items(): + path = output / (key + ".json") + if path.exists(): + record = json.loads(path.read_text()) + body = {k: v for k, v in record.items() if k != "sha256"} + if ( + record["sha256"] != digest(body) + or record["identity"] != digest(identity) + or [r["id"] for r in record["rows"]] != [r["id"] for r in group] + ): + raise ValueError("Corrupt or mismatched resumed document") + continue + parts = group[0].get("parts") or [{"text": group[0]["doc"]}] + if any((r.get("parts") or [{"text": r["doc"]}]) != parts for r in group): + raise ValueError("Document key aliases different sources") + with model.prefill(parts) as state: + rows = [] + for job in group: + result = model.engine.ask(state._data, job["block"], job["n"], job["head_key"]) + rows.append({**result, **{k: job[k] for k in ("id", "task", "gold", "n", "question_id")}}) + body = { + "identity": digest(identity), + "rows": rows, + "prefix_tokens": state.prefix_tokens, + "prefill_seconds": state._data["prefill_seconds"], + } + temp = path.with_suffix(".tmp") + temp.write_text(json.dumps({**body, "sha256": digest(body)})) + temp.replace(path) + print("Scored " + key + " " + str(len(rows)) + " branches", flush=True) + completed = { + "identity": digest(identity), + "documents": len(documents), + "branches": len(jobs), + "files": {key + ".json": sha256(output / (key + ".json")) for key in documents}, + } + (output / "complete.json").write_text(json.dumps(completed, indent=2)) + + +def read_scores(directory): + directory = Path(directory) + identity = json.loads((directory / "identity.json").read_text()) + completed = json.loads((directory / "complete.json").read_text()) + if completed["identity"] != digest(identity): + raise ValueError("Score identity mismatch") + rows = [] + for name, checksum in completed["files"].items(): + p = directory / name + if not p.resolve().is_relative_to(directory.resolve()) or sha256(p) != checksum: + raise ValueError("Score checksum mismatch") + record = json.loads(p.read_text()) + rows.extend(record["rows"]) + if len(rows) != completed["branches"]: + raise ValueError("Incomplete score set") + return rows, identity + + +def unit(row, temperature=1.0): + logits = row["letter_logits"] + task = row["task"] + gold = row["gold"] + if task in ("boolean", "entity", "multilabel"): + p = p_yes(logits, temperature) + return [1 - p, p], int(gold == 0) + width = row["n"] - 2 if row["head_key"].endswith("choiceR") else row["n"] + if not isinstance(gold, int) or not 0 <= gold < width: + return None, None + return listed_probs(logits, width, temperature).tolist(), gold + + +def fit_calibration(scores, output, *, panel_role): + if panel_role != "fit": + raise ValueError("Temperature fitting accepts fit panels only") + rows, identity = read_scores(scores) + if identity["panel_role"] != "fit": + raise ValueError("Scores were not generated from a fit panel") + output = Path(output) + if output.exists(): + raise FileExistsError("Calibration artifacts are immutable") + temperatures, losses = {}, {} + for task in TASKS: + selected = [r for r in rows if r["task"] == task and unit(r)[0] is not None] + if not selected: + raise ValueError("No fit examples for " + task) + + def loss(log_t, selected=selected): + t = float(np.exp(log_t)) + return float(np.mean([-np.log(max(unit(r, t)[0][unit(r, t)[1]], 1e-300)) for r in selected])) + + fit = minimize_scalar(loss, bounds=(np.log(0.05), np.log(20)), method="bounded") + temperatures[task] = float(np.exp(fit.x)) + losses[task] = {"before": loss(0.0), "after": float(fit.fun), "units": len(selected)} + payload = { + "schema": "solomon-mlx-temperature-v1", + "runtime": identity["runtime"]["fingerprint"], + "temperatures": temperatures, + "fit_panel_sha256": identity["panel_sha256"], + "losses": losses, + "selection_role": "fit", + "heldout_used": False, + } + output.write_text(json.dumps({**payload, "sha256": digest(payload)}, indent=2)) + return payload + + +def compare_rows(mlx_rows, cuda_rows, *, temperatures=None, reference_temperatures=None): + temperatures = temperatures or dict.fromkeys(TASKS, 1.0) + reference_temperatures = reference_temperatures or dict.fromkeys(TASKS, 1.0) + reference = {r["id"]: r for r in cuda_rows} + if len(reference) != len(cuda_rows) or set(reference) != {r["id"] for r in mlx_rows}: + raise ValueError("Comparison panels have different or duplicate branch IDs") + units = [] + questions = defaultdict(list) + for row in mlx_rows: + other = {**row, "letter_logits": reference[row["id"]]["letter_logits"]} + p, gold = unit(row, temperatures[row["task"]]) + q, _ = unit(other, reference_temperatures[row["task"]]) + if p is None: + continue + left, right = int(np.argmax(p)), int(np.argmax(q)) + item = { + "agreement": left == right, + "mlx_correct": left == gold, + "cuda_correct": right == gold, + "probability_drift": float(np.max(np.abs(np.asarray(p) - q))), + } + units.append(item) + questions[row["question_id"]].append(item) + if not units: + raise ValueError("No defined comparison targets") + agreement = float(np.mean([r["agreement"] for r in units])) + question_agreement = float(np.mean([all(x["agreement"] for x in r) for r in questions.values()])) + mlx_accuracy = float(np.mean([all(x["mlx_correct"] for x in r) for r in questions.values()])) + cuda_accuracy = float(np.mean([all(x["cuda_correct"] for x in r) for r in questions.values()])) + return { + "units": len(units), + "questions": len(questions), + "unit_decision_agreement": agreement, + "question_decision_agreement": float( + np.mean([all(x["agreement"] for x in r) for r in questions.values()]) + ), + "mlx_whole_question_accuracy": mlx_accuracy, + "cuda_whole_question_accuracy": cuda_accuracy, + "accuracy_degradation_percentage_points": 100 * (cuda_accuracy - mlx_accuracy), + "max_probability_drift": max(r["probability_drift"] for r in units), + "probability_comparison": { + "mlx_temperatures": temperatures, + "cuda_temperatures": reference_temperatures, + }, + "mean_probability_drift": float(np.mean([r["probability_drift"] for r in units])), + "quality_gate_passed": agreement >= 0.999 + and question_agreement >= 0.999 + and cuda_accuracy - mlx_accuracy <= 0.0025, + } + + +def read_cuda_scores(directory, panel, reference_identity): + """Reuse only scores bound to the exact pinned CUDA runtime and panel.""" + jobs, manifest = load_panel(panel) + directory = Path(directory) + result = {} + for file in sorted(directory.glob("scores*.json.gz")): + payload = json.loads(gzip.decompress(file.read_bytes())) + identity = payload["identity"] + if not payload["complete"] or identity["runtime"] != reference_identity: + raise ValueError("Existing CUDA scores do not match the fresh reference runtime") + if identity["manifest"]["jobs_sha256"] != manifest["jobs_sha256"]: + raise ValueError("CUDA scores use another panel") + for key, row in payload["scores"].items(): + if key in result: + raise ValueError("Duplicate CUDA score ID") + result[key] = row + if set(result) != {r["id"] for r in jobs}: + raise ValueError("CUDA score set is incomplete") + return [{**r, **result[r["id"]]} for r in jobs] + + +def select_calibration(fitted, dev_scores, output): + fitted, output = Path(fitted), Path(output) + if output.exists(): + raise FileExistsError("Selected calibration is immutable") + fit = json.loads(fitted.read_text()) + fit_payload = {k: v for k, v in fit.items() if k != "sha256"} + rows, identity = read_scores(dev_scores) + if ( + fit["sha256"] != digest(fit_payload) + or identity["runtime"]["fingerprint"] != fit["runtime"] + or identity["panel_role"] != "dev" + ): + raise ValueError("Calibration or development identity mismatch") + temperatures, selection = {}, {} + for task in TASKS: + selected = [r for r in rows if r["task"] == task and unit(r)[0] is not None] + if not selected: + raise ValueError("Missing development task " + task) + + def loss(t, selected=selected): + values = [unit(r, t) for r in selected] + return float(np.mean([-np.log(max(p[g], 1e-300)) for p, g in values])) + + original, candidate = loss(1.0), loss(fit["temperatures"][task]) + temperatures[task] = fit["temperatures"][task] if candidate < original else 1.0 + selection[task] = {"untempered_nll": original, "fit_temperature_nll": candidate} + payload = { + **fit_payload, + "temperatures": temperatures, + "selection_role": "dev_selected", + "fit_artifact_sha256": sha256(fitted), + "dev_panel_sha256": identity["panel_sha256"], + "development_selection": selection, + } + output.write_text(json.dumps({**payload, "sha256": digest(payload)}, indent=2)) + return payload + + +def heldout_report( + scores, + cuda_directory, + panel, + calibration, + reference, + output, + *, + reference_binding="evaluations/cuda-acceptance/input/serving-binding.json", +): + """Evaluate a frozen configuration once; an existing output cannot be replaced.""" + output = Path(output) + if output.exists(): + raise FileExistsError("Held-out report already exists; do not reuse it for selection") + rows, identity = read_scores(scores) + cal = json.loads(Path(calibration).read_text()) + payload = {k: v for k, v in cal.items() if k != "sha256"} + if ( + cal["sha256"] != digest(payload) + or cal["runtime"] != identity["runtime"]["fingerprint"] + or cal["selection_role"] != "dev_selected" + or identity["panel_role"] != "cert" + ): + raise ValueError( + "Held-out evaluation requires frozen development-selected calibration and cert scores" + ) + ref = json.loads(Path(reference).read_text()) + cuda = read_cuda_scores(cuda_directory, panel, ref["identity"]) + binding_path = Path(reference_binding) + source_manifest = json.loads((binding_path.parent / "manifest.json").read_text()) + if sha256(binding_path) != source_manifest["files"][binding_path.name]: + raise ValueError("CUDA acceptance binding checksum mismatch") + binding = json.loads(binding_path.read_text()) + for key in ( + "adapter_sha256", + "trained_heads_sha256", + "model_sha256", + "numerics", + "placement", + "arithmetic", + ): + if binding["runtime"][key] != ref["identity"][key]: + raise ValueError("CUDA calibration belongs to another reference runtime") + reference_temperatures = {task: binding["temperatures"]["models"][task]["temperature"] for task in TASKS} + report = { + **compare_rows( + rows, cuda, temperatures=cal["temperatures"], reference_temperatures=reference_temperatures + ), + "cuda_calibration_binding_sha256": sha256(binding_path), + "runtime": identity["runtime"], + "panel_sha256": identity["panel_sha256"], + "calibration_sha256": sha256(calibration), + "reference_sha256": sha256(reference), + "scope": "text-only held-out panel", + "image_qualification": False, + } + output.write_text(json.dumps(report, indent=2)) + return report diff --git a/mlx/src/solomon_mlx/prepare.py b/mlx/src/solomon_mlx/prepare.py new file mode 100644 index 0000000000000000000000000000000000000000..c6afc496fd22802b69961d73c9b7530a95fc8d99 --- /dev/null +++ b/mlx/src/solomon_mlx/prepare.py @@ -0,0 +1,121 @@ +"""Verify immutable input snapshots and create a separately checksummed BF16 model.""" + +import hashlib +import json +import shutil +from importlib.metadata import version +from pathlib import Path + +from .artifacts import BASE_REVISION, SOLOMON_REVISION, sha256, verify_release + + +def verify_base(root, manifest): + root = Path(root) + if manifest["revision"] != BASE_REVISION: + raise ValueError("Wrong pinned base revision") + result = {} + for row in manifest["files"]: + path = root / row["name"] + if not path.resolve().is_relative_to(root.resolve()): + raise ValueError("Unsafe base manifest path") + if path.stat().st_size != row["size"]: + raise ValueError("Base size mismatch: " + row["name"]) + actual = sha256(path) + if row["sha256"]: + if actual != row["sha256"]: + raise ValueError("Base checksum mismatch: " + row["name"]) + else: + data = path.read_bytes() + git_hash = hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest() + if git_hash != row["blob_id"]: + raise ValueError("Base Git blob mismatch: " + row["name"]) + result[row["name"]] = actual + return result + + +def prepare(base, solomon, output, manifest): + + base, solomon, output = map(Path, (base, solomon, output)) + source_hashes = verify_release(solomon) + base_hashes = verify_base(base, json.loads(Path(manifest).read_text())) + if output.exists(): + raise FileExistsError("Use a new output directory; existing conversions are immutable") + output.mkdir(parents=True) + convert_bf16(base, output / "backbone") + shutil.copy2(solomon / "adapter/adapter.safetensors", output / "adapter.safetensors") + shutil.copy2(solomon / "adapter/heads.npz", output / "heads.npz") + for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"): + shutil.copy2(solomon / name, output / name) + files = { + str(p.relative_to(output)): sha256(p) + for p in sorted(output.rglob("*")) + if p.is_file() and ".cache" not in p.parts + } + binding = { + "schema": "solomon-mlx-binding-v1", + "profile": "quality", + "dtype": "bfloat16", + "adapter_dtype": "float32", + "head_dtype": "float32", + "recurrent_state_dtype": "float32", + "sensitive_parameters": "FP32 normalization weights, A_log and dt_bias", + "conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset", + "adapter_scale": 2.0, + "adapter_placement": "question", + "quantization": None, + "base_revision": BASE_REVISION, + "solomon_revision": SOLOMON_REVISION, + "source_hashes": source_hashes, + "base_hashes": base_hashes, + "dependencies": {p: version(p) for p in ("mlx", "mlx-vlm", "transformers", "numpy")}, + "files": files, + } + (output / "binding.json").write_text(json.dumps(binding, indent=2, sort_keys=True)) + return binding + + +def convert_bf16(base, output): + """Convert one original shard at a time; never copy download caches. + + Keep normalization offsets in FP32 before adding one. Adding the unit + offset in BF16 would irreversibly round trained normalization parameters. + Large backbone matrices remain unquantized BF16. + """ + import mlx.core as mx + from mlx.utils import tree_flatten + from mlx_vlm.models.qwen3_5.config import ModelConfig + from mlx_vlm.models.qwen3_5.qwen3_5 import Model + + base, output = Path(base), Path(output) + output.mkdir(parents=True, exist_ok=False) + config = json.loads((base / "config.json").read_text()) + if config.get("model_type") != "qwen3_5" or config.get("quantization"): + raise ValueError("Expected original unquantized Qwen3.5 architecture") + model = Model(ModelConfig.from_dict(config)) + expected = {k: v.shape for k, v in tree_flatten(model.parameters())} + index = {"metadata": {"total_size": 0}, "weight_map": {}} + source_index = json.loads((base / "model.safetensors.index.json").read_text()) + for filename in sorted(set(source_index["weight_map"].values())): + arrays = mx.load(str(base / filename)) + for key, value in arrays.items(): + sensitive = value.ndim == 1 and ("norm" in key or key.endswith(("A_log", "dt_bias"))) + arrays[key] = value.astype(mx.float32 if sensitive else mx.bfloat16) + arrays = model.sanitize(arrays) + arrays = model.vision_tower.sanitize(arrays) + for key, value in arrays.items(): + if key not in expected or value.shape != expected[key]: + raise ValueError("Converted tensor shape mismatch: " + key) + if key in index["weight_map"]: + raise ValueError("Duplicate converted tensor: " + key) + index["weight_map"][key] = filename + index["metadata"]["total_size"] += value.nbytes + mx.save_safetensors(str(output / filename), arrays, metadata={"format": "mlx"}) + del arrays + mx.clear_cache() + print("Converted " + filename, flush=True) + if set(index["weight_map"]) != set(expected): + raise ValueError("Converted backbone is missing required parameters") + (output / "model.safetensors.index.json").write_text(json.dumps(index, indent=2, sort_keys=True)) + for file in base.iterdir(): + if file.is_file() and file.name != "model.safetensors.index.json" and file.suffix != ".safetensors": + shutil.copy2(file, output / file.name) diff --git a/mlx/src/solomon_mlx_hub/__init__.py b/mlx/src/solomon_mlx_hub/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ca81dd73db32b4262a415f57e36321a182737677 --- /dev/null +++ b/mlx/src/solomon_mlx_hub/__init__.py @@ -0,0 +1,5 @@ +"""Prepare the adapter-only Hub release for the frozen Solomon MLX runtime.""" + +from .prepare import load, prepare_from_hub, prepare_from_snapshot, verify_prepared + +__all__ = ["load", "prepare_from_hub", "prepare_from_snapshot", "verify_prepared"] diff --git a/mlx/src/solomon_mlx_hub/__main__.py b/mlx/src/solomon_mlx_hub/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..f6fd1d713443a9c9d8e578d89f5a2358a0f54a90 --- /dev/null +++ b/mlx/src/solomon_mlx_hub/__main__.py @@ -0,0 +1,50 @@ +"""Adapter-only Hub setup and verification command.""" + +import argparse +import json + +from .prepare import prepare_from_hub, prepare_from_snapshot, verify_prepared + + +def main(): + parser = argparse.ArgumentParser(description="Prepare full BF16 Solomon from the adapter-only release") + commands = parser.add_subparsers(dest="command", required=True) + prepare = commands.add_parser("prepare") + prepare.add_argument("--revision", required=True, help="Full Solomon repository commit SHA") + prepare.add_argument("--output", default="models/quality") + prepare.add_argument("--base", help="Existing original Qwen snapshot, already downloaded") + prepare.add_argument("--snapshot", help="Existing adapter-only Solomon snapshot; requires --base") + prepare.add_argument("--cache-dir") + prepare.add_argument("--device", choices=("cpu", "gpu"), default="cpu") + verify = commands.add_parser("verify") + verify.add_argument("directory") + args = parser.parse_args() + if args.command == "verify": + binding = verify_prepared(args.directory) + print(json.dumps({"verified": True, "files": len(binding["files"]), "profile": binding["profile"]})) + elif args.snapshot: + if not args.base: + parser.error("--snapshot requires --base for offline preparation") + print( + prepare_from_snapshot( + args.snapshot, + args.output, + revision=args.revision, + base_dir=args.base, + device=args.device, + ) + ) + else: + print( + prepare_from_hub( + args.output, + revision=args.revision, + base_dir=args.base, + cache_dir=args.cache_dir, + device=args.device, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/mlx/src/solomon_mlx_hub/data/base-manifest.json b/mlx/src/solomon_mlx_hub/data/base-manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..cb70489241c40cb013c06491d28acd16013ba7e8 --- /dev/null +++ b/mlx/src/solomon_mlx_hub/data/base-manifest.json @@ -0,0 +1,197 @@ +{ + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "files": [ + { + "name": ".gitattributes", + "size": 1570, + "blob_id": "52373fe24473b1aa44333d318f578ae6bf04b49b", + "sha256": null + }, + { + "name": "LICENSE", + "size": 11544, + "blob_id": "f938136e3adacfd92be087f6e113b5d6d97f678f", + "sha256": null + }, + { + "name": "README.md", + "size": 65012, + "blob_id": "bc8aa0e396cd029c21cd773cca21830d0ded28ec", + "sha256": null + }, + { + "name": "chat_template.jinja", + "size": 8952, + "blob_id": "c0c686f9c38d70d179fb7b5f5aa7530bc913dda3", + "sha256": null + }, + { + "name": "config.json", + "size": 4312, + "blob_id": "706cebd746c4b6f2b1d1f892630867acfdfd3df8", + "sha256": null + }, + { + "name": "crc32.txt", + "size": 238, + "blob_id": "6de5ee6a0c6596744baee911af0a5cdcb8d99a1e", + "sha256": null + }, + { + "name": "generation_config.json", + "size": 202, + "blob_id": "023756cfadf88e5bf69eefeee3e172f38c448d64", + "sha256": null + }, + { + "name": "merges.txt", + "size": 3353259, + "blob_id": "a494e019ca1502219fd0128658b979e5f05ae8e8", + "sha256": null + }, + { + "name": "model-00001-of-00018.safetensors", + "size": 3966730552, + "blob_id": "79b73cdd69afeca931d181f5d1e21ad2d73bb7b7", + "sha256": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c" + }, + { + "name": "model-00002-of-00018.safetensors", + "size": 3043080328, + "blob_id": "81cf43545884a227a01351942dfbc72577812316", + "sha256": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb" + }, + { + "name": "model-00003-of-00018.safetensors", + "size": 2542796952, + "blob_id": "a2da5e6cd6307a6cae13d04820ab0d31b47b48bd", + "sha256": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968" + }, + { + "name": "model-00004-of-00018.safetensors", + "size": 3988973152, + "blob_id": "175ddab3f2488ad181f73d789d2d778cf6d4fca6", + "sha256": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a" + }, + { + "name": "model-00005-of-00018.safetensors", + "size": 2099339864, + "blob_id": "64890208fdd563c3aac0c2f55f69948bcaa1a492", + "sha256": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701" + }, + { + "name": "model-00006-of-00018.safetensors", + "size": 3979553696, + "blob_id": "3ae29b86e4c773babfe17124dbd10d54d37819ec", + "sha256": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0" + }, + { + "name": "model-00007-of-00018.safetensors", + "size": 2108759344, + "blob_id": "b7e9c0f038884839f3dad38fef7e75ee3362c24f", + "sha256": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892" + }, + { + "name": "model-00008-of-00018.safetensors", + "size": 3979553696, + "blob_id": "0b58cbc52714e1c683711be87e7c2e33fb10e7a0", + "sha256": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79" + }, + { + "name": "model-00009-of-00018.safetensors", + "size": 2108759344, + "blob_id": "4fc0c6b28004d809225d0e115f30e9e11c2345dc", + "sha256": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8" + }, + { + "name": "model-00010-of-00018.safetensors", + "size": 3979553696, + "blob_id": "9ec693a17c788084116c51b4f34632670df77612", + "sha256": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9" + }, + { + "name": "model-00011-of-00018.safetensors", + "size": 2108759344, + "blob_id": "3c63baaed16d833432a919647af53ac76bb6b007", + "sha256": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8" + }, + { + "name": "model-00012-of-00018.safetensors", + "size": 3979553696, + "blob_id": "8568b440a4666ed1b4cda0b5d21496378304f326", + "sha256": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55" + }, + { + "name": "model-00013-of-00018.safetensors", + "size": 2108759344, + "blob_id": "724083995a2257194321a3d00b36fd7d4bb7348d", + "sha256": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a" + }, + { + "name": "model-00014-of-00018.safetensors", + "size": 3979553696, + "blob_id": "a60b80b6510d7938d951fbddc76aaadd121c6268", + "sha256": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b" + }, + { + "name": "model-00015-of-00018.safetensors", + "size": 2108759344, + "blob_id": "7d4c34f3d716e1a47acff2f576b65b41a3ec4f3f", + "sha256": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22" + }, + { + "name": "model-00016-of-00018.safetensors", + "size": 3979564040, + "blob_id": "c67e3e42c9715298fa7f68c6b378bfb1d74290d0", + "sha256": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe" + }, + { + "name": "model-00017-of-00018.safetensors", + "size": 2108759344, + "blob_id": "23dd899179729a4370c655e2c2241cfdbe4ef4ba", + "sha256": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8" + }, + { + "name": "model-00018-of-00018.safetensors", + "size": 3392197344, + "blob_id": "a92b4073fc6a58ffc9334ac63360fc8d9cc50d33", + "sha256": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06" + }, + { + "name": "model.safetensors.index.json", + "size": 112216, + "blob_id": "da35e3c564457dface7d138f0b6cac284ff8958c", + "sha256": null + }, + { + "name": "preprocessor_config.json", + "size": 390, + "blob_id": "2ea84a437d448ff71b08df68fdd949d5cc4ebb64", + "sha256": null + }, + { + "name": "tokenizer.json", + "size": 12809320, + "blob_id": "9328ce9c41e80f6dd7bc2c66d8ae1fc93bf87440", + "sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3" + }, + { + "name": "tokenizer_config.json", + "size": 17928, + "blob_id": "5de744b3fca2129d7186979ae47c06be33903243", + "sha256": null + }, + { + "name": "video_preprocessor_config.json", + "size": 385, + "blob_id": "3ba673a5ad7d4d13f54155ecd38b2a94a6dac8fe", + "sha256": null + }, + { + "name": "vocab.json", + "size": 6722759, + "blob_id": "0aa0ce0658d60ac4a5d609f4eadb0e8e43514176", + "sha256": null + } + ] +} \ No newline at end of file diff --git a/mlx/src/solomon_mlx_hub/data/release.json b/mlx/src/solomon_mlx_hub/data/release.json new file mode 100644 index 0000000000000000000000000000000000000000..a463c63a0af7d19ad2aed50db7145452ff38f2c3 --- /dev/null +++ b/mlx/src/solomon_mlx_hub/data/release.json @@ -0,0 +1,105 @@ +{ + "base_manifest_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d", + "base_repo_id": "Qwen/Qwen3.8-27B", + "binding_contract": { + "adapter_dtype": "float32", + "adapter_placement": "question", + "adapter_scale": 2.0, + "base_hashes": { + ".gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930", + "LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641", + "chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab", + "crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83", + "generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e", + "merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d", + "model-00001-of-00018.safetensors": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c", + "model-00002-of-00018.safetensors": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb", + "model-00003-of-00018.safetensors": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968", + "model-00004-of-00018.safetensors": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a", + "model-00005-of-00018.safetensors": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701", + "model-00006-of-00018.safetensors": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0", + "model-00007-of-00018.safetensors": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892", + "model-00008-of-00018.safetensors": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79", + "model-00009-of-00018.safetensors": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8", + "model-00010-of-00018.safetensors": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9", + "model-00011-of-00018.safetensors": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8", + "model-00012-of-00018.safetensors": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55", + "model-00013-of-00018.safetensors": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a", + "model-00014-of-00018.safetensors": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b", + "model-00015-of-00018.safetensors": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22", + "model-00016-of-00018.safetensors": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe", + "model-00017-of-00018.safetensors": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8", + "model-00018-of-00018.safetensors": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06", + "model.safetensors.index.json": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df", + "preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516", + "tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27", + "video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13", + "vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003" + }, + "base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset", + "dependencies": { + "mlx": "0.32.2", + "mlx-vlm": "0.7.1", + "numpy": "2.5.3", + "transformers": "5.17.0" + }, + "dtype": "bfloat16", + "head_dtype": "float32", + "profile": "quality", + "quantization": null, + "recurrent_state_dtype": "float32", + "schema": "solomon-mlx-binding-v1", + "sensitive_parameters": "FP32 normalization weights, A_log and dt_bias", + "solomon_revision": "5c0a4a82ddaeca6da2e3013f7045a8196c86957d" + }, + "converter_code_sha256": "810ec77e6a1e4972a942bc5bcea502183b97e048c9b317a6cc38c2cab45fcaeb", + "reference_files": { + "LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457", + "MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7", + "NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1", + "adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca", + "backbone/.gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930", + "backbone/LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a", + "backbone/README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641", + "backbone/chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "backbone/config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab", + "backbone/crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83", + "backbone/generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e", + "backbone/merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d", + "backbone/model-00001-of-00018.safetensors": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a", + "backbone/model-00002-of-00018.safetensors": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78", + "backbone/model-00003-of-00018.safetensors": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528", + "backbone/model-00004-of-00018.safetensors": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da", + "backbone/model-00005-of-00018.safetensors": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4", + "backbone/model-00006-of-00018.safetensors": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24", + "backbone/model-00007-of-00018.safetensors": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb", + "backbone/model-00008-of-00018.safetensors": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81", + "backbone/model-00009-of-00018.safetensors": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53", + "backbone/model-00010-of-00018.safetensors": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89", + "backbone/model-00011-of-00018.safetensors": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f", + "backbone/model-00012-of-00018.safetensors": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e", + "backbone/model-00013-of-00018.safetensors": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4", + "backbone/model-00014-of-00018.safetensors": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c", + "backbone/model-00015-of-00018.safetensors": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a", + "backbone/model-00016-of-00018.safetensors": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a", + "backbone/model-00017-of-00018.safetensors": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2", + "backbone/model-00018-of-00018.safetensors": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9", + "backbone/model.safetensors.index.json": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f", + "backbone/preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516", + "backbone/tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3", + "backbone/tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27", + "backbone/video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13", + "backbone/vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003", + "heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f" + }, + "repo_id": "DoccyHealth/Solomon", + "schema": "solomon-mlx-hub-release-v1", + "source_hashes": { + "adapter/adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca", + "adapter/heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f" + } +} diff --git a/mlx/src/solomon_mlx_hub/prepare.py b/mlx/src/solomon_mlx_hub/prepare.py new file mode 100644 index 0000000000000000000000000000000000000000..ebc7928cc2db30e8d8679ae0a238a2eef0ed2aa4 --- /dev/null +++ b/mlx/src/solomon_mlx_hub/prepare.py @@ -0,0 +1,212 @@ +"""Verified, atomic local assembly without redistributing the Qwen backbone. + +This separate package leaves the historical converter and running inference code +unchanged. Conversion uses the original FP32 normalization-offset treatment. +""" + +import copy +import fcntl +import json +import re +import shutil +import tempfile +from contextlib import contextmanager +from importlib.metadata import version +from pathlib import Path, PurePosixPath + +from huggingface_hub import snapshot_download + +from solomon_mlx.artifacts import code_identity, sha256 +from solomon_mlx.prepare import convert_bf16, verify_base + +DATA = Path(__file__).parent / "data" +RELEASE = json.loads((DATA / "release.json").read_text()) +MANIFEST = DATA / "base-manifest.json" +SOURCE_PATTERNS = [ + "adapter/**", + "mlx/bf16/LICENSE", + "mlx/bf16/NOTICE", + "mlx/bf16/MODIFICATIONS.md", +] + + +def _revision(revision): + if not isinstance(revision, str) or not re.fullmatch(r"[0-9a-f]{40}", revision): + raise ValueError("Pin the Solomon repository to a full 40-character commit SHA") + return revision + + +def _relative(name): + path = PurePosixPath(name) + if not name or path.is_absolute() or ".." in path.parts or "\\" in name: + raise ValueError("Unsafe artifact path: " + name) + return path + + +def _check_converter(): + if code_identity() != RELEASE["converter_code_sha256"]: + raise ValueError("Converter source identity mismatch; install the pinned source distribution") + for package, expected in RELEASE["binding_contract"]["dependencies"].items(): + if version(package) != expected: + raise ValueError(f"Converter requires {package}=={expected}") + if sha256(MANIFEST) != RELEASE["base_manifest_sha256"]: + raise ValueError("Pinned base manifest checksum mismatch") + + +def verify_prepared(directory): + """Verify an existing local conversion without allocating model tensors. + + Historical conversions remain usable. Their bindings and byte checksums are + preserved; a new conversion always receives its own output checksums. + """ + root = Path(directory).resolve() + binding = json.loads((root / "binding.json").read_text()) + for key, expected in RELEASE["binding_contract"].items(): + if binding.get(key) != expected: + raise ValueError("Local binding mismatch: " + key) + for key, expected in RELEASE["source_hashes"].items(): + if binding.get("source_hashes", {}).get(key) != expected: + raise ValueError("Local source identity mismatch: " + key) + files = binding.get("files", {}) + if set(files) != set(RELEASE["reference_files"]): + raise ValueError("Local conversion has missing or unexpected bound artifacts") + for name, expected in files.items(): + path = root / _relative(name) + if not path.resolve().is_relative_to(root) or sha256(path) != expected: + raise ValueError("Local artifact checksum mismatch: " + name) + # Safetensors serialization can differ between CPU and Metal. Metadata, + # processor files, trained adapter and heads must remain byte-identical. + if ( + not (name.startswith("backbone/") and name.endswith(".safetensors")) + and expected != RELEASE["reference_files"][name] + ): + raise ValueError("Pinned artifact identity mismatch: " + name) + return binding + + +@contextmanager +def _output_lock(output): + output.parent.mkdir(parents=True, exist_ok=True) + # Keep the lock inode after release so concurrent waiters cannot bypass it. + with (output.parent / f".{output.name}.prepare.lock").open("a") as handle: + try: + fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError as exc: + raise RuntimeError("Another process is preparing this output") from exc + try: + yield + finally: + fcntl.flock(handle, fcntl.LOCK_UN) + + +def _assemble(snapshot, output, *, revision, base_dir, device): + source = Path(snapshot) + for name, expected in RELEASE["source_hashes"].items(): + if sha256(source / _relative(name)) != expected: + raise ValueError("Solomon source checksum mismatch: " + name) + for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"): + if sha256(source / "mlx/bf16" / name) != RELEASE["reference_files"][name]: + raise ValueError("Solomon license/provenance checksum mismatch: " + name) + + manifest = json.loads(MANIFEST.read_text()) + if base_dir is None: + base_dir = output.parent / ".solomon-sources" / ("base-" + manifest["revision"]) + snapshot_download( + RELEASE["base_repo_id"], + revision=manifest["revision"], + local_dir=base_dir, + allow_patterns=[row["name"] for row in manifest["files"]], + max_workers=8, + ) + base_dir = Path(base_dir) + base_hashes = verify_base(base_dir, manifest) + if base_hashes != RELEASE["binding_contract"]["base_hashes"]: + raise ValueError("Base input identity differs from the original conversion") + + temporary = Path(tempfile.mkdtemp(prefix=f".{output.name}.", dir=output.parent)) + try: + import mlx.core as mx + + # Device selection is scoped to this conversion; CPU conversion also + # works on Linux. Inference still requires Apple Silicon and Metal. + with mx.stream(mx.cpu if device == "cpu" else mx.gpu): + convert_bf16(base_dir, temporary / "backbone") + for name in RELEASE["source_hashes"]: + shutil.copy2(source / name, temporary / Path(name).name) + for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"): + shutil.copy2(source / "mlx/bf16" / name, temporary / name) + binding = copy.deepcopy(RELEASE["binding_contract"]) + binding["source_hashes"] = RELEASE["source_hashes"] + binding["files"] = { + str(p.relative_to(temporary)): sha256(p) for p in sorted(temporary.rglob("*")) if p.is_file() + } + binding["hub_preparation"] = { + "repo_id": RELEASE["repo_id"], + "revision": revision, + "converter_code_sha256": RELEASE["converter_code_sha256"], + "loader_code_sha256": sha256(__file__), + "device": device, + "base_manifest_sha256": RELEASE["base_manifest_sha256"], + "calibration": "none; CUDA parity only", + } + (temporary / "binding.json").write_text(json.dumps(binding, indent=2, sort_keys=True) + "\n") + verify_prepared(temporary) + temporary.rename(output) + finally: + if temporary.exists(): + shutil.rmtree(temporary) + return output + + +def prepare_from_snapshot(snapshot, output, *, revision, base_dir, device="cpu"): + """Offline preparation from an adapter-only snapshot and original Qwen files.""" + _revision(revision) + if base_dir is None: + raise ValueError("Offline preparation requires the original Qwen base directory") + if device not in ("cpu", "gpu"): + raise ValueError("Conversion device must be cpu or gpu") + _check_converter() + output = Path(output).absolute() + with _output_lock(output): + if output.exists(): + verify_prepared(output) + return output + return _assemble(Path(snapshot), output, revision=revision, base_dir=base_dir, device=device) + + +def prepare_from_hub(output, *, revision, base_dir=None, cache_dir=None, device="cpu"): + """Download root adapters and pinned base inputs; return a verified local model. + + Existing valid outputs are reused without network traffic or rewriting their + runtime identity. Invalid outputs fail closed and are never overwritten. + """ + _revision(revision) + if device not in ("cpu", "gpu"): + raise ValueError("Conversion device must be cpu or gpu") + _check_converter() + output = Path(output).absolute() + with _output_lock(output): + if output.exists(): + verify_prepared(output) + return output + snapshot = snapshot_download( + RELEASE["repo_id"], + revision=revision, + allow_patterns=SOURCE_PATTERNS, + cache_dir=cache_dir, + ) + return _assemble(Path(snapshot), output, revision=revision, base_dir=base_dir, device=device) + + +def load(output, *, revision, base_dir=None, cache_dir=None, device="cpu", **model_options): + """Prepare the adapter-only release, then load the standard Solomon API.""" + from solomon_mlx import Solomon + + directory = prepare_from_hub( + output, + revision=revision, + base_dir=base_dir, + cache_dir=cache_dir, + device=device, + ) + return Solomon.load(directory, **model_options) diff --git a/mlx/tests/test_artifacts_and_evaluation.py b/mlx/tests/test_artifacts_and_evaluation.py new file mode 100644 index 0000000000000000000000000000000000000000..9e0c14362a5272cbcb0255e003cb5700afd6ae46 --- /dev/null +++ b/mlx/tests/test_artifacts_and_evaluation.py @@ -0,0 +1,77 @@ +import hashlib +import json + +import numpy as np +import pytest + +from solomon_mlx.artifacts import BASE_REVISION, verify_release +from solomon_mlx.evaluation import compare_rows, unit +from solomon_mlx.prepare import verify_base + + +def test_corrupt_source_and_base_fail_closed(tmp_path): + path = tmp_path / "input" + path.write_bytes(b"original") + sha = hashlib.sha256(b"original").hexdigest() + (tmp_path / "MANIFEST.json").write_text( + json.dumps({"files": [{"destination": "input", "staged_bytes": 8, "staged_sha256": sha}]}) + ) + assert verify_release(tmp_path)["input"] == sha + manifest = {"revision": BASE_REVISION, "files": [{"name": "input", "size": 8, "sha256": sha}]} + assert verify_base(tmp_path, manifest)["input"] == sha + path.write_bytes(b"corrupt!") + with pytest.raises(ValueError): + verify_release(tmp_path) + with pytest.raises(ValueError): + verify_base(tmp_path, manifest) + + +def test_reserved_choice_exclusion_and_whole_question_accuracy(): + rows = [ + { + "id": "a", + "task": "entity", + "n": 4, + "head_key": "entity/state4", + "question_id": "q", + "gold": 0, + "letter_logits": [5, 0, 0, 0], + }, + { + "id": "b", + "task": "entity", + "n": 4, + "head_key": "entity/state4", + "question_id": "q", + "gold": 3, + "letter_logits": [0, 0, 0, 5], + }, + ] + other = [{**rows[0]}, {**rows[1], "letter_logits": [5, 0, 0, 0]}] + report = compare_rows(rows, other) + assert report["unit_decision_agreement"] == 0.5 + assert report["mlx_whole_question_accuracy"] == 1 + assert report["cuda_whole_question_accuracy"] == 0 + assert not report["quality_gate_passed"] + assert unit( + {"task": "single", "n": 4, "head_key": "single/choiceR", "gold": 3, "letter_logits": [1, 2, 3, 4]} + ) == (None, None) + + +def test_norm_offset_preserves_bf16_source_values_in_fp32(): + from types import SimpleNamespace + + import mlx.core as mx + from mlx_vlm.models.qwen3_5.qwen3_5 import Model + + # Sanitization requires config only for tied embeddings and image channels. + config = SimpleNamespace( + text_config=SimpleNamespace(tie_word_embeddings=False), vision_config=SimpleNamespace(in_channels=3) + ) + original = mx.array([0.02, -0.04], dtype=mx.bfloat16) + converted = Model.sanitize( + SimpleNamespace(config=config), {"model.language_model.norm.weight": original.astype(mx.float32)} + ) + out = converted["language_model.model.norm.weight"] + assert out.dtype == mx.float32 + np.testing.assert_array_equal(np.asarray(out), np.asarray(original.astype(mx.float32)) + 1) diff --git a/mlx/tests/test_budget.py b/mlx/tests/test_budget.py new file mode 100644 index 0000000000000000000000000000000000000000..27fb7d4b649e8cfa6e63fa8c28b11d7a71e13a3a --- /dev/null +++ b/mlx/tests/test_budget.py @@ -0,0 +1,24 @@ +import json + +import pytest + +from solomon_mlx.budget import Budget + + +def test_budget_reserves_before_dispatch_and_exhausts(tmp_path): + path = tmp_path / "budget.json" + budget = Budget(path, 20) + with budget.reserve(15, "one"): + assert json.loads(path.read_text())["entries"][0]["status"] == "reserved" + with pytest.raises(RuntimeError), budget.reserve(1, "concurrent"): + pass + with pytest.raises(RuntimeError), budget.reserve(6, "too expensive"): + pass + + +def test_uncertain_dispatch_blocks_followups(tmp_path): + budget = Budget(tmp_path / "budget.json") + with pytest.raises(TimeoutError), budget.reserve(20, "uncertain"): + raise TimeoutError() + with pytest.raises(RuntimeError), budget.reserve(20, "retry"): + pass diff --git a/mlx/tests/test_contract.py b/mlx/tests/test_contract.py new file mode 100644 index 0000000000000000000000000000000000000000..06a9028fd0ac5111e1312cb9fcd1df0e1129493e --- /dev/null +++ b/mlx/tests/test_contract.py @@ -0,0 +1,96 @@ +import math +import threading + +import pytest + +from solomon_mlx._vendor.contract import parse_questions +from solomon_mlx._vendor.semantics import p_yes +from solomon_mlx.api import Solomon, branches, distributions, ordering_score + + +def test_collapse_before_temperature(): + p = p_yes([0, 0, 0, 0], 2) + assert p == pytest.approx(1 / (1 + math.sqrt(3))) + assert p != pytest.approx(0.25) + + +def test_routing_and_candidate_order(): + specs = parse_questions( + { + "single": {"type": "choice", "instructions": "Who?", "options": ["A", "B"]}, + "ordered": {"type": "score", "instructions": "Level?", "levels": ["low", "high"]}, + "entity": {"instructions": "Is {candidate} certified?", "candidates": ["Z", "X"]}, + } + ) + assert branches(specs[0])[0][1:] == (4, "single/choiceR") + assert branches(specs[1])[0][1:] == (2, "ordered/choiceS") + assert "Is Z certified?" in branches(specs[2])[0][0] + assert distributions(specs[0], [{"letter_logits": [0, 0, 100, 100]}], 1) == [[0.5, 0.5]] + + +def test_ordering_score_product(): + assert ordering_score([[0.8, 0.2], [0.1, 0.9]]) == pytest.approx(0.72) + with pytest.raises(ValueError): + ordering_score([[0.8, 0.3]]) + + +class FakeEngine: + def __init__(self): + self.identity = {"fingerprint": "test-only"} + self.lock = threading.RLock() + self.prefills = [] + + def prefill(self, parts): + self.prefills.append(parts) + return {"parts": parts, "prefix_ids": [1, 2]} + + def ask(self, state, block, width, head, **kwargs): + return { + "letter_logits": [2.0] + [0.0] * (width - 1), + "branch_tokens": 3, + "prompt_tokens": 5, + "head_key": head, + } + + +def test_state_ownership_close_and_replay(tmp_path): + model = Solomon(FakeEngine()) + other = Solomon(FakeEngine()) + state = model.prefill("A fact.") + recipe = tmp_path / "state.json" + state.save(recipe) + with pytest.raises(ValueError): + other.decide(state=state, questions={"a": "Fact?"}) + state.close() + with pytest.raises(ValueError): + model.decide(state=state, questions={"a": "Fact?"}) + with model.replay(recipe) as restored: + assert restored.prefix_tokens == 2 + recipe.write_text(recipe.read_text().replace("A fact.", "Bad fact.")) + with pytest.raises(ValueError): + model.replay(recipe) + + +def test_evidence_budget_stops_fresh_calls(): + engine = FakeEngine() + model = Solomon(engine) + with model.prefill("Alice is certified.\nBob is not certified.") as state: + result = model.decide( + state=state, questions={"a": "Is Alice certified?"}, evidence="removal", evidence_max_calls=0 + ) + assert result["answers"]["a"]["evidence_status"] == "budget_exhausted" + assert len(engine.prefills) == 1 + + +def test_evidence_spans_and_fresh_verification(): + engine = FakeEngine() + model = Solomon(engine) + text = "Alice is certified.\nBob is not certified." + with model.prefill(text) as state: + out = model.decide(state=state, questions={"a": "Is Alice certified?"}, evidence="removal")[ + "answers" + ]["a"] + assert len(engine.prefills) == 3 + for span in out["evidence"]: + assert text[span["start"] : span["end"]] == span["text"] + assert out["evidence_detail"]["verification"] == "fresh_source_reencoding" diff --git a/mlx/tests/test_hub.py b/mlx/tests/test_hub.py new file mode 100644 index 0000000000000000000000000000000000000000..3010d52ab09e88a915603c71eda1c0c67bd94064 --- /dev/null +++ b/mlx/tests/test_hub.py @@ -0,0 +1,173 @@ +"""Adapter-only downloads, immutable reuse, integrity failures and atomic assembly.""" + +import copy +import fcntl +import json +from pathlib import Path + +import pytest + +import solomon_mlx_hub.prepare as hub +from solomon_mlx.artifacts import sha256 + +REVISION = "a" * 40 + + +@pytest.fixture +def release(tmp_path, monkeypatch): + source, base = tmp_path / "source", tmp_path / "base" + (source / "adapter").mkdir(parents=True) + (source / "mlx/bf16").mkdir(parents=True) + base.mkdir() + payloads = {"adapter.safetensors": b"trained adapter", "heads.npz": b"trained heads"} + for name, value in payloads.items(): + (source / "adapter" / name).write_bytes(value) + for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"): + (source / "mlx/bf16" / name).write_text(name) + (base / "config.json").write_text('{"model_type":"qwen3_5"}') + manifest = tmp_path / "base-manifest.json" + manifest.write_text( + json.dumps( + { + "revision": hub.RELEASE["binding_contract"]["base_revision"], + "files": [ + { + "name": "config.json", + "size": (base / "config.json").stat().st_size, + "sha256": sha256(base / "config.json"), + } + ], + } + ) + ) + reference = { + **{name: sha256(source / "adapter" / name) for name in payloads}, + **{name: sha256(source / "mlx/bf16" / name) for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md")}, + "backbone/config.json": sha256(base / "config.json"), + "backbone/model.safetensors": "historical-serialization-hash", + } + spec = copy.deepcopy(hub.RELEASE) + spec["reference_files"] = reference + spec["base_manifest_sha256"] = sha256(manifest) + spec["source_hashes"] = {"adapter/" + name: reference[name] for name in payloads} + spec["binding_contract"]["base_hashes"] = {"config.json": sha256(base / "config.json")} + monkeypatch.setattr(hub, "RELEASE", spec) + monkeypatch.setattr(hub, "MANIFEST", manifest) + calls = [] + + def convert(root, output): + assert Path(root) == base + calls.append("convert") + output.mkdir() + (output / "config.json").write_bytes((base / "config.json").read_bytes()) + (output / "model.safetensors").write_bytes(b"new serialization") + + monkeypatch.setattr(hub, "convert_bf16", convert) + return source, base, tmp_path / "model", calls + + +def test_adapter_only_hub_then_verified_offline_reuse(release, monkeypatch): + source, base, output, calls = release + + def download(repo_id, **kwargs): + assert repo_id == "DoccyHealth/Solomon" + assert kwargs["revision"] == REVISION + assert "adapter/**" in kwargs["allow_patterns"] + assert not any( + "backbone" in pattern or pattern == "mlx/bf16/**" for pattern in kwargs["allow_patterns"] + ) + calls.append("download") + return str(source) + + monkeypatch.setattr(hub, "snapshot_download", download) + assert hub.prepare_from_hub(output, revision=REVISION, base_dir=base) == output + binding = hub.verify_prepared(output) + assert binding["hub_preparation"]["revision"] == REVISION + assert binding["adapter_scale"] == 2.0 + assert binding["files"]["backbone/model.safetensors"] != "historical-serialization-hash" + original_binding = (output / "binding.json").read_bytes() + assert hub.prepare_from_hub(output, revision=REVISION) == output + assert calls == ["download", "convert"] + assert (output / "binding.json").read_bytes() == original_binding + (output / "adapter.safetensors").write_bytes(b"corrupt") + with pytest.raises(ValueError, match="checksum mismatch"): + hub.prepare_from_hub(output, revision=REVISION) + assert calls == ["download", "convert"] + + +def test_base_download_pinned_and_verified_before_conversion(release, monkeypatch): + source, _base, output, calls = release + + def download(repo_id, **kwargs): + calls.append(repo_id) + if repo_id == "DoccyHealth/Solomon": + return source + assert repo_id == "Qwen/Qwen3.8-27B" + assert kwargs["revision"] == hub.RELEASE["binding_contract"]["base_revision"] + target = Path(kwargs["local_dir"]) + target.mkdir(parents=True) + (target / "config.json").write_bytes(b"corrupt base") + + monkeypatch.setattr(hub, "snapshot_download", download) + with pytest.raises(ValueError, match="Base size mismatch"): + hub.prepare_from_hub(output, revision=REVISION) + assert calls == ["DoccyHealth/Solomon", "Qwen/Qwen3.8-27B"] + assert not output.exists() + + +def test_bad_source_stops_before_base_or_conversion(release): + source, base, output, calls = release + (source / "adapter/heads.npz").write_bytes(b"corrupt") + with pytest.raises(ValueError, match="source checksum mismatch"): + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + assert not calls and not output.exists() + + +def test_converter_failure_is_atomic_and_retryable(release, monkeypatch): + source, base, output, calls = release + convert = hub.convert_bf16 + + def fail(*args): + args[1].mkdir() + (args[1] / "partial").write_bytes(b"partial output") + raise RuntimeError("conversion failed") + + monkeypatch.setattr(hub, "convert_bf16", fail) + with pytest.raises(RuntimeError, match="conversion failed"): + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + assert not output.exists() + assert list(output.parent.glob(".model.*")) == [output.parent / ".model.prepare.lock"] + monkeypatch.setattr(hub, "convert_bf16", convert) + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + assert calls == ["convert"] + + +def test_mutable_revision_and_converter_drift_fail_closed(release, monkeypatch): + source, base, output, calls = release + for revision in (None, "main", "abc123", "A" * 40): + with pytest.raises(ValueError, match="commit SHA"): + hub.prepare_from_hub(output, revision=revision) + monkeypatch.setattr(hub, "code_identity", lambda: "changed source") + with pytest.raises(ValueError, match="Converter source identity"): + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + assert not calls + + +def test_bound_paths_cannot_escape_output(release): + source, base, output, _calls = release + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + external = output.parent / "external" + target = output / "backbone/model.safetensors" + target.rename(external) + target.symlink_to(external) + with pytest.raises(ValueError, match="checksum mismatch"): + hub.verify_prepared(output) + + +def test_simultaneous_preparations_fail_without_mutation(release): + source, base, output, calls = release + with (output.parent / ".model.prepare.lock").open("a") as handle: + fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) + with pytest.raises(RuntimeError, match="Another process"): + hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base) + assert not output.exists() and not calls diff --git a/mlx/tests/test_metal_primitives.py b/mlx/tests/test_metal_primitives.py new file mode 100644 index 0000000000000000000000000000000000000000..67d3d6ffd9a96b4b0215ef52fd70951af7eb06ef --- /dev/null +++ b/mlx/tests/test_metal_primitives.py @@ -0,0 +1,40 @@ +import mlx.core as mx +import numpy as np +from mlx import nn +from mlx_vlm.models.cache import ArraysCache, KVCache + +from solomon_mlx.engine import SwitchLoRA, fork_cache + + +def test_question_cache_isolation(): + kv = KVCache() + kv.update_and_fetch(mx.ones((1, 2, 7, 4)), mx.ones((1, 2, 7, 4))) + recurrent = ArraysCache(2) + recurrent.cache = [mx.ones((1, 3, 4)), mx.ones((1, 2, 4, 4), dtype=mx.float32)] + mx.eval(kv.state, recurrent.state) + branch = fork_cache([kv, recurrent]) + branch[0].update_and_fetch(mx.full((1, 2, 2, 4), 9), mx.full((1, 2, 2, 4), 8)) + branch[1].cache[0][:] = 12 + branch[1].cache[1][:] = 15 + mx.eval(branch[0].state, branch[1].state) + assert kv.offset == 7 + np.testing.assert_array_equal(np.asarray(kv.keys[:, :, :7]), 1) + for array in recurrent.cache: + np.testing.assert_array_equal(np.asarray(array), 1) + # A second independent question starts at the original prefix. + assert fork_cache([kv, recurrent])[0].offset == 7 + + +def test_lora_boundary_multiplier_and_instance_isolation(): + context = {"start": None} + base = nn.Linear(2, 3, bias=False) + base.weight = mx.zeros((3, 2), dtype=mx.bfloat16) + layer = SwitchLoRA(base, mx.ones((2, 1)), mx.ones((1, 3)), context) + x = mx.ones((1, 4, 2), dtype=mx.bfloat16) + np.testing.assert_array_equal(np.asarray(layer(x).astype(mx.float32)), 0) + context["start"] = 2 + out = np.asarray(layer(x).astype(mx.float32)) + np.testing.assert_array_equal(out[:, :2], 0) + np.testing.assert_array_equal(out[:, 2:], 4) + other = SwitchLoRA(base, mx.ones((2, 1)), mx.ones((1, 3)), {"start": None}) + np.testing.assert_array_equal(np.asarray(other(x).astype(mx.float32)), 0) diff --git a/mlx/tests/test_parity.py b/mlx/tests/test_parity.py new file mode 100644 index 0000000000000000000000000000000000000000..bc0e034f0f17ed6e2dd3402248b9ca149be6e505 --- /dev/null +++ b/mlx/tests/test_parity.py @@ -0,0 +1,24 @@ +"""Keep the user-selected parity workflow free of calibration jobs.""" + +import importlib.util +from pathlib import Path + + +def test_parity_keeps_branches_whose_gold_label_is_reserved(): + script = Path(__file__).resolve().parents[1] / "scripts/check_cuda_parity.py" + spec = importlib.util.spec_from_file_location("parity_checker", script) + checker = importlib.util.module_from_spec(spec) + spec.loader.exec_module(checker) + row = { + "task": "single", + "n": 4, + "head_key": "single/choiceR", + "gold": 3, + "letter_logits": [2, 0, 100, 100], + } + probabilities = checker.decision_probabilities(row, 1.0) + assert len(probabilities) == 2 + assert probabilities.argmax() == 0 + assert probabilities[0] > 0.88 + + diff --git a/mlx/tests/test_processor.py b/mlx/tests/test_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..3aca3f5f4cd6b1ff2346e06c2beae7bfdc9c87e0 --- /dev/null +++ b/mlx/tests/test_processor.py @@ -0,0 +1,21 @@ +"""Pinned tokenizer/image sidecars can be tested before large weights arrive.""" + +from pathlib import Path + +import numpy as np +import pytest +from mlx_vlm.models.qwen3_vl.processing_qwen3_vl import Qwen3VLProcessor +from PIL import Image + + +@pytest.mark.skipif( + not Path("snapshots/base/tokenizer.json").exists(), reason="Pinned tokenizer not downloaded" +) +def test_torch_free_processor_and_image_token_count(): + processor = Qwen3VLProcessor.from_pretrained("snapshots/base", trust_remote_code=False) + image = Image.new("RGB", (512, 512), "white") + out = processor.image_processor(images=[image]) + assert out["image_grid_thw"].tolist() == [[1, 32, 32]] + assert np.asarray(out["pixel_values"]).shape == (1024, 1536) + assert np.isfinite(np.asarray(out["pixel_values"])).all() + assert processor.image_processor.max_pixels == 16777216 diff --git a/mlx/tests/test_tiny_decoder.py b/mlx/tests/test_tiny_decoder.py new file mode 100644 index 0000000000000000000000000000000000000000..960d0b19d4705c4830797a277b71cedfe4841fa5 --- /dev/null +++ b/mlx/tests/test_tiny_decoder.py @@ -0,0 +1,63 @@ +"""Exercise actual attention, convolution and recurrent layers on Metal.""" + +import mlx.core as mx +import numpy as np +from mlx_vlm.models.qwen3_5.config import TextConfig +from mlx_vlm.models.qwen3_5.language import LanguageModel + +from solomon_mlx.engine import Engine, fork_cache + + +def test_chunked_hybrid_decoder_and_cache_isolation(): + mx.random.seed(8) + config = TextConfig( + model_type="qwen3_5_text", + hidden_size=128, + intermediate_size=192, + linear_num_value_heads=2, + linear_num_key_heads=2, + linear_key_head_dim=32, + linear_value_head_dim=32, + linear_conv_kernel_dim=4, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=2, + head_dim=32, + rms_norm_eps=1e-6, + vocab_size=256, + max_position_embeddings=512, + rope_parameters={ + "type": "default", + "mrope_section": [2, 1, 1], + "rope_theta": 100000, + "partial_rotary_factor": 0.25, + }, + ) + engine = Engine.__new__(Engine) + engine.lm = LanguageModel(config) + engine.lm.eval() + + class ForbiddenVocabularyHead: + def __call__(self, *args, **kwargs): + raise AssertionError("Vocabulary projection must never run") + + engine.lm.lm_head = ForbiddenVocabularyHead() + engine.chunk_size = 16 + engine.context = {"start": None} + tokens = list(range(1, 74)) + p = 51 + prefix = engine.lm.make_cache() + engine.forward(tokens[:p], engine.positions(0, p), prefix) + before = [tuple(None if x is None else np.asarray(x).copy() for x in c.state) for c in prefix] + a, _ = engine.forward(tokens[p:], engine.positions(p, len(tokens) - p), fork_cache(prefix)) + b, _ = engine.forward(tokens, engine.positions(0, len(tokens)), engine.lm.make_cache()) + np.testing.assert_allclose(np.asarray(a), np.asarray(b), atol=5e-4, rtol=5e-4) + again, _ = engine.forward(tokens[p:], engine.positions(p, len(tokens) - p), fork_cache(prefix)) + np.testing.assert_array_equal(np.asarray(a), np.asarray(again)) + for original, saved in zip(prefix, before): + for x, y in zip(original.state, saved): + if y is not None: + np.testing.assert_array_equal(np.asarray(x), y) + for c in prefix: + if hasattr(c, "cache"): + assert c.cache[1].dtype == mx.float32 diff --git a/mlx/tests/test_tiny_library.py b/mlx/tests/test_tiny_library.py new file mode 100644 index 0000000000000000000000000000000000000000..675b65cc3bc1efd77ee84b731887eb1e4641fe48 --- /dev/null +++ b/mlx/tests/test_tiny_library.py @@ -0,0 +1,172 @@ +"""End-to-end BF16 library wiring on a synthetic hybrid vision model. + +This is an integration test, not evidence about Solomon's trained accuracy. +Only checkpoint hashes and the tokenizer are replaced in the test fixture. +""" + +import json +from dataclasses import asdict +from types import SimpleNamespace +from typing import ClassVar + +import mlx.core as mx +import numpy as np +from mlx.utils import tree_map_with_path +from mlx_vlm.models.qwen3_5.config import ModelConfig, TextConfig, VisionConfig +from mlx_vlm.models.qwen3_5.qwen3_5 import Model +from mlx_vlm.models.qwen3_vl.processing_qwen3_vl import Qwen3VLImageProcessor, Qwen3VLProcessor +from mlx_vlm.utils import save_weights +from PIL import Image + +import solomon_mlx.engine as engine_module +from solomon_mlx import Solomon +from solomon_mlx.artifacts import BASE_REVISION, SOLOMON_REVISION, sha256 +from solomon_mlx.prepare import convert_bf16 + + +class Tokenizer: + markers: ClassVar[dict] = {"<|vision_start|>": 251, "<|vision_end|>": 252, "<|image_pad|>": 253} + + def apply_chat_template(self, messages, **kwargs): + return "\n".join(m["content"] for m in messages) + "\nAssistant:" + + def encode(self, text, **kwargs): + for key, value in self.markers.items(): + text = text.replace(key, chr(value)) + return list(text.encode("latin1")) + + def convert_tokens_to_ids(self, text): + return self.markers[text] + + +def test_full_library_bf16_text_image_repeated_question(tmp_path, monkeypatch): + mx.random.seed(14) + text = TextConfig( + model_type="qwen3_5_text", + hidden_size=5120, + intermediate_size=64, + linear_num_value_heads=2, + linear_num_key_heads=2, + linear_key_head_dim=32, + linear_value_head_dim=32, + linear_conv_kernel_dim=4, + num_hidden_layers=4, + num_attention_heads=4, + num_key_value_heads=2, + head_dim=32, + rms_norm_eps=1e-6, + vocab_size=256, + max_position_embeddings=4096, + rope_parameters={ + "type": "default", + "mrope_section": [2, 1, 1], + "rope_theta": 100000, + "partial_rotary_factor": 0.25, + }, + ) + vision = VisionConfig( + depth=1, + hidden_size=64, + intermediate_size=64, + out_hidden_size=5120, + num_heads=4, + patch_size=16, + spatial_patch_size=16, + num_position_embeddings=64, + deepstack_visual_indexes=[], + ) + config = ModelConfig( + text_config=text, + vision_config=vision, + model_type="qwen3_5", + image_token_id=253, + video_token_id=254, + vision_start_token_id=251, + vision_end_token_id=252, + vocab_size=256, + ) + model = Model(config) + model.update( + tree_map_with_path( + lambda k, v: v.astype(mx.float32 if v.ndim == 1 else mx.bfloat16), model.parameters() + ) + ) + backbone = tmp_path / "backbone" + save_weights(backbone, model, donate_weights=True) + (backbone / "config.json").write_text(json.dumps(asdict(config))) + source = tmp_path / "original" + backbone.rename(source) + convert_bf16(source, backbone) + # Cloud conversion uses the CPU backend. Its output must match the Mac's + # default backend before either is used by the same Metal runtime. + cpu_backbone = tmp_path / "cpu-backbone" + with mx.stream(mx.cpu): + convert_bf16(source, cpu_backbone) + for shard in backbone.glob("*.safetensors"): + gpu_arrays = mx.load(str(shard)) + cpu_arrays = mx.load(str(cpu_backbone / shard.name)) + assert gpu_arrays.keys() == cpu_arrays.keys() + for key in gpu_arrays: + assert gpu_arrays[key].dtype == cpu_arrays[key].dtype + assert mx.array_equal(gpu_arrays[key], cpu_arrays[key]).item(), key + adapter = tmp_path / "adapter.safetensors" + mx.save_safetensors( + str(adapter), + { + "model.layers.0.mlp.gate_proj.lora_a": mx.full((5120, 64), 0.001, mx.float32), + "model.layers.0.mlp.gate_proj.lora_b": mx.full((64, 64), 0.001, mx.float32), + }, + ) + keys = [ + "boolean/state4", + "entity/state4", + "multilabel/state4", + "ordered/threshold4", + "single/choiceR", + "single/choiceS", + "single/sufficiency3", + "ordered/choiceR", + "ordered/choiceS", + "ordered/sufficiency3", + ] + rng = np.random.default_rng(14) + heads = {} + for k in keys: + heads[k + "/weight"] = rng.normal(0, 0.01, (10, 5120)).astype(np.float32) + heads[k + "/bias"] = np.zeros(10, np.float32) + np.savez(tmp_path / "heads.npz", **heads) + binding = { + "profile": "quality", + "schema": "solomon-mlx-binding-v1", + "base_revision": BASE_REVISION, + "solomon_revision": SOLOMON_REVISION, + "dtype": "bfloat16", + "files": {str(p.relative_to(tmp_path)): sha256(p) for p in tmp_path.rglob("*") if p.is_file()}, + } + (tmp_path / "binding.json").write_text(json.dumps(binding)) + monkeypatch.setattr(engine_module, "ADAPTER_SHA", sha256(adapter)) + monkeypatch.setattr(engine_module, "HEADS_SHA", sha256(tmp_path / "heads.npz")) + processor = SimpleNamespace( + tokenizer=Tokenizer(), image_processor=Qwen3VLImageProcessor(min_pixels=1024, max_pixels=16384) + ) + monkeypatch.setattr(Qwen3VLProcessor, "from_pretrained", lambda *a, **kw: processor) + port = Solomon.load(tmp_path, chunk_size=128) + page = tmp_path / "page.png" + Image.new("RGB", (128, 128), "white").save(page) + questions = { + "a": "Is Alice certified?", + "b": {"type": "choice", "instructions": "Who?", "options": ["Alice", "Bob"]}, + "c": {"type": "score", "instructions": "Level?", "levels": ["low", "high"]}, + "d": {"instructions": "Is {candidate} certified?", "candidates": ["Alice", "Bob"]}, + "e": {"instructions": "Which labels apply?", "candidates": ["certified", "unavailable"]}, + } + with port.prefill([{"text": "Alice is certified."}, {"image": str(page)}, {"text": "End."}]) as state: + assert state._data["counts"] == [16] + first = port.decide(state=state, questions=questions, evidence="none", diagnostics=True) + repeated = port.decide( + state=state, questions={"a": questions["a"]}, evidence="support", diagnostics=True + ) + assert first["answers"]["a"]["noul"] == repeated["answers"]["a"]["noul"] + assert repeated["answers"]["a"]["evidence_status"] == "unsupported_page_selector" + assert set(first["answers"]) == set(questions) + assert port.engine.context["start"] is None diff --git a/mlx/uv.lock b/mlx/uv.lock new file mode 100644 index 0000000000000000000000000000000000000000..337e430f723f378697d2143dba98dfde29069ffb --- /dev/null +++ b/mlx/uv.lock @@ -0,0 +1,1711 @@ +version = 1 +revision = 3 +requires-python = ">=3.12, <3.14" + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.98" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/4d/8164128d7a1d408f15849ada4632da37b13a5953c9bb3993998eb5d43ca1/boto3-1.43.98.tar.gz", hash = "sha256:1ec732e023fb29c12dc8520f925b5bbbed29eeb36b5764b5a5c26052d7c721f7", size = 112658, upload-time = "2026-09-18T20:10:05.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/46/d29efd46ecac6637065a39418c5fb95500d70ec63e76065b5a72c936172f/boto3-1.43.98-py3-none-any.whl", hash = "sha256:7454f666a9e852a56db0a7fa23be33a899f64e27828930eac43c3edba7b34e17", size = 140033, upload-time = "2026-09-18T20:10:04.513Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.98" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/5e/8d655aeaae195b9856a554e724322092b4cc1b8bb58a29167bea80bf800c/botocore-1.43.98.tar.gz", hash = "sha256:6135dd639ea6d1b3b49381bc253c8a139d61f7d44cb5f7dae8e7cd1791758572", size = 16127750, upload-time = "2026-09-18T20:10:00.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/c7617e99c3247f545233ab2181840d142c5f72034e22864172866fb1369a/botocore-1.43.98-py3-none-any.whl", hash = "sha256:84b35b10402c2fc0c265f634fbf86336eecc6489ee23f55074b329924b2cfd6f", size = 15817751, upload-time = "2026-09-18T20:09:57.566Z" }, +] + +[[package]] +name = "cbor2" +version = "6.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/14/b02446bacfe44351b1689c04937ade007588f44570431880a6937e525e6c/cbor2-6.1.4.tar.gz", hash = "sha256:01ecc79a28f33d17331943ce508fc1e21f4b06553c73f874f4c77120d72b2ef9", size = 90840, upload-time = "2026-08-01T20:41:39.797Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/76/fb64293c19cafb860060310c57b768fd9cfb7cf592449660b756538cc116/cbor2-6.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1fc15061553e4494dc10883237501e3402c645fe509248dd698e1faf2460d68b", size = 404608, upload-time = "2026-08-01T20:40:50.219Z" }, + { url = "https://files.pythonhosted.org/packages/96/ac/f58b3bafce7c86ada2ad8eaf189453136d2cf5bae526ea0540e1b9bc9d06/cbor2-6.1.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d9ada5a6ccfbb8ea7a3aa2aeb028421b52d8e0cd9323f0a2aeaa9c09d25fbce2", size = 449851, upload-time = "2026-08-01T20:40:51.725Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a5/10c6c126d59b07f2bd005094dd12a20afa46146f7e2673ed6f61a57641a7/cbor2-6.1.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:310f3dfb296ba48fe9b63c5cf26e691e3548a1eae6901d2f0c18e941d151f220", size = 461193, upload-time = "2026-08-01T20:40:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/15/e4/4445e6237088d1cca3b8536daeb90d6b4e23776de5609c9fa46773874757/cbor2-6.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e6c76004d674ad1c620660cb0bc5a8a0b72a5d8c7b70926d8e09e6d7e87332f", size = 516937, upload-time = "2026-08-01T20:40:54.952Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/9c0959510f7a402e5995c81ccfd82cb9f314140dc0cce88c12836e5b93f1/cbor2-6.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32a4663425fbca4a4a7aa918eb5789d844c406439e58424cf34511f79f559242", size = 529229, upload-time = "2026-08-01T20:40:56.365Z" }, + { url = "https://files.pythonhosted.org/packages/91/8e/6811e4ee84203ac657f6f461a37c7c9ba0287bde80eb83c7971e9b3fe156/cbor2-6.1.4-cp312-cp312-win32.whl", hash = "sha256:2310f07db3f9ba26f2a623774ff9f3dc7185af54f732ea119785a6b1bf7e1e7e", size = 278810, upload-time = "2026-08-01T20:40:57.76Z" }, + { url = "https://files.pythonhosted.org/packages/da/27/87440788fc0d9513534c3c699238e2a9ca6010f8cb72e9c203b7af20a9f6/cbor2-6.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:cc8cd300e236e9797b2e1ce306109dc481fcccf78bfa2682bf36d99e6eab1ec6", size = 299971, upload-time = "2026-08-01T20:40:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/23/f9/77981e6e63092de19d7306a09a12b0eb3fd2907dc22c10dd5d389eb27faf/cbor2-6.1.4-cp312-cp312-win_arm64.whl", hash = "sha256:553a46bda7d09552631a714e22b91e6ff2c867ecd91511596ce290d8879b8d5b", size = 290662, upload-time = "2026-08-01T20:41:00.89Z" }, + { url = "https://files.pythonhosted.org/packages/0d/17/0b20c88e76942ede86c98cdce138681690f95908c540c264fff847729cd4/cbor2-6.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c48a7c938fc5fa5300ff82b5df09068dcb4838685ae8556b5ee8279d74f97ab4", size = 403677, upload-time = "2026-08-01T20:41:02.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/3d/93eed770864540c5c9ea0841008208e9db686b7335f42520705b7d6dc6b2/cbor2-6.1.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4bd29f21529e279d50fc14f1a811f7b05b4d8e66a7969163cce98983b6817245", size = 449762, upload-time = "2026-08-01T20:41:04.094Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/69e4d37f00319b3d37322355aedc83154b4d8b75dc9e9789c06e1fbd8a92/cbor2-6.1.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ae16d64b1f7b620c1af748e7b6947e20069ef80eee56871c5fbb84cc635905", size = 460420, upload-time = "2026-08-01T20:41:05.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/26/2cfdd5ee826205a88a826bb38b7a572c676ec3efa29574be5cdbd04b4859/cbor2-6.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:69978901302ecbc8cda57b520487c5c5240ed217de783eb7728fceb258311d76", size = 516490, upload-time = "2026-08-01T20:41:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d687cd1c2c9f9a986e8552ad1fdbd22411cc86389b5705dba6ec6f7e3226/cbor2-6.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad4efa23fee6447e56a269191044e06eb39e809458bcd674e164fe9445feafd0", size = 528810, upload-time = "2026-08-01T20:41:09.144Z" }, + { url = "https://files.pythonhosted.org/packages/40/08/88cecf20b8825bdd991c47b317415c08ef9e7d5f05a1def9acd346edabde/cbor2-6.1.4-cp313-cp313-win32.whl", hash = "sha256:d2560c2ba6a95904ba2a0ca257af878c4344409d9b46d8e646d8ebb617b1e0dd", size = 278058, upload-time = "2026-08-01T20:41:10.48Z" }, + { url = "https://files.pythonhosted.org/packages/0e/67/ba140234a6415c16dcfbe0585ce12f905157b70e9cb1bb63a2b6d5721e70/cbor2-6.1.4-cp313-cp313-win_amd64.whl", hash = "sha256:c08b9c7d2ea013e24a0cb819b872b0119dde404f64a1182c0b24095b7bba781f", size = 299315, upload-time = "2026-08-01T20:41:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7f/35d53ff4252a5a85656480d3a81d5a5af823979ccd0c5cac95196a7548a6/cbor2-6.1.4-cp313-cp313-win_arm64.whl", hash = "sha256:598710183daae69cbdeb177a870ec64aa601de8138a61491fd256826d15a860f", size = 289976, upload-time = "2026-08-01T20:41:13.63Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { 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" }, +] + +[[package]] +name = "fastapi" +version = "0.141.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, +] + +[[package]] +name = "filelock" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/38/88cd6eda96c40594a1e3da7d8b40f04bc40ace5a6aef9ac5cb407540f173/filelock-4.0.1.tar.gz", hash = "sha256:fdefc3f3e87716d855ae2b732c1cfd521dd99799ef2b4d00e8c0d4dcdc7cc94b", size = 238888, upload-time = "2026-09-19T01:08:15.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/33/af0635ab07fe83b1788a1dbe370ff3e226062495a998335cb18a1cac81aa/filelock-4.0.1-py3-none-any.whl", hash = "sha256:481a321a27bef441e23c53371c6abc8d7d16e26b97090074ba44f7538a3fd55a", size = 106219, upload-time = "2026-09-19T01:08:14.49Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "grpclib" +version = "0.4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h2" }, + { name = "multidict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/28/5a2c299ec82a876a252c5919aa895a6f1d1d35c96417c5ce4a4660dc3a80/grpclib-0.4.9.tar.gz", hash = "sha256:cc589c330fa81004c6400a52a566407574498cb5b055fa927013361e21466c46", size = 84798, upload-time = "2025-12-14T22:23:14.349Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/90/b0cbbd9efcc82816c58f31a34963071aa19fb792a212a5d9caf8e0fc3097/grpclib-0.4.9-py3-none-any.whl", hash = "sha256:7762ec1c8ed94dfad597475152dd35cbd11aecaaca2f243e29702435ca24cf0e", size = 77063, upload-time = "2025-12-14T22:23:13.224Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { 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" }, +] + +[[package]] +name = "h2" +version = "4.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/85/7c366e69d84c17bb778fe41419e1fbcce3033d5b7ce29bbffff0a98b859f/h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516", size = 2157281, upload-time = "2026-08-03T11:45:09.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/22/e85faf23bd72a92d1921e37d674ca56eb298a3c8be31fdecef0ff2b3aaac/h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6", size = 62636, upload-time = "2026-08-03T11:44:59.164Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "hpack" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/0f/e83fdd856da8fca26bf78d71709ebd120432a0ce535e72b9597cab1eb5bf/huggingface_hub-1.32.0.tar.gz", hash = "sha256:ed70a45498abe86039df7c2f4e5f7575de524be908d3840e8f828d5525eafd6a", size = 1038662, upload-time = "2026-09-17T10:27:48.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/cf/d98dd561d6d0d7b7d7a64d1563f8aaaa7c235daee41c1c9bcc3da62420ed/huggingface_hub-1.32.0-py3-none-any.whl", hash = "sha256:b0c7c80561969d9cdacdd55fce67ba9584cca0b9d4ea80957a3a5c1445fac5c8", size = 842906, upload-time = "2026-09-17T10:27:46.102Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { 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" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "llguidance" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/27/972de1ba4c93072fce816b967972e1d18bc48b04b145b5c77b5bd1dc9662/llguidance-1.8.0.tar.gz", hash = "sha256:18d1579eabb040e65c870d50c6df19a7bef140c5260d12ad35b7f0dc446312e0", size = 1181409, upload-time = "2026-08-11T00:06:56.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/7b/cfd52b0b836c07c6b7d83c227f4fb82e85677fbc1087c021ac8d8a69b05d/llguidance-1.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:020b4ec2254a20555e69095c7d488907f17d7e65ab5a840b267530f2cb369f70", size = 3241118, upload-time = "2026-08-11T00:06:38.82Z" }, + { url = "https://files.pythonhosted.org/packages/78/98/568132150d3f6f09200f389191f4f863b405f1c46429dd8d27a120c1518d/llguidance-1.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bb9a89e8cdd7c8b5cf4f84e45b04177e79acdcc4d5116fbc775e511f7314df44", size = 3168313, upload-time = "2026-08-11T00:06:40.597Z" }, + { url = "https://files.pythonhosted.org/packages/32/aa/db393283f934e8a22e2b8f750924eb17ba6d320f160174fcdee0223a107c/llguidance-1.8.0-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:6ae4343bd40b88d1dd824a17edcee11b5e5a000b16b6fedb9fcf7f58d019177c", size = 2923367, upload-time = "2026-08-11T00:06:42.555Z" }, + { url = "https://files.pythonhosted.org/packages/d1/97/208f22f995c64fd3232db41d0004aece45fecaf267b97cf5dd5943ef967e/llguidance-1.8.0-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:39668c11396896e5f05f59b70c81e4afd060b3408f02c8518b7a6943bfbb8a5d", size = 3093405, upload-time = "2026-08-11T00:06:44.502Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c5/5cce5e3880f42b522d0d231244bda3604083b5deae2d4f582bfd14f48208/llguidance-1.8.0-cp39-abi3-manylinux_2_34_i686.whl", hash = "sha256:79b0576991b8fc7534456b65c41d43c3183c8ca974a17799359af969c1489c07", size = 3338590, upload-time = "2026-08-11T00:06:46.19Z" }, + { url = "https://files.pythonhosted.org/packages/75/16/55527f58cbb2ea1aaff147b49362b331c502c25687d0934020b49f52d749/llguidance-1.8.0-cp39-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:0eb7be70bf822e54cd4021bb200cfe2b86e4f3379d251067dc9f7da327f3ceab", size = 3635965, upload-time = "2026-08-11T00:06:48.348Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fe/c714cef8a976fcb8c68f366cb26120fad0bf6eff5beddbc227d8f3128ea9/llguidance-1.8.0-cp39-abi3-win32.whl", hash = "sha256:b5e866d8a896e255f30ec952f5280c61a3d6f391a9dce575ce976dd58f0b7000", size = 2627800, upload-time = "2026-08-11T00:06:50.066Z" }, + { url = "https://files.pythonhosted.org/packages/57/43/82ae24a80a6157b05cc988f3d15ffa188fe806d09c840c1351bb8d38c57a/llguidance-1.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:a8837ac2b3bf4c46e1b6363012a22de043b7f8ef013b04d8689d471eb573b766", size = 2903412, upload-time = "2026-08-11T00:06:52.269Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/c34a975b37f686dfa27fa0798bca3432d8d93d2427db6674969b39903791/llguidance-1.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:6bf3953d06e7f5e24bd02fa6a89a5b2b88f7e811c0fa0228487d8267ef7cec54", size = 2754606, upload-time = "2026-08-11T00:06:54.574Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "miniaudio" +version = "1.71" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/d5/e5439dc08561f73656bfeb3340fc64ab63163e101426593d8fb9a025ff1e/miniaudio-1.71.tar.gz", hash = "sha256:ff51e2887bb673e2e757752b586b3dc924d59aa5fbcae9bbc45f4a111bd3262b", size = 1116480, upload-time = "2026-04-29T21:20:38.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/d3/71124f5abbcdcae62e040f58d3dca3bd3d90fd01faa7bb276b112387b47a/miniaudio-1.71-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:62db602651bc20a2698f36a0d356d7217ed6f4f917550c7ffb3705c8e8be90cf", size = 377186, upload-time = "2026-04-29T21:20:16.713Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ac/30a324f758bed1b193e017ec25183cfb10a79e549656331f5d068a2d343a/miniaudio-1.71-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fc1a4f084cc1b4b25c567d22f54d1e46bfa505c17ed777c8b198e5c53d0f785", size = 351485, upload-time = "2026-04-29T21:20:17.761Z" }, + { url = "https://files.pythonhosted.org/packages/fa/62/ae884a9d3b2ebec9c2ed1db857593e74bff45b70c4ab17fccc11db31cbeb/miniaudio-1.71-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19be6f0a1e601c2237433e579734cfaf6469191b224c20c9e5f73c32ef9ee2b9", size = 643556, upload-time = "2026-04-29T21:20:18.87Z" }, + { url = "https://files.pythonhosted.org/packages/a5/39/84fc665e2ea8f9f1301b6370226e3a24f08d5ad5d97143b69b4ae7ac260a/miniaudio-1.71-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e6287f15caa808a88aad0700a182bec1ff6d98769717425adf9ebf41259d1936", size = 645176, upload-time = "2026-04-29T21:20:20.194Z" }, + { url = "https://files.pythonhosted.org/packages/81/b8/37d9f67d4511da29bdb82b6c73a3ef6f4ebf2fbd30f9524f1e4a84d6f033/miniaudio-1.71-cp312-cp312-win32.whl", hash = "sha256:ab100e5240b104b5326e4ec1be07b6ae461f7d3d4d7a694857fd2f0493d210f9", size = 235095, upload-time = "2026-04-29T21:20:21.653Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/c1a19e6800e725b6e2b4576407620a798d69e3528ebdea9aea84d69d7088/miniaudio-1.71-cp312-cp312-win_amd64.whl", hash = "sha256:f4a44b70b66628b0c307e40ae0ae857695978cae18462179b806d8edc807d416", size = 274252, upload-time = "2026-04-29T21:20:22.824Z" }, + { url = "https://files.pythonhosted.org/packages/3a/85/44545f767ec21142ffed5f9108406d11dc8a19aafed9bd57621a0892bb60/miniaudio-1.71-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:61b86f26d653040db32d9d15b05446321dd10e45beba25b44f841e26935213d5", size = 377184, upload-time = "2026-04-29T21:20:24.109Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d1/071a560000c8ce903dc919968ecce40fbe7a73213ac399051b887184f8a3/miniaudio-1.71-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d9dc15eff711bcfc62a9d05e0c78e4bc34821a455595e049629f2fea7491a523", size = 351488, upload-time = "2026-04-29T21:20:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/46/24/5873a569451cae5686fb656ebd78ffe0b5eebe48ca21ef61e227d237d20a/miniaudio-1.71-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12bc33e7e61072b4b541c14e10ef76119d5643e6bbb98e2dec0c0738889438fb", size = 643552, upload-time = "2026-04-29T21:20:26.211Z" }, + { url = "https://files.pythonhosted.org/packages/90/9b/25785525e6b5ff9afd7f4c4279215dc09c3317ea4d837275b1ae17912b36/miniaudio-1.71-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70fa2ea5353e6919aca59b8c5768144af009d18c3bca251749d66fb497424563", size = 645171, upload-time = "2026-04-29T21:20:27.494Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6d/cbfd55fdc40256231f7b0c861e2bf79cc289bfbbe5e15869317944e6d673/miniaudio-1.71-cp313-cp313-win32.whl", hash = "sha256:1bf93aeede652926f27f430f0fd69ef0cf8a949c07b537d6a2f295602c747037", size = 235088, upload-time = "2026-04-29T21:20:29.031Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8d/d5059c04b247b1079c0e48914a9ec20352910a3b9373060fb258dfd194ab/miniaudio-1.71-cp313-cp313-win_amd64.whl", hash = "sha256:4c849ccb1349f7b3553a77a66fe7e972315185f5c4c44a0bbda7ebcdd224db37", size = 274247, upload-time = "2026-04-29T21:20:29.96Z" }, +] + +[[package]] +name = "mlx" +version = "0.32.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mlx-metal", marker = "sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/f7/5664e13821491559c953494e6705c818203d6880d713a0c22e12bead9564/mlx-0.32.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:77217798a2b036bae9f213b851d4cde4581893787c9964458b7d471f86036bd6", size = 619370, upload-time = "2026-08-25T10:31:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/c821eed5b7c13b42c114aec54c757545d6eeecce25d3212ea7d18dd79cfd/mlx-0.32.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf63fd5c32258ab07523b06401c20ee2280bf56e26e991d05bf6fd8a4d42d1f6", size = 619376, upload-time = "2026-08-25T10:31:45.647Z" }, + { url = "https://files.pythonhosted.org/packages/0b/eb/af6b1a8b45f24d22e4735c52e0696c66233bd477e1b9e9dbbef225bdf88b/mlx-0.32.2-cp312-cp312-macosx_26_0_arm64.whl", hash = "sha256:68560fd648c5bb900aa6f6765cd74c5a8abaf092d97d73584a57b7545966c227", size = 619488, upload-time = "2026-08-25T10:31:47.485Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/1acf68f9d27d24bf1abeb0b2cff93155e93af81af2f3f08206f8a32bbf86/mlx-0.32.2-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:48e8738b078eeb7bfde74931fc02d8aa9c32f05d3c0dbf97b5c3cbe0470fbddf", size = 680208, upload-time = "2026-08-25T10:31:48.93Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3b/d8cf1d13dd2d434095818cc6d28211e00b891a4a58a07b5ad5f0529a8162/mlx-0.32.2-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:8d270ade1e48e006383a6b5f33a3a4ec5c265389299a60a60bf55165af1770d9", size = 743277, upload-time = "2026-08-25T10:31:50.351Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/f191c8f7ed84d50370fa8e4d390ed747dddd148c03756c8b7d4f7feb9084/mlx-0.32.2-cp312-cp312-win_amd64.whl", hash = "sha256:6c615ad1c6877d7d38affe8526446955cd48d6796e871150f59145cf0d1a265d", size = 620981, upload-time = "2026-08-25T10:31:51.749Z" }, + { url = "https://files.pythonhosted.org/packages/6d/54/4b8b690a5322a92271214e49480d083f253ab48c81825ade92473828846f/mlx-0.32.2-cp312-cp312-win_arm64.whl", hash = "sha256:65beb9ce75153072808ef18913ecca3d929b87be5d7c1f57d9df391c04b6f957", size = 607910, upload-time = "2026-08-25T10:31:53.269Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d7/0f9717acf577621ff0899f311eaa16abbfd2ee5a5c2156313f49d080cb5f/mlx-0.32.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:65d3d29b66045ed8dd2d8e437c8770de325843c364f7b7c38cd8ae90a7eec854", size = 619420, upload-time = "2026-08-25T10:31:55.042Z" }, + { url = "https://files.pythonhosted.org/packages/2d/96/d333b83d04c31046d49d9e1f915c9da76eb264b8e4205bd36d428d09421f/mlx-0.32.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:daebf84dfb857d70e87b1b98189a057e691e99a4a2b9f6f64cadebad917f8964", size = 619418, upload-time = "2026-08-25T10:31:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/38/96/9db8e27f384d83d3816ec7392c7a2b375979c14509d7f345d1fa3d05cac8/mlx-0.32.2-cp313-cp313-macosx_26_0_arm64.whl", hash = "sha256:df8c75e509de868fca148dfeb38d92ce956eed386569c87caeb72bd16d2d6962", size = 619417, upload-time = "2026-08-25T10:31:58.569Z" }, + { url = "https://files.pythonhosted.org/packages/f7/24/0af50b41582fc138024d77a7b94bf8b4950fc2cad034dc55e3ce2d9c979a/mlx-0.32.2-cp313-cp313-manylinux_2_35_aarch64.whl", hash = "sha256:45857fadb381fea3db57d9681b04c987813fa883f3182ec9fb05bc56f468978a", size = 679976, upload-time = "2026-08-25T10:32:00.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/3c6c37077949963e67594b05fc817c1a40d7b6b236cb9e90b6811963859c/mlx-0.32.2-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:583111ec13fedf63ddbfefda77dabd57168978f06474f5e5d2637180d154fcc4", size = 743284, upload-time = "2026-08-25T10:32:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/cb8e0bb55a318e0745a2534f478d313b84cd8efc305c5f9e1aec633458f5/mlx-0.32.2-cp313-cp313-win_amd64.whl", hash = "sha256:50ced716f4ab860cbda9d65adf74877923fe26036c4e28d8f572ecec57621cfc", size = 621080, upload-time = "2026-08-25T10:32:03.911Z" }, + { url = "https://files.pythonhosted.org/packages/88/6a/f2107ecd8d4322a81b49b9607a2f5005e9cbcf9f6972d74ddf61c3c61cca/mlx-0.32.2-cp313-cp313-win_arm64.whl", hash = "sha256:fe813a4dee2daa6d5ac496ef25117034728c77fe26f713983a3b406eb6f4e8f0", size = 607858, upload-time = "2026-08-25T10:32:05.715Z" }, + { url = "https://files.pythonhosted.org/packages/bf/86/c574112fc8193c6e032c4be3a394361dfa283a57e276a76538fa8da341df/mlx-0.32.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc5eb3cc30d4285f2734c368442f717599e97294663441cb42959e3b856d6898", size = 645041, upload-time = "2026-08-25T10:32:07.218Z" }, + { url = "https://files.pythonhosted.org/packages/10/44/d50dd35a356323105f2d25aa5b463869e6dfe6e75cb2422e738472b6127a/mlx-0.32.2-cp313-cp313t-macosx_15_0_arm64.whl", hash = "sha256:c95a384de1a0c0ba18425344cad8ac87180e3c5c1921a42e2621475be5966bcf", size = 645042, upload-time = "2026-08-25T10:32:08.879Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a3/9feb2ed4bb045cb077beed6fec309154e3a4ec71ce6a714e8487d133e355/mlx-0.32.2-cp313-cp313t-macosx_26_0_arm64.whl", hash = "sha256:bc69bd1062b97028ae6b79522ed0bd635a2b133fe3c398c03b66b060c20d9240", size = 644908, upload-time = "2026-08-25T10:32:10.487Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bb/b351f3b2bad9c8e67a612f9bba4e9fadb0f85db3cfa7bff45d05edc0cf9c/mlx-0.32.2-cp313-cp313t-manylinux_2_35_aarch64.whl", hash = "sha256:9d21abe340403b6bf445e8494590a7341b655739088c92f178d7bd4241ba0110", size = 695201, upload-time = "2026-08-25T10:32:12.222Z" }, + { url = "https://files.pythonhosted.org/packages/09/b3/a32e9312380efa2a0821e6abcf905b603942146cf4fb2e883dc130acbe42/mlx-0.32.2-cp313-cp313t-manylinux_2_35_x86_64.whl", hash = "sha256:f6071e4973927966c12b3894deb75f4fcfc33300e0705312a80fdbbaa92a9c4f", size = 751133, upload-time = "2026-08-25T10:32:13.818Z" }, + { url = "https://files.pythonhosted.org/packages/40/8a/c480999b9efaba3adaafa43abfebec40d3a1c9aa808091d3fe0700935c4c/mlx-0.32.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f77e47e6c1e176a61ec1bb1f9e74d874482eea6eb86ffb162a1f3e789c806453", size = 652120, upload-time = "2026-08-25T10:32:15.576Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f8/291c026e2eea82cdcfc86ed71da6757ab82e419b3aa30603da4bd2be93b9/mlx-0.32.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c7670ffb854c11e6776349a3797fa076d1a2a00290ebe2e9cfed8b60ca4a5db0", size = 625676, upload-time = "2026-08-25T10:32:16.982Z" }, +] + +[package.optional-dependencies] +cpu = [ + { name = "mlx-cpu" }, +] + +[[package]] +name = "mlx-audio" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "miniaudio" }, + { name = "mlx" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "sounddevice" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/71/07fc039c603d8fb6531a2eca75b18b150c4ee9f3780c03ba20538a576f32/mlx_audio-0.5.4.tar.gz", hash = "sha256:3e1895860d9a636360a9377b5651239f5197e649d56fe25f1382cac6f2ed1e55", size = 1725509, upload-time = "2026-09-14T15:26:06.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/99/08dbae6b2e7771b3f9cf55c8c160d104f8c41c6c71dffebe785ed1344248/mlx_audio-0.5.4-py3-none-any.whl", hash = "sha256:d350ecc43a65b94b1578381370be64229948f79aef9ff599d20adb65e38351b3", size = 2077152, upload-time = "2026-09-14T15:26:04.598Z" }, +] + +[[package]] +name = "mlx-cpu" +version = "0.32.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/fa/4a37da905a3ea27bd5c041a26d8a5a58a35fb540b4c4de5a69c5e0173e29/mlx_cpu-0.32.2-py3-none-manylinux_2_35_aarch64.whl", hash = "sha256:d0f94625588b51a878786dd51cec5617894ce586cf3c94645ce1b64e24c27c0b", size = 8822016, upload-time = "2026-08-25T10:31:05.912Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/2305637be752d0827594de266eaee3d282f780ba7877c0e15881cdf9da5e/mlx_cpu-0.32.2-py3-none-manylinux_2_35_x86_64.whl", hash = "sha256:fc5d31b90fc4f457b2f9614ed45651a567d22a6a11a03d114a90a4e10bdd8878", size = 10388414, upload-time = "2026-08-25T10:31:08.632Z" }, +] + +[[package]] +name = "mlx-metal" +version = "0.32.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ab/ba1952908c5d2a5070cf1cfbfea0161c4751ea62299e2776819810917483/mlx_metal-0.32.2-py3-none-macosx_14_0_arm64.whl", hash = "sha256:3825fff379dbc107dd3413e564a06caeaa24819910ec49c0439e454c06a1b9b8", size = 42516623, upload-time = "2026-08-25T10:31:18.396Z" }, + { url = "https://files.pythonhosted.org/packages/79/ec/34f37376e26d537fadffb99af3a760d6545e37f5e1a30a552baadf237fc5/mlx_metal-0.32.2-py3-none-macosx_15_0_arm64.whl", hash = "sha256:55a369250d220b2cf10213a87a2ac1b1a420608c5b35b1df4e7147ac8e32f121", size = 42512323, upload-time = "2026-08-25T10:31:22.673Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cd/4e50bf325100e7165e13d025f264362bf0009196269f9eaf87f2c6e738a2/mlx_metal-0.32.2-py3-none-macosx_26_0_arm64.whl", hash = "sha256:e6abeac9ac5265830c9c1541b6f96e9be37a85c2446763a46ad466c63a3837ab", size = 64367860, upload-time = "2026-08-25T10:31:28.056Z" }, +] + +[[package]] +name = "mlx-vlm" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "jinja2" }, + { name = "llguidance" }, + { name = "miniaudio" }, + { name = "mlx" }, + { name = "mlx-audio" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "python-multipart" }, + { name = "requests" }, + { name = "sentencepiece" }, + { name = "starlette" }, + { name = "tqdm" }, + { name = "transformers" }, + { name = "uvicorn" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/62/7fb872fd4c4cb2b8069afde85f08590098d0689efc588eab01bedb2d741b/mlx_vlm-0.7.1.tar.gz", hash = "sha256:b8abd3cc7e3513d9915bf9f6903833c697dc4c3084c2acdc880029c33e331a03", size = 2326562, upload-time = "2026-09-14T18:09:51.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/fc/7fbd894db247582bfa16df9b0b7514a47e9c3971836fc8d3fbd9f6efdbe8/mlx_vlm-0.7.1-py3-none-any.whl", hash = "sha256:d9696bc3a2e961f43b5948a101dc0c966b5989d382e6910c37e2f3d555bc3408", size = 2924465, upload-time = "2026-09-14T18:09:49.737Z" }, +] + +[[package]] +name = "modal" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "cbor2" }, + { name = "certifi" }, + { name = "click" }, + { name = "grpclib" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "synchronicity" }, + { name = "toml" }, + { name = "types-certifi" }, + { name = "types-toml" }, + { name = "typing-extensions" }, + { name = "watchfiles" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a5/9e322043716e511b7f6c9120804f33b92e21a818bf4709b2d98b3c76d665/modal-1.5.5.tar.gz", hash = "sha256:30df363ed1898cc3d91a09ff3f95c38ab043f6b6294011b01085312c6a0ac777", size = 870356, upload-time = "2026-08-28T19:51:34.881Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/19/b3dca8baec119126058b12ca620e44022e4d08a091956668bd04180c89a7/modal-1.5.5-py3-none-any.whl", hash = "sha256:8d10d3ee09818aaba1973b73ce2521ab8961b63a29b5b52e3ff0d25e7a74808e", size = 985163, upload-time = "2026-08-28T19:51:32.404Z" }, +] + +[[package]] +name = "multidict" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/59/84b6cad9ddfdd9471db727b0e987c60ecbdb6b206ba265e8c50e74a1ab80/multidict-6.9.0.tar.gz", hash = "sha256:d7d32c0543494efbc9394e2b571725071d08e295993486bc9a43f6f89375ee01", size = 173221, upload-time = "2026-09-18T12:50:55.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/c4/9cbd1370a191a49a2c4d0217c19e391aff080a9b35222f10c71ec73fc0e9/multidict-6.9.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fdd484b84d3394e805689c56be3ab1f877ae7ff0eb9ff90a3ee7a755cbebab4f", size = 94050, upload-time = "2026-09-18T12:46:43.407Z" }, + { url = "https://files.pythonhosted.org/packages/cc/16/898930380953e7fcc66aabb9f822c1f46c4d459cdc0d82bed0de2f687628/multidict-6.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f93c9058a0eceac0df2ce9d4c8823b84786ee598194753c2ca0c100224405e47", size = 58006, upload-time = "2026-09-18T12:46:44.916Z" }, + { url = "https://files.pythonhosted.org/packages/cf/22/0a55faaa9bba51bd7a01040106a5d40292e87bea575a2905afc37256e684/multidict-6.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c7ab60b91e11b25e7682c5cd8763fdd17929ea83f234ba441091f1492e631ea3", size = 55015, upload-time = "2026-09-18T12:46:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e7/6dba7bcfda65432a5af6f5978fb84ffda0360471f9571ea434afb06bd09a/multidict-6.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:640113258c5925a9eed2c12523410b25565ac5df2fa6735fbae88fb09bcdd212", size = 313468, upload-time = "2026-09-18T12:46:47.587Z" }, + { url = "https://files.pythonhosted.org/packages/20/b7/3317f5a0aba7a38f74a250a872c5a7ba58407f71028e09a9961fb3804ac2/multidict-6.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ea72901860ccbe94517421681c60b13533ce03ba2f7bd96293c3a4d16ac4ccb", size = 313112, upload-time = "2026-09-18T12:46:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/57/d3/46a482725368f3f29558bfca1a12a05ea68db6a51be54799d80b1bf81626/multidict-6.9.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:408fac672931f2458be3bc8c89d9facd16dac2aad17c7cee2ca1693eee99f07e", size = 298902, upload-time = "2026-09-18T12:46:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/ce4e30a3387b403bee1623873636e2b84f2e8b686a8d584f8eb9c62fa209/multidict-6.9.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:67bed23e9803945b0760650ec3e903af772c49abe869f3bc03c66b2e7649d5ef", size = 321654, upload-time = "2026-09-18T12:46:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/de/a4/3ad5099a178cbbd28913cd1fe77fd724cc576839f7893c301428d1d4f798/multidict-6.9.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb769ccc72e15d7d441e1a08f169d418376be77cdc387e813129c26b357fe50", size = 325316, upload-time = "2026-09-18T12:46:53.732Z" }, + { url = "https://files.pythonhosted.org/packages/47/cd/f27bf3242c2d70046211788d897679686a498bb032fbbc25f3c4d41ae9a5/multidict-6.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a5111a2bd824c821a3dd09da29680391b0caaa18fea7761358f4001e6898d1c", size = 317426, upload-time = "2026-09-18T12:46:55.155Z" }, + { url = "https://files.pythonhosted.org/packages/32/0c/8215c0167863a262ba8f886288c48fa2475c28fd0bc39fe548a8c86c8f73/multidict-6.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e991677c4bdc5d9f2e71c74717a4e32cfe98930ca05becdf722b5eae1329d6a", size = 278390, upload-time = "2026-09-18T12:46:56.749Z" }, + { url = "https://files.pythonhosted.org/packages/03/23/c51276d1086756f45cfa72d07f9d7bd21a59ce49b4ee7a245f99b2c9e527/multidict-6.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb69b724c345420ba49187a17a894f146099f5b2e501df42ddb4452ac8be37fa", size = 304354, upload-time = "2026-09-18T12:46:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/7d04a6347d7b06bf1868a995f91b2f2dc168a265860c55874101e4be7afe/multidict-6.9.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cff3cff5a725bdb8359962de8d7429aea592d7693dbd197eeabbdfab9b6300e9", size = 303669, upload-time = "2026-09-18T12:46:59.791Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/402d6b85d0e4aab551e700ca9123ebe2a52217836359eefbc1d92fd7f64d/multidict-6.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fca5b74b5909c29041f857c40d51d9273636fb4221cf020e4c452deb1c448a40", size = 316363, upload-time = "2026-09-18T12:47:01.41Z" }, + { url = "https://files.pythonhosted.org/packages/64/f6/49b70d1f876d7bc9263c972d2ecc58262590ea0c70743082ef091585b20d/multidict-6.9.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11e32ccf23cdbfcf8299a6a825930a858ec9a6aa05d6752d0f90f2bdf19489e1", size = 315126, upload-time = "2026-09-18T12:47:02.889Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5c/afff3cb22ccb3221b9715637f5e39f6a576a4c28ce8fc405555d94a17975/multidict-6.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ec1e387b1f8a85ae5b94aa8c4e0576912ffa4d31bd0578f24c950d4f05ee476", size = 278125, upload-time = "2026-09-18T12:47:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/edd123ad77ac77a108b2f7ba322727e92c2d39d714df2bbca9c09af9d741/multidict-6.9.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4826f6b56456fb1e98111d7bc20cbdc7fa0a41b1f9ad80ff2dd3f2f5b226fe1", size = 315167, upload-time = "2026-09-18T12:47:06.897Z" }, + { url = "https://files.pythonhosted.org/packages/79/32/0ba8f8b6a0529bc46c5d4376128a3b9dbc3d7610d0a5768be0e599b5e56d/multidict-6.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e71a072c52c78b7f97cd4611df6cef10977e4f2367cd0654a7626192f931adde", size = 313411, upload-time = "2026-09-18T12:47:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/a1/77/3b7b32a4331e99e89dc915c2dc1b054e49dd0f8c9c0c783c4d6063048da5/multidict-6.9.0-cp312-cp312-win32.whl", hash = "sha256:95d339c3b75b4a50c665bdcf8417428cd71c3e5cd48e194cb1d336fcb856beac", size = 50943, upload-time = "2026-09-18T12:47:10.105Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/484132b6c9d175939d8fb8b6c45b1d4eacecc79add962e1b4157ba138f60/multidict-6.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f06c4da5315a6f709b13408c3e13f3b475f8c559ec7608c3c67062512871235", size = 57304, upload-time = "2026-09-18T12:47:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/27/14/63792486623819be73ce2149749bcc0b82772d2de22e02d452213603c432/multidict-6.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:0db5bf96ec2ce45a8bc7fbbe8a486089969bb2791a66b6789ee3aed0d5dd562e", size = 53498, upload-time = "2026-09-18T12:47:14.294Z" }, + { url = "https://files.pythonhosted.org/packages/a2/cd/d9475e63b3bf46bb369437323a5906f2442063812f4c7c892d39cda2a578/multidict-6.9.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:662315f8621b3559268813b11134c1f9633edd4ab5f7b713688a243c13f4026d", size = 61405, upload-time = "2026-09-18T12:47:15.908Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/69cfa797b3e5f85ebc45c39d5aca05f69afc95e23b22943ee57f9499ece6/multidict-6.9.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4bef8cb5edea9c9daeb8396a75eeb97f912c3fb3fa408fe659edfecf91e47424", size = 53250, upload-time = "2026-09-18T12:47:17.254Z" }, + { url = "https://files.pythonhosted.org/packages/89/29/dd5d451e17dcec77f6b2dc0ed032714244d3562477349465dcc525f86297/multidict-6.9.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:75f7fc21ffce9a792cb919f0e0b0b52117bd672f2a55d1929d573b6fc437f374", size = 54521, upload-time = "2026-09-18T12:47:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/93/d5/20d0b17d61a1fba726b96fefdaa224a2da04e66b0ad9ad08af248023bb98/multidict-6.9.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bb6be697065cbf31051465f9939d65624573d2219c3da8316db9e7cf5e0f5a7", size = 93484, upload-time = "2026-09-18T12:47:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/c0/12/8067d5bd826b16eba31cb11b3f37ca6089d31b6afa33f208e77943f86230/multidict-6.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67bcff396d2ad62197c95488a716a88462792b384b5ea6e44bf7c1070eddb8fc", size = 57700, upload-time = "2026-09-18T12:47:21.668Z" }, + { url = "https://files.pythonhosted.org/packages/d4/da/ebf8f7e41e6bb58240ec346e5b8165acefd7ffdbce8ff503ab1bf2a1b883/multidict-6.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:beda95a6bd0a2e2265b941b90ff72543afbeaebb6a450e3a6a010d10d4a2ffca", size = 54762, upload-time = "2026-09-18T12:47:23.163Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d0/00ea9e7835ef5d61ce7d780d0aaca89a1d2215285937c9f12a089d1223a4/multidict-6.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e78870909e9f9e3d672ba99f1eb75130d7e01c42e642e6a70f434df32ca0ee1", size = 313470, upload-time = "2026-09-18T12:47:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/81/67/1cc27283d5a8b92f499addf7f8b2d47af5528dd019d3375a49c58bccf8ec/multidict-6.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91092d597fcf0940cd6a59e64ba7156ee22d6899993fd7c5b1897fda71482f8f", size = 312460, upload-time = "2026-09-18T12:47:26.282Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ca/e5e9ff320467bfc5301499f6cfef049b427f9e2f757dbe2c2d63912c32a7/multidict-6.9.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4be612f23990a261060ccd8f15e89dad1f9b7bb0c1021c5c3987f5fc57959391", size = 297202, upload-time = "2026-09-18T12:47:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/53dd52b603ab0eb5a6b4c29992d902f4f76a802c192aae48f915e7bdb566/multidict-6.9.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7aaa14f0b9ffa2780c5d3b21da0e9b58778ed47af3369df72e5fd6dc10ff8041", size = 320331, upload-time = "2026-09-18T12:47:29.704Z" }, + { url = "https://files.pythonhosted.org/packages/54/f9/e9d3c76824ed6eb6f92a992e501f15c4daf1b01bba2451d0254c943c8eba/multidict-6.9.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97555ad30ad20a8eb90fa86522088eebbbd68aba03e56d53f4850b3535c65e61", size = 324944, upload-time = "2026-09-18T12:47:31.451Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/19deb0d051c694dbc2d5008ec89b341f7ee42c4412b38630a47a14339115/multidict-6.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c51fd8d59e72e45c64907bf7f81fbd94f4c8dcd1c8b2f23f4c2d7ede788fcfc3", size = 316918, upload-time = "2026-09-18T12:47:33Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/b4e3a2e62ea902f2f000cd7a8eee8da4f31c6137ed35446eed11d62ae321/multidict-6.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22067e88ff266e6a5dc59114529332692a01cc04b10dbdc2c14ab91217dee819", size = 279224, upload-time = "2026-09-18T12:47:34.624Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a7/656ae0635cf82d9bbd8742403c7cbebd5bf555e02686b5e1ac43a80268b8/multidict-6.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e231de8ce43d4fd10bec4f67f71238ed88a974de30a9f20be7a4ab16c970a988", size = 303343, upload-time = "2026-09-18T12:47:36.335Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/cabef61a7151451a7f153679a1dd8b9d31684a0f8edaad782da5f1900083/multidict-6.9.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b33e499a7b1f722547d57b4865ed68c03161643b9afe49fe097a0832078d183d", size = 302877, upload-time = "2026-09-18T12:47:38.323Z" }, + { url = "https://files.pythonhosted.org/packages/07/63/942663ac05edd16c96ecf71f89f2f632dc113192684303287d727e465605/multidict-6.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1eb7939025bd9289d9dfe3a399102b1642f4fbb105a6af82f284052ee89e97a9", size = 317489, upload-time = "2026-09-18T12:47:39.843Z" }, + { url = "https://files.pythonhosted.org/packages/08/d2/359d4ef44c74c8ae25099dc50fbbebd074cca913d49d713aad10ac83b710/multidict-6.9.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:51a08dceed4b42ef25755ee2cbf50df325e90e6b415d00955ffdea2c394ad6c0", size = 314278, upload-time = "2026-09-18T12:47:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/37/fd/0b7a4fa989135045cd531c31c1adbb7ae762ee7143f5f1bc27985ae71d6c/multidict-6.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb847cb4002e725f88ffe29883da8290fd0754fa0461ed8988735e20964a9f65", size = 279318, upload-time = "2026-09-18T12:47:43.812Z" }, + { url = "https://files.pythonhosted.org/packages/78/08/7fd82e0e627b30733619c2d5ee52df047c608b562232e501835d645a5384/multidict-6.9.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:516fa4817cd070088f616a901380db56189f86daee9da79aada3c9b653f49ed7", size = 315045, upload-time = "2026-09-18T12:47:45.659Z" }, + { url = "https://files.pythonhosted.org/packages/b6/99/08c09a9aa8797246bf6b646e603654a8f25326df4881855da4b9b10a5e56/multidict-6.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:254e53be2ec70518bb82dfa9b0c7166baaf2bd0e918bd17b65caaf489d5a5522", size = 313360, upload-time = "2026-09-18T12:47:47.503Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3c/72f0c58d20f3e8d167105b0ab3e8b4a9e3d7fd285b9f6fe4f1e1295ad002/multidict-6.9.0-cp313-cp313-win32.whl", hash = "sha256:9869105ab61db13004f9ed610cd29ce4237b2c609f5f7b8bb8f2339fb9dad17d", size = 50744, upload-time = "2026-09-18T12:47:49.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/51/384557cf30472e760ab9e897105f3a4dc7bbb8162e4a60695008ba012c40/multidict-6.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:85ed0f3c3b01174aea5a8a8fcd456f64c2e9719c0f42c612c2694ecd35974a65", size = 57094, upload-time = "2026-09-18T12:47:50.611Z" }, + { url = "https://files.pythonhosted.org/packages/59/89/8a3741537d155588cdf220bba1f1e5c96ef2e92ee1f41856be76237eafc2/multidict-6.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:1126782e3c3b1a7ccd990be3d3221709348e4b07d6ecf8b50965a93ae2624145", size = 53342, upload-time = "2026-09-18T12:47:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/75/2a/557689d56936a83c112ee28f4c4d7698ff01c24e22d217ad8fcce19982c7/multidict-6.9.0-py3-none-any.whl", hash = "sha256:57c2445049f7d8e66306f712868219da7ff7168ef42263dc032401211bf1205c", size = 19175, upload-time = "2026-09-18T12:50:52.172Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, + { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, + { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, + { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, + { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, + { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749, upload-time = "2026-07-02T06:59:53.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443, upload-time = "2026-07-02T05:50:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755, upload-time = "2026-07-02T05:51:30.556Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064, upload-time = "2026-07-02T06:53:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711, upload-time = "2026-07-02T06:54:13.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576, upload-time = "2026-07-02T06:54:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032, upload-time = "2026-07-02T06:55:03.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734, upload-time = "2026-07-02T05:49:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345, upload-time = "2026-07-02T05:49:54.971Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "propcache" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/9a/9fbf4e4ec0c2d7f1c32519fff782ef467859b8faa9fbc5331a96f6395d43/propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558", size = 61545, upload-time = "2026-09-16T00:17:14.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cd/348d58f142aebc4873345c6b31087629182ca6e0f2b3caeaa528cf882eba/propcache-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429", size = 87285, upload-time = "2026-09-16T00:14:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/df/f4/f3ffaee281b276da854ac1d7a6a506d26cbc62ea2e623756f1d0a4a1ba1a/propcache-0.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8", size = 50984, upload-time = "2026-09-16T00:14:30.473Z" }, + { url = "https://files.pythonhosted.org/packages/25/88/1d7df7201750b37765ef2b23bc1c526c028dadde80afa0f57a118fc01182/propcache-0.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21", size = 52460, upload-time = "2026-09-16T00:14:31.692Z" }, + { url = "https://files.pythonhosted.org/packages/83/4f/48865bd02a16ee5236bc46166b2946f37b93e07b0eae355dac0be0b216ca/propcache-0.5.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef", size = 251768, upload-time = "2026-09-16T00:14:32.908Z" }, + { url = "https://files.pythonhosted.org/packages/b0/19/3742a5eed62317b03b4002ee865dc9fd720308bdd0da1f29a5786c630311/propcache-0.5.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f", size = 257723, upload-time = "2026-09-16T00:14:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/ee6350fb0be9122bb6c67082a876d34b90d980d100c106af4b81023e04f4/propcache-0.5.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546", size = 265597, upload-time = "2026-09-16T00:14:35.56Z" }, + { url = "https://files.pythonhosted.org/packages/85/9f/83a07b6ec0e043c050cfdd35fb0cf1b7897b91d554d6eea293740309afe7/propcache-0.5.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b", size = 250424, upload-time = "2026-09-16T00:14:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/2c/a763a8251f50fba042af0fb1f02bfec4b31381e40aff760db2be7b2e1f84/propcache-0.5.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab", size = 216748, upload-time = "2026-09-16T00:14:38.369Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e2/4d11bea8fd6a777149c6c20645f873952eab5de3a2497aa11648ec9ab6ab/propcache-0.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc", size = 246533, upload-time = "2026-09-16T00:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/9f/36/6683597de4907e70c717e3588c541202c66086a72ff3db58be49de66e72c/propcache-0.5.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937", size = 238173, upload-time = "2026-09-16T00:14:41.259Z" }, + { url = "https://files.pythonhosted.org/packages/85/84/cb08d79f1762daafeb2b030c470cd0c725c97b8ad67412457c6f35c53e9d/propcache-0.5.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526", size = 251128, upload-time = "2026-09-16T00:14:42.652Z" }, + { url = "https://files.pythonhosted.org/packages/c2/0d/41b848036db6621370c1f2e5471a7da8149c730f8552a5257567721f4576/propcache-0.5.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944", size = 214821, upload-time = "2026-09-16T00:14:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/adfae4bf9c63bccf12e2d9690a175c6579047a6eec3b5a6a5f51428c15e2/propcache-0.5.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031", size = 254793, upload-time = "2026-09-16T00:14:45.429Z" }, + { url = "https://files.pythonhosted.org/packages/51/6f/eeca9647245d5f92e87d53e5f14335bb42fce1a7e6842c8045b364eded8b/propcache-0.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161", size = 247134, upload-time = "2026-09-16T00:14:46.976Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a9/424e38838793d37160b4379c702f61c74c598fc6cd17204adbe3c554f7a8/propcache-0.5.4-cp312-cp312-win32.whl", hash = "sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8", size = 43073, upload-time = "2026-09-16T00:14:48.338Z" }, + { url = "https://files.pythonhosted.org/packages/58/7b/6e8ef26f6d510a7916064fec68d55fcbfbdf7eb01e377480d66a122152d8/propcache-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb", size = 46190, upload-time = "2026-09-16T00:14:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/08/b9/72028c5b56ced97f456de6aefa79435ca64d7f77af78ea8cf3c76fc5195f/propcache-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737", size = 43075, upload-time = "2026-09-16T00:14:51.155Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/3b1365d58a667689e067e13d055fcd92bdf8d9a2fca3d9201b47ed5b3631/propcache-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba", size = 85290, upload-time = "2026-09-16T00:14:52.342Z" }, + { url = "https://files.pythonhosted.org/packages/8f/61/5f9c29c3aa67c30238c4eadf95149b1d983a48f69b86b0cff927a7d6df13/propcache-0.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0", size = 50027, upload-time = "2026-09-16T00:14:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/25/7d/c1ab1ef09e9d4d835be5d58c0a32a1e1de8397abaa4e502a9d4141328cad/propcache-0.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111", size = 51425, upload-time = "2026-09-16T00:14:54.826Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/0093091ebb270fcd1bc1f6e095f93b2e0ed7f1011c28837dc2dbe5f96b99/propcache-0.5.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c", size = 233595, upload-time = "2026-09-16T00:14:56.09Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/0de9d4c8e05ce0be71b436919a216bd7fc5cc6e2691c0602295efb22b9ed/propcache-0.5.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867", size = 240318, upload-time = "2026-09-16T00:14:57.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/2b35e91455209b85ee98f7859583e0814fab57d3af0f2381aaee34c37304/propcache-0.5.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9", size = 246649, upload-time = "2026-09-16T00:14:59.352Z" }, + { url = "https://files.pythonhosted.org/packages/ed/74/08e6c1faf26ee2732023a3828787ba535557122774f4a386b1f715cbd8e0/propcache-0.5.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572", size = 234316, upload-time = "2026-09-16T00:15:00.696Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/08385733c9321c9bb78039d3ff31045e4fca962d9665023c4eb70f998819/propcache-0.5.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8", size = 204666, upload-time = "2026-09-16T00:15:02.019Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f4/e87bc7629af9a14a752b218764a78742d73c2c563ac58315da6841f0cbe4/propcache-0.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894", size = 225900, upload-time = "2026-09-16T00:15:03.394Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6d/11014938d3fe9bea2ea2dcf930f26ed565bfb2f5be3c756362ea48c92636/propcache-0.5.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9", size = 219988, upload-time = "2026-09-16T00:15:04.811Z" }, + { url = "https://files.pythonhosted.org/packages/dc/72/fbf17c589f92c0b3bbf6709a425661f8ef2ed0d46b38985a7d7b5a0f6b91/propcache-0.5.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47", size = 233611, upload-time = "2026-09-16T00:15:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/55/7e/dbd637572a279692e5518d117274a9331bf5faac59f191d30e82521a3ec7/propcache-0.5.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c", size = 204333, upload-time = "2026-09-16T00:15:07.961Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/f99c92068f1e0f5c886899ce0e4a619db376ca98c5279d93f95bd86906af/propcache-0.5.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1", size = 235177, upload-time = "2026-09-16T00:15:09.334Z" }, + { url = "https://files.pythonhosted.org/packages/ee/28/95456fabd2daf6be89049a13fbf03341756014d2959c83d12957d4c49694/propcache-0.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c", size = 228982, upload-time = "2026-09-16T00:15:10.729Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bb/df90f62c9cf7c93ea235f6f9405143bba802914607317266dd81fc8d737e/propcache-0.5.4-cp313-cp313-win32.whl", hash = "sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25", size = 42611, upload-time = "2026-09-16T00:15:12.111Z" }, + { url = "https://files.pythonhosted.org/packages/01/bc/e0a7b84af04ec02d73a48aa71f091e1e4a2107e3074b7ce12195b66901f4/propcache-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77", size = 45342, upload-time = "2026-09-16T00:15:13.519Z" }, + { url = "https://files.pythonhosted.org/packages/9a/70/50b031cafe72a5c1878b903ee87303f71313345566bf3d6ec202e5ddc9ec/propcache-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177", size = 42408, upload-time = "2026-09-16T00:15:14.788Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cd/785c64ed382f3f04201870267b02783f63b4678c2acfddc177a3ebcc2727/propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468", size = 16338, upload-time = "2026-09-16T00:17:13.106Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { 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" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "regex" +version = "2026.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609, upload-time = "2026-09-09T20:57:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024, upload-time = "2026-09-09T20:57:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905, upload-time = "2026-09-09T20:57:13.127Z" }, + { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055, upload-time = "2026-09-09T20:57:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133, upload-time = "2026-09-09T20:57:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627, upload-time = "2026-09-09T20:57:18.402Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587, upload-time = "2026-09-09T20:57:19.859Z" }, + { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320, upload-time = "2026-09-09T20:57:21.62Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572, upload-time = "2026-09-09T20:57:23.048Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485, upload-time = "2026-09-09T20:57:24.658Z" }, + { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925, upload-time = "2026-09-09T20:57:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800, upload-time = "2026-09-09T20:57:27.901Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648, upload-time = "2026-09-09T20:57:29.786Z" }, + { url = "https://files.pythonhosted.org/packages/fc/65/eac1a79115c8475d8ff539602eb54031564d719fdea5a599c4b0a1a26d1b/regex-2026.9.10-cp312-cp312-win32.whl", hash = "sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348", size = 267326, upload-time = "2026-09-09T20:57:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d4/4dcd0a05d3e97ca829165df1c39717f899d1d484dac8a6032439f2cb8d6d/regex-2026.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86", size = 277933, upload-time = "2026-09-09T20:57:33.648Z" }, + { url = "https://files.pythonhosted.org/packages/55/f8/22617a80dee28f2451011eae36bc26b3d78c4994ba87b5281d60acf9b6c0/regex-2026.9.10-cp312-cp312-win_arm64.whl", hash = "sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae", size = 277447, upload-time = "2026-09-09T20:57:35.156Z" }, + { url = "https://files.pythonhosted.org/packages/20/90/d4452bf1ef7dbe406980e8b921a257024482203c1dafac535eae207611bc/regex-2026.9.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ef5a059ea1c6ee5d1c7e99a2484e628608d010921efe876c6f0e2029d2f35eca", size = 496408, upload-time = "2026-09-09T20:57:36.757Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/c763c6424a0f99d021d46dc1f9065147bb5a40c2b2cdf28d2ebdbcd96508/regex-2026.9.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dce932f8e3ba936475ea3d0d8b59f7b050a9e206e994f53f8fd80299871e87da", size = 296931, upload-time = "2026-09-09T20:57:38.811Z" }, + { url = "https://files.pythonhosted.org/packages/fa/68/241f88458b17c46ed2f80147a60a03b2ada7fb815c23b6bc76c298abb0a5/regex-2026.9.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d8c668af8f7bdb1d18739c27d30cd9f4b371495a883f75a002fb7a39d740fecd", size = 291741, upload-time = "2026-09-09T20:57:40.482Z" }, + { url = "https://files.pythonhosted.org/packages/90/9e/974d6de404c63e2d09525f4ddb99874c7ab8e1f781ccbe0dd3e26fa6f6e5/regex-2026.9.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aebdd9a946de328b3f6f61dbf48dd064a36eb6dddf96e34ae6651d37f6e9383", size = 800088, upload-time = "2026-09-09T20:57:42.098Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fd/3875b73f9e7ba3321dcaa02c19f650c05c61345328acf84599ac6f45ceed/regex-2026.9.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f2374c27deb189b282ec7e16106752c22ad39b056bbd8018960b1e4cc95d67a1", size = 871212, upload-time = "2026-09-09T20:57:44.03Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/2358e791c0e171194dd6a8b97b520579098a21397fb79dbe6b7edc9e3fa7/regex-2026.9.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e0dc78251154b66dc60211563fc115345da332eaa881e4e2523fb1edae3772f4", size = 919752, upload-time = "2026-09-09T20:57:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/20/3b/000c79c3f9c06b7542225a5d3a7f9a85405da7224b3b9af94a491d07abea/regex-2026.9.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041", size = 804578, upload-time = "2026-09-09T20:57:47.548Z" }, + { url = "https://files.pythonhosted.org/packages/30/6d/195eedb1de87f26639191e7487e41eb81e2ce255bc7563a64f3f5a95eb08/regex-2026.9.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ebb2ba68e4641a994061f70bf44ed448fba0b9b1d18c94ffb9efc1cca805b39b", size = 777345, upload-time = "2026-09-09T20:57:49.63Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/11fe2b313fcd92cb75c583648f2746031b9f4da9e9ed4241204a5e8b3721/regex-2026.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:048a89ee797db10160bd2bd519286577a6b43a100279bd4b7d8456a3d69c80a0", size = 790556, upload-time = "2026-09-09T20:57:51.27Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c0/07ec9b4c43b0e16d62454971a5ab3886eccb0bfa161300a02d801ab28620/regex-2026.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:79e9432995e14c749d34209413de5e621ec8e67789bf4f46dbfabea9d06a2406", size = 865572, upload-time = "2026-09-09T20:57:53.163Z" }, + { url = "https://files.pythonhosted.org/packages/19/07/43bc9a9cf9fc8e37d2ba47980dfe4a6e151d2cf3ab969e0031e2a9b21484/regex-2026.9.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5847e22bbf959764d776937d791d034cc2d19b787e361c88d97e859e8dc68502", size = 767971, upload-time = "2026-09-09T20:57:54.805Z" }, + { url = "https://files.pythonhosted.org/packages/9c/49/3b9286a3a94f3c89ed4ddbe74e72bdde21c1a5eadd520d5f4ed4a61936cb/regex-2026.9.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c103b3b14e011774af4fb7e4617ad4d72b9171905cd3b231a70a4efd76e477d7", size = 858835, upload-time = "2026-09-09T20:57:56.627Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9e/e5d27ce9fee8e3ef95f886c7b6ecec211efa4cfc18bd73bd5cf26cca4741/regex-2026.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b34a778c695d24e77c140e3b4c95da69282e34f2f6b02b55656aa4a0379f643", size = 791793, upload-time = "2026-09-09T20:57:58.313Z" }, + { url = "https://files.pythonhosted.org/packages/63/03/c28a6bebedc3e2d86ee27ec2de16f7ec0419dcd10e771d43dcc9c58a2e99/regex-2026.9.10-cp313-cp313-win32.whl", hash = "sha256:7abb38b8c40f3a235235a44da452c64b7b5c1d650ec6351027db0e090804f2e5", size = 267298, upload-time = "2026-09-09T20:58:00.009Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fd/5c85fa6cfb8e034080bda5a72fa0a4df2b7777a35eb7e73c2799c2adda7a/regex-2026.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:20e8bfb07ad79a282f8b95b56fe67f9750b1b7f775724e4ba1f23cb296115ce4", size = 277894, upload-time = "2026-09-09T20:58:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/c1/28/f5a25f6f65501675977fda35d9f61abb1468c4b87c0f73e536d8b21a60b8/regex-2026.9.10-cp313-cp313-win_arm64.whl", hash = "sha256:3bdeed3318a8eb2bbadc9c56347e0ff651639e934a47e168d05a3b12929fd0e7", size = 277436, upload-time = "2026-09-09T20:58:03.422Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "ruff" +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592, upload-time = "2026-07-22T19:30:44.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216, upload-time = "2026-07-22T19:30:43.251Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "scipy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/f7/240c110c08693826b4513a52f5717d62ec7c7af72f2920821247c03b17b3/scipy-1.18.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:457fd7a2a8edeb044ab6ffbc0aa03ff6cd18491356e5e0c834d76ce621b916d1", size = 31111061, upload-time = "2026-08-21T23:23:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/05/4a/78c6285577c375e7cf27277ea8ee6961224327f1e1a0c44af5f17f23635c/scipy-1.18.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:e708533e8b2ae2497d65346538a7dcc92814410b25b81432eac66de0f2af8265", size = 28733332, upload-time = "2026-08-21T23:23:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f6/a5b82f8abbe14d134691b8b903696f701d25a081353a29dc655c364d9e62/scipy-1.18.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7bbf207c4453ce1ad2e00b17313852b33310b83090c2311bdaf97f93c0380d12", size = 20475078, upload-time = "2026-08-21T23:23:54.138Z" }, + { url = "https://files.pythonhosted.org/packages/23/22/0858a0bbd6b3e825ceb8cd9baf9eaf3b2f2b1d77727eb6be40500bcdc92f/scipy-1.18.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:78c0665edead396b1abb4897c41a5c1d9bf090c8a637a4c20a61678e0a264e66", size = 23108904, upload-time = "2026-08-21T23:23:57.824Z" }, + { url = "https://files.pythonhosted.org/packages/75/9a/2e71719f31eaefe0e3a1706c4a1ded94e664bfd95ffca2b219a671faee01/scipy-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c085faa2cfa879c5141df483f836f4d691045a078224a670fa570fa01612d89", size = 34025113, upload-time = "2026-08-21T23:24:02.209Z" }, + { url = "https://files.pythonhosted.org/packages/df/64/ff35eb9e54894cf471ff4716abd3c81eb0a0626869217ce3e6ba4ccf17d7/scipy-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f55fa87b6c612ecd6b058f167c53231b1d14e412efe361d3d6e38b3631c73218", size = 35344199, upload-time = "2026-08-21T23:24:07.844Z" }, + { url = "https://files.pythonhosted.org/packages/d3/af/c5538be1792f7034c12c7db6ee67cace58253c7b87b122d68253eaf5de89/scipy-1.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c35d74ce0e193ff740c2f2be2ac913ddc232fe6c1ff40b26cfecb9c670c63314", size = 35639587, upload-time = "2026-08-21T23:24:13.05Z" }, + { url = "https://files.pythonhosted.org/packages/91/4c/075e4f66471bac101141ac739e9e135549be1bae584571bd03a530c056e1/scipy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2924a03db38dc2e848bca2fe9f077dafb891480b91a00a0963a8cf86dfc31c1", size = 37480330, upload-time = "2026-08-21T23:24:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/39/e7/979fd14e75008623df31ba70d6bb144700f68feadcea042021c06a05bf82/scipy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:5e4d44984abc0020154ea81b247adeddcc3ac5527b975ff798bd1ba0adc513c2", size = 36658278, upload-time = "2026-08-21T23:24:25.463Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/e1525354ff9d7d5feb6d1b31af6d14072e5c91e9607b421fa1ec889660b3/scipy-1.18.1-cp312-cp312-win_arm64.whl", hash = "sha256:d65d448389b8436493abcf629cc94ad0cf32aecaf06e1acca1de53cc795f2f12", size = 24400588, upload-time = "2026-08-21T23:24:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/4540ee0f9c42a9ad7109d0d1a8cc70de54c3572b01c6693a2b1c70e90ceb/scipy-1.18.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:3ab3523da44749156e1f68b464dc56af11ae4cbc5c739a49d05f32b982eca9f3", size = 31089958, upload-time = "2026-08-21T23:24:35.8Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6fb6a55cc0ba97b59a1f288fb86dc6fce8bdfc0fffcbfd015e3a954bf2a2d93", size = 28715106, upload-time = "2026-08-21T23:24:40.775Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d7/21d890274f75ea37a8209d5519e72da3da90302e3b9fb8397a0918386a62/scipy-1.18.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ea324d9dd34c38bfb9bec8ca4d1b407db97dbb74029f566b8e322b1b6fe56fe6", size = 20456846, upload-time = "2026-08-21T23:24:45.066Z" }, + { url = "https://files.pythonhosted.org/packages/ec/01/798430ecea2e78ec7c02663d5f71c007bb6abeca931080debd40d7fa55ea/scipy-1.18.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b00eb8fb802090aa903f4ea1c7f5a584779f967361e68b7e98e531cc2d7174", size = 23087986, upload-time = "2026-08-21T23:24:49.539Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5f/4634e9d35c68496e4e34cb6946eafab044458e6cedab42b40b6588e475b6/scipy-1.18.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d416b16cccfd70fbf62400e84d0bb2f4e6af519a45557f1692c749b37f14b315", size = 33998146, upload-time = "2026-08-21T23:24:54.714Z" }, + { url = "https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fdaf5ea890a6183d0565f51a61799d67081bd5b1cf03c5f4b3fd3732108625c9", size = 35312578, upload-time = "2026-08-21T23:25:00.44Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/bf5a4be6a3525676499f6dff307991739ff6fdcad1481b1aeb6745339f58/scipy-1.18.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c825cef2f49e46753726a7181a8e199804a912b29519ada542c6ebc654951899", size = 35612621, upload-time = "2026-08-21T23:25:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4e/3c45c33e00a77996c4b1cb707929f833ba7b1d522ee29f882512c330676d/scipy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3b417bf8c2c7c16e8f58ad91db17783ec911ac16e7b50eb6eab6e809b4f5b07", size = 37457323, upload-time = "2026-08-21T23:25:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/93/0e/e0348fbc0dbab65c114cf78957e7dfeb49f8e8b556b4d930cc12ff195e18/scipy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:559ed65f60c1af5a03f3912605a1b5114f522c7c32fb23c3376ae8f03219fe28", size = 36622841, upload-time = "2026-08-21T23:25:18.722Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/6a77f5f267c555108f0a864b6db714363dab567a8266422a79a385f9232b/scipy-1.18.1-cp313-cp313-win_arm64.whl", hash = "sha256:cd479fc04dd9401e3b4f49e76518768ef99c4f517a98c284eb091fd725719adf", size = 24399315, upload-time = "2026-08-21T23:25:23.458Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384, upload-time = "2026-07-12T08:38:28.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553, upload-time = "2026-07-12T08:38:30.552Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176, upload-time = "2026-07-12T08:38:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200, upload-time = "2026-07-12T08:38:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736, upload-time = "2026-07-12T08:38:35.771Z" }, + { url = "https://files.pythonhosted.org/packages/8d/11/753fca2e6b109be3ab7867abf357dfe48677fe726ae5a5363d0b54ca9450/sentencepiece-0.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383", size = 1248030, upload-time = "2026-07-12T08:38:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0a/70efbe861ca182d7d4b6e1a20f58e043400848fa9f2915229f082e221648/sentencepiece-0.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d", size = 1187325, upload-time = "2026-07-12T08:38:39.348Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a3/b3b05095c174d6e80d37d5ddc2f57c2c56237333e7bbd6079cf3243c2a8a/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8", size = 2188346, upload-time = "2026-07-12T08:38:41.089Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/72ebc4acb10a06bcf7503fbc6091c8f5db68300f6aac4356c09e6c76e0e1/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c", size = 1441434, upload-time = "2026-07-12T08:38:42.56Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/f9ea1a6844b4fa5dfe2312095cd866a1f724cd0905054ab9d5991778ba50/sentencepiece-0.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a", size = 1347267, upload-time = "2026-07-12T08:38:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/32/4f/31c1073314ad94466bca37d29581761d70110237ee3d46b0efece59a8c1e/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0", size = 1324980, upload-time = "2026-07-12T08:38:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/59/b4/a0356fa04d6a14337a6e0e443556785a0422c53ec58baae6b9568120eb0f/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb", size = 1397593, upload-time = "2026-07-12T08:38:48.302Z" }, + { url = "https://files.pythonhosted.org/packages/09/fa/d2d6369257fd2f0de616b1c7110b73fab409ef61b14f1b9e0010ed325914/sentencepiece-0.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9", size = 1247987, upload-time = "2026-07-12T08:38:50.15Z" }, + { url = "https://files.pythonhosted.org/packages/17/ee/2bb594da6fd95e32f29057f1aa7fa996701b8980090923c2d8711fdc0a24/sentencepiece-0.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91", size = 1187250, upload-time = "2026-07-12T08:38:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/58/9c/dfc82846460e7a712310f5613f23d8b553cabb4e2e648663c11d8382af56/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78", size = 2223080, upload-time = "2026-07-12T08:38:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/3ff12cebe6d31662d9ceeabfb282de20bd0d6098fa282b4a3b8305abc7e8/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563", size = 1458511, upload-time = "2026-07-12T08:38:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/5a/16d51d05360be4cee3ebfe4837c184054c4eed16cabaeb3b039524e9a000/sentencepiece-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5", size = 1361138, upload-time = "2026-07-12T08:38:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/0f/af/c30ee2a9f99d51db9844acaa8fa0b611a97c2fa7116646fa43db3300b187/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d", size = 1328625, upload-time = "2026-07-12T08:39:00.849Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1a/4c6b39d03f5ba8439509adbd5a23c9538088a3cb679e7a47b911e8442bc6/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b", size = 1398595, upload-time = "2026-07-12T08:39:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9eedddcec1fd57bc70200fa3ebf792d18fa63527a5369581cd416c81f97f/sentencepiece-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53", size = 1259346, upload-time = "2026-07-12T08:39:04.559Z" }, + { url = "https://files.pythonhosted.org/packages/41/15/7e74c8533848866ff560b29f7d8719921b76c4ec7149592d6d28e0deee75/sentencepiece-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd", size = 1196596, upload-time = "2026-07-12T08:39:06.454Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "solomon-mlx" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "huggingface-hub" }, + { name = "mlx" }, + { name = "mlx-vlm" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "safetensors" }, + { name = "scipy" }, + { name = "transformers" }, +] + +[package.optional-dependencies] +cloud = [ + { name = "boto3" }, + { name = "mlx", extra = ["cpu"], marker = "sys_platform == 'linux'" }, + { name = "modal" }, +] +dev = [ + { name = "modal" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "boto3", marker = "extra == 'cloud'", specifier = "==1.43.98" }, + { name = "huggingface-hub", specifier = "==1.32.0" }, + { name = "mlx", specifier = "==0.32.2" }, + { name = "mlx", extras = ["cpu"], marker = "sys_platform == 'linux' and extra == 'cloud'", specifier = "==0.32.2" }, + { name = "mlx-vlm", specifier = "==0.7.1" }, + { name = "modal", marker = "extra == 'cloud'", specifier = "==1.5.5" }, + { name = "modal", marker = "extra == 'dev'", specifier = "==1.5.5" }, + { name = "numpy", specifier = "==2.5.3" }, + { name = "pillow", specifier = "==12.3.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = "==9.1.1" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.8" }, + { name = "safetensors", specifier = "==0.8.0" }, + { name = "scipy", specifier = "==1.18.1" }, + { name = "transformers", specifier = "==5.17.0" }, +] +provides-extras = ["dev", "cloud"] + +[[package]] +name = "sounddevice" +version = "0.5.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/db/0c890e2d9aab9ba284021efc02e1d3aebfecab1b611762d7434602209bcf/sounddevice-0.5.6.tar.gz", hash = "sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410", size = 1120094, upload-time = "2026-08-17T07:55:05.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/1f/62eef605172bddc1017508469a12f75bc7c4194ece35c734f822795f53b1/sounddevice-0.5.6-py3-none-any.whl", hash = "sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0", size = 32793, upload-time = "2026-08-17T07:54:57.507Z" }, + { url = "https://files.pythonhosted.org/packages/b6/84/85e719d49cf98b2f406d9ac9c338892286c4448eb42ef0b2625ccf159616/sounddevice-0.5.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c", size = 1009647, upload-time = "2026-08-17T07:54:58.814Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6f/6292145099f72a153a710245f46ae43e5fb6c77bec1b6086cb76c12dc280/sounddevice-0.5.6-py3-none-win32.whl", hash = "sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c", size = 1009627, upload-time = "2026-08-17T07:55:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3e/cbc593c31a5f0d817b3fe97e64aa8461bd0f55cb07b67ce1b776296ae336/sounddevice-0.5.6-py3-none-win_amd64.whl", hash = "sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4", size = 1009630, upload-time = "2026-08-17T07:55:02.088Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/b0c21c9f215a6fd9606b8f8748c21212dc098e5d5a2d93068c50edcf19b4/sounddevice-0.5.6-py3-none-win_arm64.whl", hash = "sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45", size = 1009630, upload-time = "2026-08-17T07:55:03.689Z" }, +] + +[[package]] +name = "starlette" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, +] + +[[package]] +name = "synchronicity" +version = "0.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/1c/f51dc54bbd302991026a53f9790735540e0e9e1184e9d5939f02446aa5bc/synchronicity-0.12.5.tar.gz", hash = "sha256:94d96b1d85698e3056b96a793b8c0949af6584e4a7d877fabdeb5385efe230aa", size = 60745, upload-time = "2026-06-18T21:06:23.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/74/ad9b99520f70c0bc3318e582e359d360cfc0f7afd7bf368a7f24013cece7/synchronicity-0.12.5-py3-none-any.whl", hash = "sha256:fdbbb10d437bc08a6b0f814fc66fddd1b58ffed314533d42f1ab555801e781af", size = 41107, upload-time = "2026-06-18T21:06:22.505Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { 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" } +wheels = [ + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "transformers" +version = "5.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/9e/750649904a065007a838981785b2bd8d9ff26154c6c341ac67d0b7f82c68/transformers-5.17.0.tar.gz", hash = "sha256:a153be279169b55b92d8000bf4af294aed684503d091cca7804da2dd8a9de000", size = 9817878, upload-time = "2026-09-09T15:39:56.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", hash = "sha256:78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", size = 12295140, upload-time = "2026-09-09T15:39:53.746Z" }, +] + +[[package]] +name = "typer" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "types-certifi" +version = "2021.10.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/68/943c3aeaf14624712a0357c4a67814dba5cea36d194f5c764dad7959a00c/types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", size = 2095, upload-time = "2022-06-09T15:19:05.244Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/63/2463d89481e811f007b0e1cd0a91e52e141b47f9de724d20db7b861dcfec/types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a", size = 2136, upload-time = "2022-06-09T15:19:03.127Z" }, +] + +[[package]] +name = "types-toml" +version = "0.10.8.20260518" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/11/6ece999e91f2ccb848ab4420f3f4816e78ac0541f739e6864affdaaa5737/types_toml-0.10.8.20260518.tar.gz", hash = "sha256:80e10facd24fdeda9d5c672187d72be3ac284843788d67f5aae59e3e016db6fe", size = 9419, upload-time = "2026-05-18T06:02:16.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/25/489751806bf5c95e4007f8e17409199c54d31e49ffbea07c5729b1286c8e/types_toml-0.10.8.20260518-py3-none-any.whl", hash = "sha256:0e564ab05f6fde62a315b3b5a9b6624fda569399795d30a37e64705a70459303", size = 9669, upload-time = "2026-05-18T06:02:15.86Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { 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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.53.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/ad/04bbb797c84fc1f26cb171f7394716f4865ffb8d8c5e1eef42565c2dfa6b/uvicorn-0.53.0.tar.gz", hash = "sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e", size = 110881, upload-time = "2026-09-14T07:44:23.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/18/0eea75741ee812e9f598b687619ce2454f6c3a1c5cd21ea990ec6bd26f45/uvicorn-0.53.0-py3-none-any.whl", hash = "sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e", size = 87081, upload-time = "2026-09-14T07:44:22.179Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, +] + +[[package]] +name = "websockets" +version = "17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/13/86/bc1317b1a4d8c4688e2a7e564b5e004dab44c2534d7ca05de6ae9a863fca/websockets-17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98", size = 214692, upload-time = "2026-08-26T14:55:46.366Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e7/df821761772beaa48c211ee0e234930b35c1473778470773823f56d3911b/websockets-17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab", size = 214959, upload-time = "2026-08-26T14:55:47.885Z" }, + { url = "https://files.pythonhosted.org/packages/3e/92/c3fb72f11764812fc648bf3838d224972427b348e8b3989d9e0a9df87da3/websockets-17.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74", size = 224278, upload-time = "2026-08-26T14:55:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/fb/05/9f82d090c8d2d861604147ef6dfb938a90b039f9358d5193f1df62558593/websockets-17.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6", size = 224557, upload-time = "2026-08-26T14:55:50.348Z" }, + { url = "https://files.pythonhosted.org/packages/8a/50/5cbf677b865290fe36819ff00615826e7edc1df38786f770123ff39a933d/websockets-17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08", size = 225791, upload-time = "2026-08-26T14:55:51.75Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1c/eb8a032285243381b09a221ae384c972d5000453ad136add4d1595cec798/websockets-17.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11", size = 228574, upload-time = "2026-08-26T14:55:52.922Z" }, + { url = "https://files.pythonhosted.org/packages/69/85/413736251cb3ac04ce84cbd90e893d9a36a9698d4820b323aff3aa187e50/websockets-17.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4", size = 226428, upload-time = "2026-08-26T14:55:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2b/a08bcc7fa1ca81a10f84ba32b6e6edd73a913f4b0c2640eed1fd626efacd/websockets-17.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8", size = 225184, upload-time = "2026-08-26T14:55:55.943Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8a/3bd2d0cf6b148c8c866d5d9fdcde30c04bfd81fdfac86813e69377eb4448/websockets-17.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391", size = 222430, upload-time = "2026-08-26T14:55:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c9/8e891ae342668735eabbbc669895e15195e4b45f24a4beeb58af76f414c7/websockets-17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a", size = 225227, upload-time = "2026-08-26T14:55:58.375Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6f/c816f332dca11425e9bda7c07f7573eb5c5f8a735849d02b0d81e8ee20fa/websockets-17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837", size = 223831, upload-time = "2026-08-26T14:55:59.664Z" }, + { url = "https://files.pythonhosted.org/packages/53/67/5e91d5308ce24fc1ec74f56536c12f4888bad45ff5ea50f3180f8c518c57/websockets-17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641", size = 224600, upload-time = "2026-08-26T14:56:00.873Z" }, + { url = "https://files.pythonhosted.org/packages/bb/96/faa298ecf2570d35b0eb37caddf4992178d907e108ed74bfffb6bc092c29/websockets-17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090", size = 225707, upload-time = "2026-08-26T14:56:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/0b/12/5710d2482ca5061c1eec5eb46f6313837c760d4115b1795c85b6c08be4e3/websockets-17.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1", size = 223263, upload-time = "2026-08-26T14:56:03.092Z" }, + { url = "https://files.pythonhosted.org/packages/27/47/0c30f4eebfd1d93fae779d268f678d48847fb98516f5200849574eee8820/websockets-17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067", size = 224244, upload-time = "2026-08-26T14:56:04.321Z" }, + { url = "https://files.pythonhosted.org/packages/41/33/46c256195a1255079ae23d1b1267b2e1843dc5f46a67f973cdf2a3523dff/websockets-17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60", size = 224520, upload-time = "2026-08-26T14:56:05.521Z" }, + { url = "https://files.pythonhosted.org/packages/06/9a/aef0792731df4352e5f417369b532b3325fe434765ca90c193f594ae1e67/websockets-17.1-cp312-cp312-win32.whl", hash = "sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb", size = 217485, upload-time = "2026-08-26T14:56:06.715Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/493ecfdaf32898e5ea24dc900e33e5e317f9662d5d9ab2d44b2e111b4e1c/websockets-17.1-cp312-cp312-win_amd64.whl", hash = "sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec", size = 217786, upload-time = "2026-08-26T14:56:08.055Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/91954e2f7876f74ce1213e9b92c65a63b559cc4b942a931ebeb351cd9932/websockets-17.1-cp312-cp312-win_arm64.whl", hash = "sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729", size = 217711, upload-time = "2026-08-26T14:56:09.267Z" }, + { url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" }, + { url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" }, + { url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" }, + { url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" }, + { url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" }, + { url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" }, +] + +[[package]] +name = "yarl" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/16/e8be8e2fb175bbf41a0680381a319f1199fae256588241a2ac8677eafb49/yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7", size = 246245, upload-time = "2026-09-15T19:35:02.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/b3/cd32ac66ae622b854c2df0ac52106dda220d361b65a64fde7d5b3684aa3f/yarl-1.25.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4", size = 144798, upload-time = "2026-09-15T19:31:01.821Z" }, + { url = "https://files.pythonhosted.org/packages/61/fb/a2c52a8007c2051ba74662afb112ecf3d00346af4c25e33df9d80fd14fb8/yarl-1.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d", size = 104583, upload-time = "2026-09-15T19:31:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/be/dd/ee38aec8e09fdf957e50d4085453fbe202f56c6c3b4cf07b81cdb4f09ee9/yarl-1.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6", size = 104325, upload-time = "2026-09-15T19:31:06.338Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b3/058dbfb1857b484c9cf9cc135659f50b85ce66e03c99e44dc2f7b6161f55/yarl-1.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58", size = 115358, upload-time = "2026-09-15T19:31:08.593Z" }, + { url = "https://files.pythonhosted.org/packages/db/39/29693446cf0cf6b15a0e2f75a5d40f93c56819b05b0622196f45e95b5cc0/yarl-1.25.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad", size = 107658, upload-time = "2026-09-15T19:31:10.802Z" }, + { url = "https://files.pythonhosted.org/packages/86/b3/3c4dd7e1af43b931fba95e0a722737f2ea94a6d199c802585282831d7abd/yarl-1.25.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40", size = 122660, upload-time = "2026-09-15T19:31:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b5/1b60dbc3cfc9c5712b15148c206748f2bc93953ffdbe25ea75b63dfc89c9/yarl-1.25.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c", size = 126506, upload-time = "2026-09-15T19:31:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7b/ca212cbe170ac8b96e45317ecbcf9c3c3ecf0cdec98d5b088a9c4088929b/yarl-1.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d", size = 117050, upload-time = "2026-09-15T19:31:17.241Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c3/72b4938cdbe619ad71ac156182faef4908846b84dc3ca4dbb4c4e6f84014/yarl-1.25.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54", size = 114174, upload-time = "2026-09-15T19:31:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/e8/43/268717870f9ba0cc9701a95181587f6dc8c5f387aab4aeecc83158f38a79/yarl-1.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f", size = 114944, upload-time = "2026-09-15T19:31:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/baa5bf504d51fe062c4bcaf62936da97fffb43285978d0b39984824231fd/yarl-1.25.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc", size = 108263, upload-time = "2026-09-15T19:31:23.388Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/779a2ed9e0152a601a27039bed9aead3f0b79797a67e2c44bfa444622dd8/yarl-1.25.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d", size = 122184, upload-time = "2026-09-15T19:31:25.343Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1f/118e9e5b8f07694d63fd3222e801d7782270003f1a222aa798df3f8d5933/yarl-1.25.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1", size = 114001, upload-time = "2026-09-15T19:31:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/a4cf1cf372313734b17996d4007f9f73596e7a178b9485802e5494ecf484/yarl-1.25.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68", size = 120565, upload-time = "2026-09-15T19:31:29.47Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ad94f93ca731bc9e44d321833ab96b82a4f9f5f63cf773f81a4aeea5ecc1/yarl-1.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707", size = 117060, upload-time = "2026-09-15T19:31:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/bb/cc/51a7b4abf4ac593b8e7eb3794b28e5a35ae26eed8bc04787628d215af82f/yarl-1.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03", size = 102593, upload-time = "2026-09-15T19:31:33.329Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/0941a6b93a58b59a1ec75e5333bf06929b671309c43c0cd201c172d9c39f/yarl-1.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85", size = 97697, upload-time = "2026-09-15T19:31:35.855Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/2f3129bbcc9a5c8ba12cc2b29d8060a3bab9c8043c456cfd4b5ca3188890/yarl-1.25.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5", size = 143623, upload-time = "2026-09-15T19:31:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f1bc3390fdca352826676b531d0712736f156919090206700421d46b2c37/yarl-1.25.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26", size = 104011, upload-time = "2026-09-15T19:31:40.25Z" }, + { url = "https://files.pythonhosted.org/packages/a8/aa/50acc5c3e5da04172ae3c281c75405af4d2ca911e16120ab0563f4dffb66/yarl-1.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec", size = 103677, upload-time = "2026-09-15T19:31:42.46Z" }, + { url = "https://files.pythonhosted.org/packages/30/d2/7d1e0ab9f8390e1fbcede5a6dbf70d23c96ad09b8c5567f3a514d1ddb0e2/yarl-1.25.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc", size = 115392, upload-time = "2026-09-15T19:31:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/71/e1/5ba1e3a2a22139213655e760919038e8ed7e2d4a99826d0bbddb3beb96e5/yarl-1.25.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184", size = 107493, upload-time = "2026-09-15T19:31:46.273Z" }, + { url = "https://files.pythonhosted.org/packages/f5/53/780653d5e0f73831f467cf13548912e5eec97f21dc49fc8daf21da027df4/yarl-1.25.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651", size = 122537, upload-time = "2026-09-15T19:31:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/03/92/d54fa70236c6036271c9c9c09fd978df5cbe3ef49ef6c46e9b833476d215/yarl-1.25.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df", size = 126170, upload-time = "2026-09-15T19:31:50.872Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b7/a82a49bf88340b837ef6972b508a1604ae377b9e6904b46b10cf5f1cf925/yarl-1.25.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab", size = 117012, upload-time = "2026-09-15T19:31:53.189Z" }, + { url = "https://files.pythonhosted.org/packages/ef/78/5d684b411e3f3602464ee9b538db48205038f8605872985f61efb809ced0/yarl-1.25.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3", size = 114950, upload-time = "2026-09-15T19:31:55.694Z" }, + { url = "https://files.pythonhosted.org/packages/2f/11/51d82b852c64f7fad0fc7a7ff3031517204887e874c722bbca839c0b23ac/yarl-1.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a", size = 115428, upload-time = "2026-09-15T19:31:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/e4/49/9d1978049bf646b9ea918313926453c6901b71c92f097467777d47d36a88/yarl-1.25.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b", size = 108428, upload-time = "2026-09-15T19:32:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/43/35/7b8f1ebb45d7ec3dda7d1909bf44f458de41ef91e2937f107733582a5166/yarl-1.25.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a", size = 121961, upload-time = "2026-09-15T19:32:02.436Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/d8b689ab7ca26edeb85f6ff28812aac7a25376eefc1780e303a7bfbaceff/yarl-1.25.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a", size = 114961, upload-time = "2026-09-15T19:32:04.456Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/1a1798ea4dc6b7ee3260010a27907ebc697c95dae99817d817ed446d24aa/yarl-1.25.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98", size = 120036, upload-time = "2026-09-15T19:32:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/91/8d/b1b35ed7903da6669b1d367cb2c09436acd4ff508029b4f39a0c0c2058fc/yarl-1.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa", size = 117276, upload-time = "2026-09-15T19:32:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8f/4db01cef62caff0d7a4593ed694fb8a41a27a11158cab80d290221f13e57/yarl-1.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9", size = 101945, upload-time = "2026-09-15T19:32:11.545Z" }, + { url = "https://files.pythonhosted.org/packages/c0/5e/3ce00497c5c0babb74d4130c10c3828ccd215b4819d12020c42429f991ac/yarl-1.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe", size = 97270, upload-time = "2026-09-15T19:32:14.127Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, +] diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000000000000000000000000000000000000..fe799d6e84ea98bde4f298e991ab8aa1ceab4749 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,60 @@ +# Solomon -- runtime pins. +# +# These are the EXACT pins of the image the shipped weights were qualified and live-accepted on +# (one B200, CUDA, fp32 arithmetic). They are the image definition, not a resolved lock file: +# four packages were installed unpinned in the qualified image and their resolved versions were +# not captured at run time. They are listed below as unpinned, honestly, rather than guessed. +# +# Pinned in the qualified image: +torch==2.13.0 # observed at run time as 2.13.0+cu130; bound in the serving binding +torchvision==0.28.0 +transformers==5.17.0 +flash-linear-attention==0.5.2 + +# Installed UNPINNED in the qualified image; resolved versions were not recorded. +# Pin them yourself before relying on numerical reproducibility. +safetensors +accelerate +numpy +scipy +pillow + +# Python 3.12. +# +# The serving binding (serving/serving-binding.json) records `torch: 2.13.0+cu130` among 15 runtime +# identity keys and REFUSES TO LOAD if the live engine differs on any of them. A different torch build +# will not silently serve different numbers; it will refuse. +# +# Not required for serving: the MLX path is not qualified and is not shipped. + +# --------------------------------------------------------------------------- +# DEPENDENCY LICENCES +# +# None of these packages is redistributed in this repository. You install them from their own +# publishers, so Apache-2.0 4(a) imposes no bundled-notice obligation here and no dependency licence +# text is packaged. The summary below is provided because a reviewer will ask. +# +# Each licence is the one declared in that distribution's OWN package metadata, read from a copy on +# the maintainer's machine. Where no copy existed, the row says NOT VERIFIED rather than guessing. +# A licence read from one version is evidence about that version only. +# +# torch BSD-3-Clause — read from version 2.8.0 +# evidence: .venv-probes/lib/python3.12/site-packages/torch-2.8.0.dist-info/METADATA, field `License: BSD-3-Clause` +# torchvision NOT VERIFIED +# evidence: NOT VERIFIED: no copy of torchvision exists anywhere in this project, so no licence statement is made here. Check the distribution you install. +# transformers Apache 2.0 License +# evidence: .venv/lib/python3.12/site-packages/transformers-5.17.0.dist-info/METADATA, field `License: Apache 2.0 License` +# flash-linear-attention NOT VERIFIED +# evidence: NOT VERIFIED: no copy of flash-linear-attention exists anywhere in this project, so no licence statement is made here. Check the distribution you install. +# safetensors Apache Software License — read from version 0.8.0 +# evidence: .venv-reference/lib/python3.12/site-packages/safetensors-0.8.0.dist-info/METADATA, classifier `License :: OSI Approved :: Apache Software License` +# accelerate Apache (Apache Software License) — read from version 1.15.0 +# evidence: .venv-reference/lib/python3.12/site-packages/accelerate-1.15.0.dist-info/METADATA, field `License: Apache`, classifier `License :: OSI Approved :: Apache Software License` +# numpy BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0 — read from version 2.5.3 +# evidence: .venv-reference/lib/python3.12/site-packages/numpy-2.5.3.dist-info/METADATA, field `License-Expression` +# scipy BSD License (classifier); the METADATA `License` field carries the Enthought / SciPy Developers copyright line rather than an SPDX identifier — read from version 1.18.1 +# evidence: .venv/lib/python3.12/site-packages/scipy-1.18.1.dist-info/METADATA, classifier `License :: OSI Approved :: BSD License` +# pillow MIT-CMU — read from version 12.3.0 +# evidence: .venv/lib/python3.12/site-packages/pillow-12.3.0.dist-info/METADATA, field `License-Expression: MIT-CMU` +# +# See README.md, "Third-party dependency licences". diff --git a/serving/evidence-head.json b/serving/evidence-head.json new file mode 100644 index 0000000000000000000000000000000000000000..2844208729c08f9c7c67d08377fc569da652403d --- /dev/null +++ b/serving/evidence-head.json @@ -0,0 +1,99 @@ +{ + "arch": "scope10-evidence-mlp-v1", + "hidden_size": 5120, + "width": 512, + "input": "concat(q, u, q*u), LayerNorm(3H, elementwise_affine=False)", + "activation": "gelu", + "hidden_layer": "lm.norm(output of language_model.layers[42]) (the frozen final norm applied to a mid-layer tap)", + "branch_state": "last position of question branch, adapter active (question placement)", + "unit_pooling": "mean over document-prefill tokens overlapping the unit char range (adapter off)", + "output": "logit per unit; probability = sigmoid(logit)", + "dtype": "float32", + "variant": "refit-mlp-mid+lex", + "layer": "mid", + "layer_index": 42, + "lexical_residual": { + "alpha": 9.770263671875, + "rule": "logit = head_logit + alpha * lexical; probability = sigmoid(logit)", + "lexical": "solomon.evidence_selector.lexical: question words minus stop words, document-IDF weighted overlap per sentence unit, divided by the row max", + "question": "solomon.evidence_selector.question_of(the answer branch prompt)", + "warning": "solomon.evidence_head.load alone returns head_logit WITHOUT the lexical term; the serving selector adds it" + }, + "refit": { + "dev": { + "gold_rows": 753, + "hit@1": 0.5285524568393094, + "hit@3": 0.8167330677290837, + "hit@5": 0.8539176626826029, + "recall@5": 0.8253652058432935 + }, + "thresholded_dev": { + "precision": 0.0, + "recall": 0.0, + "f1": 0.0 + }, + "thresholds": { + "policy": "scope10-evidence-thresholds-v1", + "min_no_support_accuracy": 0.95, + "rule": "unit kept iff max(unit probs) >= absent and prob >= select (not used in serving: v1.1 serves ranked pointers, see evidence-policy.json)", + "tasks": { + "boolean": { + "select": 0.0, + "absent": 1.0, + "constraint_met": true, + "rows": 1375, + "precision": 0.0, + "recall": 0.0, + "f1": 0.0, + "tp": 0, + "fp": 0, + "fn": 291, + "no_support_rows": 1122, + "no_support_accuracy": 1.0 + }, + "multilabel": { + "select": 0.0, + "absent": 1.0, + "constraint_met": true, + "rows": 931, + "precision": 0.0, + "recall": 0.0, + "f1": 0.0, + "tp": 0, + "fp": 0, + "fn": 385, + "no_support_rows": 569, + "no_support_accuracy": 1.0 + }, + "ordered": { + "select": 0.0, + "absent": 1.0, + "constraint_met": true, + "rows": 134, + "precision": 0.0, + "recall": 0.0, + "f1": 0.0, + "tp": 0, + "fp": 0, + "fn": 120, + "no_support_rows": 36, + "no_support_accuracy": 1.0 + }, + "single": { + "select": 0.0, + "absent": 1.0, + "constraint_met": true, + "rows": 134, + "precision": 0.0, + "recall": 0.0, + "f1": 0.0, + "tp": 0, + "fp": 0, + "fn": 48, + "no_support_rows": 94, + "no_support_accuracy": 1.0 + } + } + } + } +} \ No newline at end of file diff --git a/serving/evidence-head.safetensors b/serving/evidence-head.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..b7532869a441fb36c276edc4a66bcfce6d4b7b2a --- /dev/null +++ b/serving/evidence-head.safetensors @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c +size 31461804 diff --git a/serving/evidence-policy.json b/serving/evidence-policy.json new file mode 100644 index 0000000000000000000000000000000000000000..2a7ef0ab601f702bf73c48c0cdb4f183f36a9d58 --- /dev/null +++ b/serving/evidence-policy.json @@ -0,0 +1,11 @@ +{ + "policy": "scope10-evidence-ranked-v1", + "method": "trained_relevance_head_ranked", + "top_k": 3, + "tasks": ["boolean", "multilabel", "single", "ordered"], + "suppress_when_answer_not_stated": true, + "rule": "per answer unit: the top_k sentence units by sigmoid(head_logit + alpha * lexical) (alpha from evidence-head.json lexical_residual), each returned with its score and rank; no spans when the unit's four-state readout tops at C (not stated) or a reserved-option choice tops at the reserved 'does not state this' option", + "status": "experimental: ranked pointers, faithfulness not established, no absence threshold (the refit head has no reliable no-evidence signal; the answer's not-stated state is the absence signal)", + "selected_on": "real dev hit@3 0.817 vs word overlap 0.667 (dev-selected; optimistic; no held-out test measurement)", + "fallback": "lexical_overlap_fallback for image documents, answer types outside this list, or missing prefix states" +} diff --git a/serving/readout-temperature-v3.json b/serving/readout-temperature-v3.json new file mode 100644 index 0000000000000000000000000000000000000000..88f344a38be6b3a95b2e5f17cc7dc5461e2139fc --- /dev/null +++ b/serving/readout-temperature-v3.json @@ -0,0 +1,86 @@ +{ + "application": { + "by_head_key": { + "boolean/state4": [ + "boolean", + "multilabel" + ], + "ordered/choiceS": "ordered", + "single/choiceR": "single" + }, + "choice": { + "branches": "|R single choice (single/choiceR), |S ordered choice (ordered/choiceS)", + "confidence": "the listed top-1 probability", + "expression": "probabilities = softmax(x[:n] / T)", + "note": "slice to the n listed options first, then divide by T. Reserved slots are never scored.", + "rule": "SLICE THEN TEMPER" + }, + "granularity": "per answer type. The merged yes/no head serves two types, so by_head_key maps it to both and the served type selects the scalar.", + "idempotence": "apply exactly once; the returned probability and the listed top-1 derive from the same tempered read.", + "note": "Nouls and choices are tempered DIFFERENTLY. Implement exactly as written.", + "noul": { + "branches": "yes/no and every multi-label candidate (head_key boolean/state4, the merged head)", + "confidence": "max(P(yes), 1 - P(yes))", + "expression": "z = x[0] - logsumexp(x[1:]); P(yes) = sigmoid(z / T)", + "note": "x is the full four-letter logit vector. Do NOT compute softmax(x / T)[0].", + "rule": "COLLAPSE THEN TEMPER" + } + }, + "fit": { + "calibration_file_sha256": "ea069d224501af950caaae6914d6fdf14ce6d4d2bda566c41549b1e8c30a6222", + "decision": "served at T = 1.0 for every type: the fitted scalars did not improve held-out calibration (test ECE worse in 8 of 10 type x modality cells; n-weighted 0.0212 fitted vs 0.0199 unscaled)", + "modality": "image rows use the same per-type scalar (no modality key in serving)", + "scored_heads_note": "scores were produced with a heads file whose two extra (entity/multilabel) slots were never read; its other 16 arrays are byte-identical to the shipped heads file, so the served logits are the fitted logits", + "scored_heads_sha256": "96ea51416bbeb32d991b7b38d7f0c22ff3e82539c8910284c4fb1961f2869ace", + "source": "real development documents (held out from test), this model's BF16 scores, one NLL-minimising scalar per answer type" + }, + "fitted_on_model": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "frozen": true, + "models": { + "boolean": { + "applied": false, + "fit_units": 192, + "fitted_temperature": 0.8175095705097734, + "head_key": "boolean/state4", + "kind": "noul", + "task": "boolean", + "temperature": 1.0, + "unit": "question" + }, + "multilabel": { + "applied": false, + "fit_units": 1862, + "fitted_temperature": 0.842297230286191, + "head_key": "boolean/state4", + "kind": "noul", + "task": "multilabel", + "temperature": 1.0, + "unit": "candidate noul" + }, + "ordered": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.2561869742268443, + "head_key": "ordered/choiceS", + "kind": "choice", + "task": "ordered", + "temperature": 1.0, + "unit": "question" + }, + "single": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.107722547236206, + "head_key": "single/choiceR", + "kind": "choice", + "task": "single", + "temperature": 1.0, + "unit": "question" + } + }, + "schema": "solomon-readout-temperature-v3", + "sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b" +} diff --git a/serving/selection.json b/serving/selection.json new file mode 100644 index 0000000000000000000000000000000000000000..532a740c4c9c0f5e7d5310ad1c6132372d7745e7 --- /dev/null +++ b/serving/selection.json @@ -0,0 +1,19 @@ +{ + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "binding": "serving-binding.json", + "contract": "solomon-v1", + "design": { + "ordered": "S", + "single_choice": "R" + }, + "evidence_head": { + "prefix": "evidence-head", + "thresholds": "evidence-policy.json" + }, + "frozen": true, + "heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "note": "readout mode, served design, the default (bf16) serving binding and the evidence head, named relative to this file. The binding pins the full runtime identity. For fp32 or int8 serving, pass the matching serving-binding-.json and build the engine with that precision.", + "readout": "four_collapsed", + "readout_mode": "four_collapsed", + "schema": "solomon-selection-v1" +} diff --git a/serving/service-export.json b/serving/service-export.json new file mode 100644 index 0000000000000000000000000000000000000000..7d0beb27a6393ab9d9034bb52f48c34cd835bc8e --- /dev/null +++ b/serving/service-export.json @@ -0,0 +1,11 @@ +{ + "contract": "solomon-v1", + "envelope": { + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_policy_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84" + }, + "note": "envelope hashes (calibration payload, evidence head and evidence policy). Every serving binding carries the same `envelope` and the two are checked against each other at acceptance.", + "readout_mode": "four_collapsed", + "schema": "solomon-service-export-v1" +} diff --git a/serving/serving-binding-fp32.json b/serving/serving-binding-fp32.json new file mode 100644 index 0000000000000000000000000000000000000000..9d818fc7a9879fdf5921b7649aabedc61e7cb371 --- /dev/null +++ b/serving/serving-binding-fp32.json @@ -0,0 +1,144 @@ +{ + "answer_policy": "always_answers", + "contract": "solomon-v1", + "design": { + "ordered": "S", + "single_choice": "R" + }, + "envelope": { + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_policy_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84" + }, + "head_routing": { + "entity/state4": "boolean/state4", + "multilabel/state4": "boolean/state4" + }, + "precision": "fp32", + "prompts_sha256": null, + "provenance": { + "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "calibration_fitted_on": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "identity_source": "live engine on the serving image, one capture per precision", + "temperature_values": { + "boolean": 1.0, + "multilabel": 1.0, + "ordered": 1.0, + "single": 1.0 + } + }, + "readout": "four_collapsed", + "runtime": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "answer_engine_sha256": "839442be642ac6449b32492b2ca0a4bce65de2d6095f097b053ed667304ec9fb", + "answer_heads": "boolean/state4,ordered/choiceR,ordered/choiceS,ordered/sufficiency3,ordered/threshold4,single/choiceR,single/choiceS,single/sufficiency3", + "answer_projection": "trained-semantic-head-float32", + "arithmetic": "fp32", + "backend": "cuda", + "base_engine_sha256": "28572bc9bafae9c9bd9ad42d4de2dc267ad083f126379beaf486bd4b1795641a", + "base_fingerprint": "abeeabe09b5dab7888754fb40f09756f8e0bef5971ebcb1c203018d578d190e6", + "code_sha256": "ff6779232b56469d2273cc548f4de7f7a0f6a15731c7c0986e453a2dd1180e5f", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_thresholds_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84", + "execution": "cached", + "model_sha256": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df", + "numerics": "fp32-attention-qblock128-fp64-recurrence-reference-v2", + "placement": "question", + "serving_sha256": "978a6f1d1193dd535a5fc03fa393b6421a2cd7c85bb77b1dfa796b7fcfb92836", + "torch": "2.13.0+cu130", + "trained_heads_report_sha256": null, + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "schema": "solomon-serving-binding-v1", + "sha256": "517f263000cf65457751c4fba519221d48ac550e060b241f198b007ae88c59db", + "temperatures": { + "application": { + "by_head_key": { + "boolean/state4": [ + "boolean", + "multilabel" + ], + "ordered/choiceS": "ordered", + "single/choiceR": "single" + }, + "choice": { + "branches": "|R single choice (single/choiceR), |S ordered choice (ordered/choiceS)", + "confidence": "the listed top-1 probability", + "expression": "probabilities = softmax(x[:n] / T)", + "note": "slice to the n listed options first, then divide by T. Reserved slots are never scored.", + "rule": "SLICE THEN TEMPER" + }, + "granularity": "per answer type. The merged yes/no head serves two types, so by_head_key maps it to both and the served type selects the scalar.", + "idempotence": "apply exactly once; the returned probability and the listed top-1 derive from the same tempered read.", + "note": "Nouls and choices are tempered DIFFERENTLY. Implement exactly as written.", + "noul": { + "branches": "yes/no and every multi-label candidate (head_key boolean/state4, the merged head)", + "confidence": "max(P(yes), 1 - P(yes))", + "expression": "z = x[0] - logsumexp(x[1:]); P(yes) = sigmoid(z / T)", + "note": "x is the full four-letter logit vector. Do NOT compute softmax(x / T)[0].", + "rule": "COLLAPSE THEN TEMPER" + } + }, + "fit": { + "calibration_file_sha256": "ea069d224501af950caaae6914d6fdf14ce6d4d2bda566c41549b1e8c30a6222", + "decision": "served at T = 1.0 for every type: the fitted scalars did not improve held-out calibration (test ECE worse in 8 of 10 type x modality cells; n-weighted 0.0212 fitted vs 0.0199 unscaled)", + "modality": "image rows use the same per-type scalar (no modality key in serving)", + "scored_heads_note": "scores were produced with a heads file whose two extra (entity/multilabel) slots were never read; its other 16 arrays are byte-identical to the shipped heads file, so the served logits are the fitted logits", + "scored_heads_sha256": "96ea51416bbeb32d991b7b38d7f0c22ff3e82539c8910284c4fb1961f2869ace", + "source": "real development documents (held out from test), this model's BF16 scores, one NLL-minimising scalar per answer type" + }, + "fitted_on_model": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "frozen": true, + "models": { + "boolean": { + "applied": false, + "fit_units": 192, + "fitted_temperature": 0.8175095705097734, + "head_key": "boolean/state4", + "kind": "noul", + "task": "boolean", + "temperature": 1.0, + "unit": "question" + }, + "multilabel": { + "applied": false, + "fit_units": 1862, + "fitted_temperature": 0.842297230286191, + "head_key": "boolean/state4", + "kind": "noul", + "task": "multilabel", + "temperature": 1.0, + "unit": "candidate noul" + }, + "ordered": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.2561869742268443, + "head_key": "ordered/choiceS", + "kind": "choice", + "task": "ordered", + "temperature": 1.0, + "unit": "question" + }, + "single": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.107722547236206, + "head_key": "single/choiceR", + "kind": "choice", + "task": "single", + "temperature": 1.0, + "unit": "question" + } + }, + "schema": "solomon-readout-temperature-v3", + "sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b" + } +} \ No newline at end of file diff --git a/serving/serving-binding-int8.json b/serving/serving-binding-int8.json new file mode 100644 index 0000000000000000000000000000000000000000..fed73d08e3740d714db3e2d44cc347f6bd788ec0 --- /dev/null +++ b/serving/serving-binding-int8.json @@ -0,0 +1,147 @@ +{ + "answer_policy": "always_answers", + "contract": "solomon-v1", + "design": { + "ordered": "S", + "single_choice": "R" + }, + "envelope": { + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_policy_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84" + }, + "head_routing": { + "entity/state4": "boolean/state4", + "multilabel/state4": "boolean/state4" + }, + "precision": "int8", + "prompts_sha256": null, + "provenance": { + "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "calibration_fitted_on": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "identity_source": "live engine on the serving image, one capture per precision", + "temperature_values": { + "boolean": 1.0, + "multilabel": 1.0, + "ordered": 1.0, + "single": 1.0 + } + }, + "readout": "four_collapsed", + "runtime": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "answer_engine_sha256": "839442be642ac6449b32492b2ca0a4bce65de2d6095f097b053ed667304ec9fb", + "answer_heads": "boolean/state4,ordered/choiceR,ordered/choiceS,ordered/sufficiency3,ordered/threshold4,single/choiceR,single/choiceS,single/sufficiency3", + "answer_projection": "trained-semantic-head-float32", + "arithmetic": "int8-bf16-fp32-recurrence", + "backend": "cuda", + "base_engine_sha256": "28572bc9bafae9c9bd9ad42d4de2dc267ad083f126379beaf486bd4b1795641a", + "base_fingerprint": "c723d90f4a75c9ffc00ed289b9404650ac33d5183cb888f3a184b4937178d0f2", + "code_sha256": "ff6779232b56469d2273cc548f4de7f7a0f6a15731c7c0986e453a2dd1180e5f", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_thresholds_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84", + "execution": "cached", + "int8_linears": 496, + "model_sha256": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df", + "numerics": "bf16-sdpa-attention-fp32-recurrence-fla", + "placement": "question", + "precision": "int8", + "serving_sha256": "978a6f1d1193dd535a5fc03fa393b6421a2cd7c85bb77b1dfa796b7fcfb92836", + "torch": "2.13.0+cu130", + "trained_heads_report_sha256": null, + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "weights": "int8-weight-only-per-channel(torchao) language_model.layers nn.Linear; rest bf16" + }, + "schema": "solomon-serving-binding-v1", + "sha256": "b550254777ceb3f53e7e10e15f7dc9f80f620ddd9c1ed69592190da5f2107f16", + "temperatures": { + "application": { + "by_head_key": { + "boolean/state4": [ + "boolean", + "multilabel" + ], + "ordered/choiceS": "ordered", + "single/choiceR": "single" + }, + "choice": { + "branches": "|R single choice (single/choiceR), |S ordered choice (ordered/choiceS)", + "confidence": "the listed top-1 probability", + "expression": "probabilities = softmax(x[:n] / T)", + "note": "slice to the n listed options first, then divide by T. Reserved slots are never scored.", + "rule": "SLICE THEN TEMPER" + }, + "granularity": "per answer type. The merged yes/no head serves two types, so by_head_key maps it to both and the served type selects the scalar.", + "idempotence": "apply exactly once; the returned probability and the listed top-1 derive from the same tempered read.", + "note": "Nouls and choices are tempered DIFFERENTLY. Implement exactly as written.", + "noul": { + "branches": "yes/no and every multi-label candidate (head_key boolean/state4, the merged head)", + "confidence": "max(P(yes), 1 - P(yes))", + "expression": "z = x[0] - logsumexp(x[1:]); P(yes) = sigmoid(z / T)", + "note": "x is the full four-letter logit vector. Do NOT compute softmax(x / T)[0].", + "rule": "COLLAPSE THEN TEMPER" + } + }, + "fit": { + "calibration_file_sha256": "ea069d224501af950caaae6914d6fdf14ce6d4d2bda566c41549b1e8c30a6222", + "decision": "served at T = 1.0 for every type: the fitted scalars did not improve held-out calibration (test ECE worse in 8 of 10 type x modality cells; n-weighted 0.0212 fitted vs 0.0199 unscaled)", + "modality": "image rows use the same per-type scalar (no modality key in serving)", + "scored_heads_note": "scores were produced with a heads file whose two extra (entity/multilabel) slots were never read; its other 16 arrays are byte-identical to the shipped heads file, so the served logits are the fitted logits", + "scored_heads_sha256": "96ea51416bbeb32d991b7b38d7f0c22ff3e82539c8910284c4fb1961f2869ace", + "source": "real development documents (held out from test), this model's BF16 scores, one NLL-minimising scalar per answer type" + }, + "fitted_on_model": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "frozen": true, + "models": { + "boolean": { + "applied": false, + "fit_units": 192, + "fitted_temperature": 0.8175095705097734, + "head_key": "boolean/state4", + "kind": "noul", + "task": "boolean", + "temperature": 1.0, + "unit": "question" + }, + "multilabel": { + "applied": false, + "fit_units": 1862, + "fitted_temperature": 0.842297230286191, + "head_key": "boolean/state4", + "kind": "noul", + "task": "multilabel", + "temperature": 1.0, + "unit": "candidate noul" + }, + "ordered": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.2561869742268443, + "head_key": "ordered/choiceS", + "kind": "choice", + "task": "ordered", + "temperature": 1.0, + "unit": "question" + }, + "single": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.107722547236206, + "head_key": "single/choiceR", + "kind": "choice", + "task": "single", + "temperature": 1.0, + "unit": "question" + } + }, + "schema": "solomon-readout-temperature-v3", + "sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b" + } +} \ No newline at end of file diff --git a/serving/serving-binding.json b/serving/serving-binding.json new file mode 100644 index 0000000000000000000000000000000000000000..33654fc359b7b8f4ed86c75b15fc31483ba2decb --- /dev/null +++ b/serving/serving-binding.json @@ -0,0 +1,146 @@ +{ + "answer_policy": "always_answers", + "contract": "solomon-v1", + "design": { + "ordered": "S", + "single_choice": "R" + }, + "envelope": { + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_policy_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84" + }, + "head_routing": { + "entity/state4": "boolean/state4", + "multilabel/state4": "boolean/state4" + }, + "precision": "bf16", + "prompts_sha256": null, + "provenance": { + "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9", + "calibration_fitted_on": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b", + "identity_source": "live engine on the serving image, one capture per precision", + "temperature_values": { + "boolean": 1.0, + "multilabel": 1.0, + "ordered": 1.0, + "single": 1.0 + } + }, + "readout": "four_collapsed", + "runtime": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "answer_engine_sha256": "839442be642ac6449b32492b2ca0a4bce65de2d6095f097b053ed667304ec9fb", + "answer_heads": "boolean/state4,ordered/choiceR,ordered/choiceS,ordered/sufficiency3,ordered/threshold4,single/choiceR,single/choiceS,single/sufficiency3", + "answer_projection": "trained-semantic-head-float32", + "arithmetic": "bf16-fp32-recurrence", + "backend": "cuda", + "base_engine_sha256": "28572bc9bafae9c9bd9ad42d4de2dc267ad083f126379beaf486bd4b1795641a", + "base_fingerprint": "ff3728053ed11ba8bfabca47eb048a51657e3228a3beef41743605e5916633d6", + "code_sha256": "ff6779232b56469d2273cc548f4de7f7a0f6a15731c7c0986e453a2dd1180e5f", + "evidence_head_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c", + "evidence_thresholds_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84", + "execution": "cached", + "model_sha256": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df", + "numerics": "bf16-sdpa-attention-fp32-recurrence-fla", + "placement": "question", + "precision": "bf16", + "serving_sha256": "978a6f1d1193dd535a5fc03fa393b6421a2cd7c85bb77b1dfa796b7fcfb92836", + "torch": "2.13.0+cu130", + "trained_heads_report_sha256": null, + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab", + "weights": "bf16" + }, + "schema": "solomon-serving-binding-v1", + "sha256": "0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8", + "temperatures": { + "application": { + "by_head_key": { + "boolean/state4": [ + "boolean", + "multilabel" + ], + "ordered/choiceS": "ordered", + "single/choiceR": "single" + }, + "choice": { + "branches": "|R single choice (single/choiceR), |S ordered choice (ordered/choiceS)", + "confidence": "the listed top-1 probability", + "expression": "probabilities = softmax(x[:n] / T)", + "note": "slice to the n listed options first, then divide by T. Reserved slots are never scored.", + "rule": "SLICE THEN TEMPER" + }, + "granularity": "per answer type. The merged yes/no head serves two types, so by_head_key maps it to both and the served type selects the scalar.", + "idempotence": "apply exactly once; the returned probability and the listed top-1 derive from the same tempered read.", + "note": "Nouls and choices are tempered DIFFERENTLY. Implement exactly as written.", + "noul": { + "branches": "yes/no and every multi-label candidate (head_key boolean/state4, the merged head)", + "confidence": "max(P(yes), 1 - P(yes))", + "expression": "z = x[0] - logsumexp(x[1:]); P(yes) = sigmoid(z / T)", + "note": "x is the full four-letter logit vector. Do NOT compute softmax(x / T)[0].", + "rule": "COLLAPSE THEN TEMPER" + } + }, + "fit": { + "calibration_file_sha256": "ea069d224501af950caaae6914d6fdf14ce6d4d2bda566c41549b1e8c30a6222", + "decision": "served at T = 1.0 for every type: the fitted scalars did not improve held-out calibration (test ECE worse in 8 of 10 type x modality cells; n-weighted 0.0212 fitted vs 0.0199 unscaled)", + "modality": "image rows use the same per-type scalar (no modality key in serving)", + "scored_heads_note": "scores were produced with a heads file whose two extra (entity/multilabel) slots were never read; its other 16 arrays are byte-identical to the shipped heads file, so the served logits are the fitted logits", + "scored_heads_sha256": "96ea51416bbeb32d991b7b38d7f0c22ff3e82539c8910284c4fb1961f2869ace", + "source": "real development documents (held out from test), this model's BF16 scores, one NLL-minimising scalar per answer type" + }, + "fitted_on_model": { + "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0", + "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab" + }, + "frozen": true, + "models": { + "boolean": { + "applied": false, + "fit_units": 192, + "fitted_temperature": 0.8175095705097734, + "head_key": "boolean/state4", + "kind": "noul", + "task": "boolean", + "temperature": 1.0, + "unit": "question" + }, + "multilabel": { + "applied": false, + "fit_units": 1862, + "fitted_temperature": 0.842297230286191, + "head_key": "boolean/state4", + "kind": "noul", + "task": "multilabel", + "temperature": 1.0, + "unit": "candidate noul" + }, + "ordered": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.2561869742268443, + "head_key": "ordered/choiceS", + "kind": "choice", + "task": "ordered", + "temperature": 1.0, + "unit": "question" + }, + "single": { + "applied": false, + "fit_units": 134, + "fitted_temperature": 1.107722547236206, + "head_key": "single/choiceR", + "kind": "choice", + "task": "single", + "temperature": 1.0, + "unit": "question" + } + }, + "schema": "solomon-readout-temperature-v3", + "sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b" + } +} \ No newline at end of file diff --git a/src/solomon/__init__.py b/src/solomon/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7f3264fdb14e4f8c3f9180e408905e89cfc53881 --- /dev/null +++ b/src/solomon/__init__.py @@ -0,0 +1,17 @@ +"""Solomon: a document plus structured questions in, one probability per decision out. + + from solomon import service, api + layer = service.service(store, engine, selection='serving/selection.json') + server = api.serve(layer) + +Reading order. `service` is the serving contract and the layer that reads the model's own letter logits. +`binding` is the identity check that refuses to serve a stack that is not the one that was measured, and +`calibration` the one positive scalar per answer type it carries. `semantics` and `reliability` are what a +probability means here; `readout` turns letter logits into one. `engine` is the served engine, built on +`engine_numerics` over `engine_cuda`; `heads` are the trained answer heads it reads through. +`engine_contract` and `engine_reference` hold the prompt blocks every answer type is asked with. The +`service_*` modules are the pinned chain the layer wraps, innermost first: `service_states`, +`service_checked`, `service_answers`, `service_heads`, `service_evidence`, `service_packages`, `serving`. + +Every answer type is answered. There is no abstention on this path; see `service.ORDERING_DISCLOSURE`. +""" diff --git a/src/solomon/api.py b/src/solomon/api.py new file mode 100644 index 0000000000000000000000000000000000000000..d0001644175fb6502f33613cebb820e6d8f88d2e --- /dev/null +++ b/src/solomon/api.py @@ -0,0 +1,75 @@ +"""Solomon HTTP surface for the Solomon layer (solomon/service.py). Named api.py, not http.py, so that running a +script from inside Solomon/ can never shadow the standard-library http package. + + GET /health contract, readout mode, serving binding, runtime identity + POST /states {"state": text | object | parts} -> {"state_id", ...} + POST /v1/decide {"state" | "state_id", "questions", ...} -> {"answers", "usage", ...} + POST /states//decide {"questions", ...} -> same, reusing the saved state + +v1.1: every question is answered. A decide response carries probabilities (one per candidate for multi-label +questions) and an `ordering_score`. The entity answer type was removed: ask one yes/no question per candidate, or send +the candidates as labels; an old entity request is answered 400 with that instruction. Evidence spans name the selector +that produced them (`evidence_method`: 'trained_relevance_head_ranked' -- experimental ranked pointers with scores -- or the labelled 'lexical_overlap_fallback'); it never carries `abstain`, `confidence`, a threshold or anything +that reads as a certified error rate -- `solomon.service.decide` refuses to emit such a field. `ordering_score` is +uncalibrated and rank-only; `ordering_score_semantics` says so in every response and on /health. +""" +import json +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +DECIDE_KEYS = {'state', 'state_id', 'questions', 'population', 'evidence', 'evidence_max_calls', 'evidence_detail', 'budget', 'model'} + + +def handler(service): + class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def reply(self, status, data): + raw = json.dumps(data).encode() + self.send_response(status) + self.send_header('Content-Type', 'application/json') + self.send_header('Content-Length', str(len(raw))) + self.end_headers() + self.wfile.write(raw) + + def do_GET(self): + if self.path != '/health': + return self.reply(404, {'error': 'not found'}) + return self.reply(200, service.health()) + + def do_POST(self): + try: + if self.headers.get('Origin'): + return self.reply(403, {'error': 'cross-origin requests unsupported'}) + length = int(self.headers.get('Content-Length', '0')) + if not 0 < length <= 8_000_000: + raise ValueError('request body must be 1 to 8000000 bytes') + body = json.loads(self.rfile.read(length)) + if not isinstance(body, dict): + raise ValueError('request body must be a JSON object') + path = self.path.strip('/').split('/') + if path == ['states']: + return self.reply(201, service.create(body.get('state', body.get('document')))) + if path == ['v1', 'decide'] or (len(path) == 3 and path[0] == 'states' and path[2] == 'decide'): + unknown = set(body) - DECIDE_KEYS + if unknown: + raise ValueError('unknown request fields: ' + ', '.join(sorted(unknown))) + body.pop('model', None) # some clients send a model name; one model is served here, so it is accepted and ignored + if len(path) == 3: + if 'state' in body or 'state_id' in body: + raise ValueError('state is given by the URL') + body['state_id'] = path[1] + return self.reply(200, service.decide(**body)) + return self.reply(404, {'error': 'not found'}) + except (ValueError, KeyError, TypeError) as exc: + return self.reply(400, {'error': str(exc)}) + except FileNotFoundError: + return self.reply(404, {'error': 'state or input file not found'}) + return Handler + + +def serve(service, host='127.0.0.1', port=0): + """Start a ThreadingHTTPServer (caller runs serve_forever / shutdown).""" + server = ThreadingHTTPServer((host, port), handler(service)) + server.service = service + return server diff --git a/src/solomon/binding.py b/src/solomon/binding.py new file mode 100644 index 0000000000000000000000000000000000000000..bf79eb723581df9569c2f03b9378c12952dd2b35 --- /dev/null +++ b/src/solomon/binding.py @@ -0,0 +1,230 @@ +"""Serving identity: which readout is served, and proof that it is the one that was measured. + +Split out of `solomon/service.py` unchanged. Three things live here: + + * the frozen selection file (readout mode, served design, where the binding is), + * the two-letter prompt identity, and + * `RuntimeBinding` -- the one safety property this release keeps. A binding carries the runtime identity + of the model that was scored, and every key of it must equal the live engine's or the service refuses + to load. The binding also carries the calibration, so a temperature cannot drift from the stack it was + fitted against, and it refuses a calibration fitted on a different model. +""" +import copy +import hashlib +import importlib +import json +from pathlib import Path + +from solomon.calibration import NoTemperature, ROOT, digest, load_temperature +from solomon.engine_contract import BOOLEAN_TASK, LABEL + +SELECTION = ROOT / 'serving/selection.json' +DEFAULT_BINDING = ROOT / 'serving/serving-binding.json' +DESIGN_FREEZE = ROOT / 'serving/design-freeze.json' +CONTRACT = 'solomon-v1' +MODES = ('four_collapsed', 'two_letter') +BINDING_SCHEMA = 'solomon-serving-binding-v1' +# ------------------------------------------------------------------ selection and prompts +def load_selection(path=SELECTION): + """The frozen selection file -> {'mode', 'design', 'prompts', 'binding', 'sha256'}. + + Accepted keys: 'readout', 'readout_mode' or 'mode' (four_collapsed | two_letter). + Optional: 'design' {single_choice, ordered} override, 'two_letter_prompts' (inline headers dict or + 'module:ATTRIBUTE'), 'binding' (path to the Solomon serving-binding.json: absolute, or relative to the + selection file's own directory, else to the project root). + """ + path = Path(path) + if not path.exists(): + raise ValueError(f'Solomon selection not found: {path} (pass mode= explicitly for development use)') + raw = json.loads(path.read_text()) + mode = raw.get('readout') or raw.get('readout_mode') or raw.get('mode') + if mode not in MODES: + raise ValueError('selection.json names no readout mode (readout / readout_mode / mode)') + return {'mode': mode, 'design': raw.get('design'), 'prompts': raw.get('two_letter_prompts'), 'binding': raw.get('binding'), + 'evidence_head': raw.get('evidence_head'), + 'sha256': hashlib.sha256(path.read_bytes()).hexdigest(), 'path': str(path), 'dir': str(path.resolve().parent)} + + +def default_design(mode): + """four_collapsed serves the frozen design (single R, ordered S); two_letter was trained on S rows only.""" + if mode == 'two_letter': + return {'single_choice': 'S', 'ordered': 'S'} + frozen = json.loads(DESIGN_FREEZE.read_text()) if DESIGN_FREEZE.exists() else {'single_choice': 'R', 'ordered': 'S'} + return {'single_choice': frozen['single_choice'], 'ordered': frozen['ordered']} + + +class TwoLetterPrompts: + """Header swap four-state -> two-letter. headers: {'boolean': str, 'label': str, optional 'entity': str}. + + The four-state block is HEADER + rest (question / label / answer cue); only HEADER changes, so a two-letter block + is byte-identical to what training used iff the training builder does the same swap (checked by `check_parity`). + """ + FOUR = {'boolean': BOOLEAN_TASK, 'label': LABEL} + + def __init__(self, headers): + if not isinstance(headers, dict) or not {'boolean', 'label'} <= set(headers) or set(headers) - {'boolean', 'label', 'entity'}: + raise ValueError('two-letter prompts need headers for boolean and label (optional entity)') + if any(not isinstance(v, str) or not v.strip() for v in headers.values()): + raise ValueError('two-letter headers must be nonempty strings') + if any(v.startswith(four) or four.startswith(v) for v in headers.values() for four in self.FOUR.values()): + raise ValueError('two-letter header must differ from the four-state headers') + self.headers = dict(headers) + self.sha256 = digest(self.headers) + + def header(self, task, kind): + return self.headers.get('entity', self.headers['boolean']) if (kind == 'boolean' and task == 'entity') else self.headers[kind] + + def rewrite(self, task, block): + """Four-state block -> (two-letter block, 2), or None when the block is not a four-state Noul block.""" + for kind, four in self.FOUR.items(): + if block.startswith(four) and (kind == 'label') == (task == 'multilabel') and task in ('boolean', 'entity', 'multilabel'): + return self.header(task, kind) + block[len(four):], 2 + return None + + def check_parity(self, rows): + """rows: two-letter training/eval rows {'task', 'block'} (Noul tasks). Returns mismatching row indices.""" + bad = [] + for i, row in enumerate(rows): + task, block = row['task'], row['block'] + kind = 'label' if task == 'multilabel' else 'boolean' + head = self.header(task, kind) + if not block.startswith(head) or self.rewrite(task, self.FOUR[kind] + block[len(head):]) != (block, 2): + bad.append(i) + return bad + + +def load_prompts(spec): + """spec: None (solomon/prompts_two_letter.py, the headers the two-letter readout was trained on), inline headers dict, or 'module:ATTRIBUTE'.""" + if spec is None: + from solomon import prompts_two_letter as p9 + loaded = TwoLetterPrompts({'boolean': p9.BOOLEAN_TASK2, 'label': p9.LABEL2}) + loaded.sha256 = hashlib.sha256(Path(p9.__file__).read_bytes()).hexdigest() # = the prompts_sha256 a two_letter binding pins + return loaded + if isinstance(spec, str): + module, _, attr = spec.partition(':') + spec = getattr(importlib.import_module(module), attr or 'TWO_LETTER_HEADERS') + return spec if isinstance(spec, TwoLetterPrompts) else TwoLetterPrompts(spec) + + +class UnboundServing: + """Development / no-binding-file path. Still answers everything; it simply claims no bound identity.""" + status = 'unbound' + schema = None + sha256 = None + + def __init__(self, reason='unbound: no Solomon serving binding is loaded'): + self.reason = reason + self.bound_keys = [] + self.calibration = NoTemperature() + self.head_routing = None + + def describe(self): + return {'status': self.status, 'schema': None, 'sha256': None, 'bound_runtime_keys': [], + 'reason': self.reason, 'calibration': self.calibration.describe()} + + +class RuntimeBinding: + """The one safety property Solomon v1.1 keeps: the stack being served is the stack that was measured. + + A `solomon-serving-binding-v1` artifact carries the runtime identity of the scored model (15 keys for this + release: every ServiceEngine identity key except the recomputed `fingerprint`). Every one of them must equal the + live engine identity or construction raises, so the service refuses to load on drift. It carries NO thresholds, + NO per-task status, NO temperatures and NO fitted correctness head: there is nothing here that could gate an + answer, because nothing gates an answer. + """ + status = 'bound' + schema = BINDING_SCHEMA + # Without these two the binding would not pin the model at all; a binding that omits them is refused. + REQUIRED = ('adapter_sha256', 'trained_heads_sha256') + # Keys that must never appear in a serving binding: they are the machinery of the abstention path (v1.1). + # `temperatures` is deliberately NOT here -- a per-task scalar is a calibration parameter, not a decision, and + # carrying it inside the binding is what stops it drifting from the stack it was fitted against. + REFUSED = ('tasks', 'thresholds', 'confidence', 'correctness', 'abstention', 'policy', 'status') + # A temperature is fitted on ONE model's logits and is meaningless on another, even though the numbers would + # look identical. So a non-trivial calibration must record the model it was fitted on, and that model must be + # the one this binding serves. + CALIBRATION_BOUND_KEYS = ('adapter_sha256', 'trained_heads_sha256') + + def __init__(self, binding, *, mode, runtime, design, prompts_sha256=None): + if not isinstance(binding, dict) or binding.get('schema') != BINDING_SCHEMA: + raise ValueError('Solomon serving binding schema required') + if binding.get('sha256') != digest({k: v for k, v in binding.items() if k != 'sha256'}): + raise ValueError('Solomon serving binding checksum mismatch') + present = [k for k in self.REFUSED if k in binding] + if present: + raise ValueError('serving binding carries removed abstention machinery: ' + ', '.join(present)) + if binding.get('contract') not in (None, CONTRACT): + raise ValueError('Solomon serving binding was issued for a different serving contract: ' + str(binding.get('contract'))) + if binding.get('readout') != mode: + raise ValueError('Solomon serving binding was built for a different readout mode') + bound = binding.get('runtime') or {} + if not isinstance(bound, dict) or any(k not in bound for k in self.REQUIRED): + raise ValueError('serving binding must bind at least ' + ', '.join(self.REQUIRED)) + drift = sorted(k for k, v in bound.items() if runtime.get(k) != v) + if drift: + raise ValueError('Solomon serving binding runtime identity mismatch: ' + ', '.join(drift)) + if binding.get('design') is not None and binding['design'] != design: + raise ValueError('Solomon serving binding design mismatch') + if mode == 'two_letter' and binding.get('prompts_sha256') != prompts_sha256: + raise ValueError('Solomon serving binding prompt identity mismatch') + routing = binding.get('head_routing') + if routing is not None and (not isinstance(routing, dict) or any(not isinstance(k, str) or not isinstance(v, str) + for k, v in routing.items())): + raise ValueError('serving binding head_routing must map head_key -> head_key') + self.head_routing = dict(routing) if routing is not None else None + self.binding = copy.deepcopy(binding) + self.sha256 = binding['sha256'] + self.bound_keys = sorted(bound) + # Covered by this binding's checksum and by the identity check above, so the temperatures cannot drift + # from the runtime they were measured on. + self.calibration = load_temperature(binding.get('temperatures')) + provenance = binding.get('provenance') or {} + if self.calibration.file_sha256 is None: # embedded copy: take the file hash from the provenance + self.calibration.file_sha256 = provenance.get('calibration_file_sha256') + self._check_calibration_model(bound, provenance) + + def _check_calibration_model(self, bound, provenance): + """Refuse a calibration inherited from a different model. + + The 15-key identity check already proves the SERVING stack is the scored one. It cannot see this: a + temperature fitted on model A and copied onto a binding for model B has the same numbers and passes every + value check there is. The only thing that distinguishes them is which model's logits the scalars were + fitted against, so that has to be recorded and matched. + """ + if self.calibration.is_identity(): + return # T = 1.0 everywhere is a no-op; safe on any model + fitted = provenance.get('calibration_fitted_on') or {} + missing = [k for k in self.CALIBRATION_BOUND_KEYS if not fitted.get(k)] + if missing: + raise ValueError( + 'serving binding carries temperatures but no record of the model they were fitted on (missing ' + + ', '.join('provenance.calibration_fitted_on.' + k for k in missing) + '). A temperature fitted ' + 'on one model is meaningless on another, so an unattributed calibration is refused. Refit on this ' + 'model, or serve at T = 1.0.') + for key in self.CALIBRATION_BOUND_KEYS: + if bound.get(key) != fitted[key]: + raise ValueError( + f'calibration was fitted on {key} {fitted[key]}, binding carries {key} {bound.get(key)}. ' + 'Temperatures fitted on one model do not transfer to another even when the numbers match; ' + 'refit on this model against the error-rich fit panel, or serve it at T = 1.0.') + + def describe(self): + return {'status': self.status, 'schema': self.schema, 'sha256': self.sha256, + 'bound_runtime_keys': list(self.bound_keys), 'calibration': self.calibration.describe(), + **({'head_routing': dict(self.head_routing)} if self.head_routing is not None else {})} + + +def load_binding(path, *, mode, runtime, design, prompts_sha256=None): + """None / missing file -> UnboundServing; a present but invalid or drifted file fails closed (raises).""" + if isinstance(path, (UnboundServing, RuntimeBinding)) or hasattr(path, 'describe'): + return path + if isinstance(path, dict): + return RuntimeBinding(path, mode=mode, runtime=runtime, design=design, prompts_sha256=prompts_sha256) + path = Path(path) if path is not None else DEFAULT_BINDING + if not path.is_absolute(): + path = ROOT / path + if not path.exists(): + return UnboundServing(f'unbound: no Solomon serving binding at {path.relative_to(ROOT) if path.is_relative_to(ROOT) else path}') + return RuntimeBinding(json.loads(path.read_text()), mode=mode, runtime=runtime, design=design, prompts_sha256=prompts_sha256) + + diff --git a/src/solomon/calibration.py b/src/solomon/calibration.py new file mode 100644 index 0000000000000000000000000000000000000000..89d381fddd5982ab569f17af645f25908798fe46 --- /dev/null +++ b/src/solomon/calibration.py @@ -0,0 +1,191 @@ +"""The readout calibration: one positive scalar per answer type, and nothing else. + +Split out of `solomon/service.py` unchanged. A temperature rescales a probability; it cannot reorder one, +so nothing here can decline an answer. `ReadoutTemperature` refuses an artifact that carries anything with +structure beyond the five scalars and the head_key map, which is what a smuggled decision rule would look +like. +""" +import copy +import hashlib +import json +import math +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + +# Per-task readout temperature. Not a gate and not a decision: one positive scalar per task, applied to the logits +# before the softmax, which is the only parameter that fixes the MAGNITUDE of the returned probability. It cannot +# change any ranking (a positive temperature is monotone), so it cannot reintroduce abstention by another name. +TEMPERATURE_SCHEMA = 'solomon-readout-temperature-v2' +# v1 is accepted structurally so an older artifact still LOADS rather than crashing; which fit may actually SHIP +# is decided by the release tooling:RULED_TEMPERATURES, on values rather than on a version string. +TEMPERATURE_SCHEMAS = ('solomon-readout-temperature-v3', 'solomon-readout-temperature-v2', 'solomon-readout-temperature-v1') +TEMPERATURE = 1.0 # served when no calibration artifact is bound +TEMPERATURE_TASKS = ('boolean', 'entity', 'multilabel', 'single', 'ordered') +# v1.1: the answer types the public API serves. A v3 temperature artifact must cover exactly these; it MAY also carry +# an entity scalar (the fit still reports the type), which is recorded and never served. +SERVED_TASKS = ('boolean', 'multilabel', 'single', 'ordered') +def digest(value): + return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()).hexdigest() + + +class ReadoutTemperature: + """A frozen `solomon-readout-temperature-v1`: one positive scalar per task, nothing else. + + This is a fitted artifact, and that is deliberate. The object removed from this path today was a + 5,125-parameter logistic regression over the full hidden state fitted on ~1 effective negative, whose + regularisation was chosen on a panel containing zero errors; it separated perfectly and did not replicate. + This is five scalars fitted on thousands of units, stable across the two disjoint fit panels, and it decides + nothing -- it rescales a number. The risk classes are not comparable. It is still bound by the runtime + identity like everything else, so it cannot drift from the stack it was measured on. + + Application (must match abstention_refit/readout.py, which is what the scalars were fitted on): + Noul -> p_yes(t) = sigmoid(z/t) on the COLLAPSED binary logit z (solomon.semantics.p_yes) + Choice -> softmax(logits[:n]/t) over the listed options only (solomon.semantics.listed_probs) + """ + # Nothing that could gate, rank-order differently, or make a certified claim may ride along with the scalars. + # Prose is fine: the artifact documents itself, and `application.noul.confidence` is a sentence describing + # what a caller reads off the tempered probability, not a fitted object. So a refused name is only refused + # when it carries STRUCTURE (numbers, arrays, objects) -- that is what a smuggled head would look like. + REFUSED = ('thresholds', 'threshold', 'weights', 'mean', 'scale', 'confidence', 'correctness', + 'feature_recipe', 'abstention', 'coverage', 'error_upper') + REFUSED_ANYWHERE = ('weights', 'thresholds', 'correctness', 'abstention') + + def __init__(self, artifact, file_sha256=None): + if not isinstance(artifact, dict) or artifact.get('schema') not in TEMPERATURE_SCHEMAS: + raise ValueError('one of ' + ', '.join(TEMPERATURE_SCHEMAS) + ' required') + if artifact.get('frozen') is not True: + raise ValueError('readout temperature artifact must be frozen') + stated = artifact.get('sha256') + if stated != hashlib.sha256(json.dumps({k: v for k, v in artifact.items() if k != 'sha256'}, + sort_keys=True, separators=(',', ':')).encode()).hexdigest(): + raise ValueError('readout temperature checksum mismatch') + bad = sorted(_structural_keys(artifact) & set(self.REFUSED)) + if bad: + raise ValueError('readout temperature artifact carries non-temperature machinery: ' + ', '.join(bad)) + anywhere = sorted(_all_keys(artifact) & set(self.REFUSED_ANYWHERE)) + if anywhere: + raise ValueError('readout temperature artifact names forbidden machinery: ' + ', '.join(anywhere)) + if set(artifact) & set(self.REFUSED): + raise ValueError('readout temperature artifact carries a refused top-level field') + models = artifact.get('models') or {} + if artifact['schema'] == TEMPERATURE_SCHEMAS[0]: + # v3 (v1.1): exactly the served types, plus an optional (never served) entity scalar. + if not set(SERVED_TASKS) <= set(models) or set(models) - set(TEMPERATURE_TASKS): + raise ValueError('readout temperature v3 needs one model per served task: ' + ', '.join(sorted(SERVED_TASKS)) + + ' (entity optional)') + elif set(models) != set(TEMPERATURE_TASKS): + raise ValueError('readout temperature needs exactly one model per task: ' + ', '.join(sorted(TEMPERATURE_TASKS))) + values = {} + for task, model in models.items(): + t = model.get('temperature') + if isinstance(t, bool) or not isinstance(t, (int, float)) or not math.isfinite(t) or not 0 < t <= 20: + raise ValueError(f'{task}: temperature must be a finite scalar in (0, 20]') + values[task] = float(t) + self.artifact = copy.deepcopy(artifact) + self.values = values + # TWO DIFFERENT HASHES, and confusing them makes a sound provenance chain look tampered with: + # payload_sha256 -- the artifact's own `sha256` field, over its contents with that field removed. A + # self-referential field cannot hash the file containing it, so this is what it covers, + # and it is what this class verifies (above) and what survives being embedded in the + # serving binding, where there is no file at all. + # file_sha256 -- sha256 of the .json file on disk. This is what `shasum -a 256 ` returns and + # what a reader verifying the standalone artifact will compute. Known only when we + # loaded from a path; recorded in the binding's provenance either way. + self.payload_sha256 = stated + self.file_sha256 = file_sha256 + # The artifact indexes by head_key, which is the field the runtime carries on every branch. The mapping to + # the five tasks is one-to-one, so a served branch whose head_key is not in the map is a readout the + # scalars were never fitted against (e.g. a different choice design) -- that must fail, not silently use 1.0. + # v3: a head_key may serve several types (the merged yes/no head serves boolean and multilabel), each with its + # own scalar; the map then names a list. v1/v2: one type per head_key. + self.by_head_key = dict((artifact.get('application') or {}).get('by_head_key') or {}) + for key, task in self.by_head_key.items(): + for t in (task if isinstance(task, list) else [task]): + if t not in values: + raise ValueError(f'readout temperature by_head_key maps {key} to unknown task {t}') + + def is_identity(self): + """All temperatures exactly 1.0: a no-op, and therefore safe on any model.""" + return all(v == 1.0 for v in self.values.values()) + + def temperature(self, task, head_keys=()): + if task not in self.values: + raise ValueError(f'readout temperature has no scalar for task {task}') + for key in {k for k in head_keys if k}: + if self.by_head_key and key not in self.by_head_key: + raise ValueError(f'readout temperature was not fitted for head_key {key!r} (fitted: ' + + ', '.join(sorted(self.by_head_key)) + '); refusing to serve it uncalibrated') + fitted = self.by_head_key.get(key) + if self.by_head_key and task not in (fitted if isinstance(fitted, list) else [fitted]): + raise ValueError(f'head_key {key!r} is calibrated for task {fitted}, served as {task}') + return self.values[task] + + def describe(self): + return {'schema': self.artifact['schema'], 'payload_sha256': self.payload_sha256, + 'file_sha256': self.file_sha256, 'per_task': dict(self.values), + 'by_head_key': dict(self.by_head_key), + 'applied': 'logits are divided by the task temperature before the softmax; Noul temperature ' + 'applies to the collapsed binary logit (p_yes = sigmoid(z/T)), choice temperature to ' + 'the listed options (softmax(logits[:n]/T)); applied exactly once per read'} + + +class NoTemperature: + """No calibration artifact bound: the readout is served raw at T = 1.""" + payload_sha256 = None + file_sha256 = None + + def is_identity(self): + return True + + def temperature(self, task, head_keys=()): + return TEMPERATURE + + def describe(self): + return {'schema': None, 'payload_sha256': None, 'file_sha256': None, 'per_task': {}, 'by_head_key': {}, + 'applied': 'none: raw readout at T = 1'} + + +def load_temperature(source): + """None -> NoTemperature; a dict or a path -> ReadoutTemperature (invalid fails closed). + + Loading from a path also records the file hash, which is what `shasum` returns; loading from a dict (the + embedded copy inside a serving binding) cannot know it, and the binding's provenance carries it instead. + """ + if source is None: + return NoTemperature() + if hasattr(source, 'temperature'): + return source + if isinstance(source, dict): + return ReadoutTemperature(source) + path = Path(source) + if not path.is_absolute(): + path = ROOT / path + if not path.exists(): + raise ValueError(f'readout temperature artifact not found: {path}') + raw = path.read_bytes() + return ReadoutTemperature(json.loads(raw), file_sha256=hashlib.sha256(raw).hexdigest()) + + +def _structural_keys(value): + """Keys anywhere in `value` whose own value is not a descriptive string (i.e. carries structure).""" + out = set() + if isinstance(value, dict): + for k, v in value.items(): + if not isinstance(v, str): + out.add(k) + out |= _structural_keys(v) + elif isinstance(value, list): + for v in value: + out |= _structural_keys(v) + return out + + +def _all_keys(value): + if isinstance(value, dict): + return set(value) | (set().union(*(_all_keys(v) for v in value.values())) if value else set()) + if isinstance(value, list): + return set().union(*(_all_keys(v) for v in value)) if value else set() + return set() + + diff --git a/src/solomon/engine.py b/src/solomon/engine.py new file mode 100644 index 0000000000000000000000000000000000000000..6efc3a4b257f76c6ad9b386423efa367f8ef57ac --- /dev/null +++ b/src/solomon/engine.py @@ -0,0 +1,108 @@ +"""Question-only CUDA runtime for immutable semantic-head checkpoints. + +v1.1 (Solomon v1.1): + * precision: 'bf16' (default: bf16 weights, fp32 recurrence, the trainer's mode), 'fp32' (the qualified v1 + numerics) or 'int8' (torchao weight-only int8 on the decoder linears). Recorded in the identity. + * head layout: any subset of the ten semantic heads loads, provided the yes/no head boolean/state4 is present. + The v1.1 heads file carries ONE merged yes/no head (boolean/state4) and no entity/multilabel heads; the + serving layer routes multi-label branches to it through the binding's head routing table. A non-v1 layout + is recorded in the identity (answer_heads), so a binding pins it. + * the document prefill keeps its final hidden states (and any tapped layer) for the trained evidence head. +""" +import copy +from contextlib import contextmanager +import hashlib +import json +from pathlib import Path +import threading +import numpy as np +from solomon.engine_numerics import CudaEngine +from solomon.heads import semantic_head_key + +def sha(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest() + +SEMANTIC_HEADS=('boolean/state4','entity/state4','multilabel/state4','single/choiceR','single/choiceS','single/sufficiency3', + 'ordered/choiceR','ordered/choiceS','ordered/sufficiency3','ordered/threshold4') +REQUIRED_HEADS=('boolean/state4',) + + +def load_heads(heads_path): + """{head_key: {'weight','bias'}} from a semantic-heads .npz. Tolerant of the merged v1.1 layout.""" + with np.load(heads_path,allow_pickle=False) as arrays: + heads={k[:-7]:{'weight':arrays[k].copy(),'bias':arrays[k[:-7]+'/bias'].copy()} + for k in arrays.files if k.endswith('/weight')} + unknown=sorted(set(heads)-set(SEMANTIC_HEADS)) + if unknown:raise ValueError('unknown semantic heads: '+', '.join(unknown)) + missing=[k for k in REQUIRED_HEADS if k not in heads] + if missing:raise ValueError('semantic heads file lacks the yes/no head: '+', '.join(missing)) + return heads + +class SolomonEngine(CudaEngine): + def __init__(self, adapter, heads_path, *, expected_adapter=None, expected_heads=None, correctness=None, precision='bf16', + model_dir=None, prefix_layers=()): + if expected_adapter and sha(adapter)!=expected_adapter:raise ValueError('adapter hash mismatch') + if expected_heads and sha(heads_path)!=expected_heads:raise ValueError('heads hash mismatch') + self.answer_heads=load_heads(heads_path) + self.keep_prefix_hidden=True;self.prefix_layers=tuple(int(i) for i in prefix_layers) + super().__init__(adapter=adapter,placement='question',precision=precision,**({'model_dir':model_dir} if model_dir else {})) + self.base_identity=dict(self.identity) + for head in self.answer_heads.values(): + if head['weight'].shape!=(10,5120) or head['bias'].shape!=(10,) or not all(np.isfinite(v).all() for v in head.values()): + raise ValueError('invalid semantic head') + self.correctness_models=correctness or {} + self._task=None;self._lock=threading.RLock() + if len(self.answer_heads)!=10:self.identity['answer_heads']=','.join(sorted(self.answer_heads)) # a v1 layout keeps the v1 key set + self.identity.update(trained_heads_sha256=sha(heads_path),answer_engine_sha256=sha(__file__), + answer_projection='trained-semantic-head-float32',base_fingerprint=self.base_identity['fingerprint']) + self.identity.pop('fingerprint',None) + self.identity['fingerprint']=hashlib.sha256(json.dumps(self.identity,sort_keys=True).encode()).hexdigest() + + @contextmanager + def task_context(self,task): + with self._lock: + previous=self._task;self._task=task + try:yield self + finally:self._task=previous + + def ask(self,state,block,n_letters,execution='cached',head_key=None,tap_layers=()): + with self._lock: + key=head_key or semantic_head_key(self._task,block) + if key not in self.answer_heads or not 2<=n_letters<=10:raise ValueError('unknown semantic head or width') + if execution not in ('cached','full'):raise ValueError('invalid execution') + torch=self.torch;p=state['prefix_tokens'];previous=self.ctx['start'];handles=[];taps={} + def hook(index): + def capture(module,args,output): + h=output[0] if isinstance(output,tuple) else output + # Intermediate states are normalized using the frozen final norm. + taps[str(index)]=self.lm.norm(h[:,-1:])[0,-1].detach().float().cpu().numpy().copy() + return capture + try: + for index in tap_layers: + if type(index)is not int or not 0<=index